[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [klange]\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Reporting Vulnerabilities and Exploits\n\nAs ToaruOS is not intended for serious real-world use, responsible disclosure should not typically be necessary: Issue reports for security vulnerabilities should be filed [directly on Github as regular issues](https://github.com/klange/toaruos/issues). There may be exceptions to this, eg. if you discover a remote exploit that could affect casual users or impacts the host system during the build process.\n\nReports are greatly appreciated, but my bandwidth to work on the OS is limited. While I will generally try to spend some time on quick fixes for issues that adversely affect the behavior of benign software, I may never get around to addressing vulnerabilities which require more careful exploits - but, please do still report these. As an exception to my general contribution guidelines, I am open to accepting unprompted code contributions related to resolving security issues.\n\n## For Users\n\nBeyond the usual boilerplate about the software being provided \"as is\" and \"without warranty\", potential users should understand that ToaruOS is not meant to be \"used\" at all. ToaruOS is intended as an educational tool - it is meant to be studied. While users are encouraged to run the OS in a virtual machine to that end, proper precautions should be taken. If the OS is exposed to untrusted users, it should be properly isolated and firewalled. The use of virtual machine hosts which employ tunnel devices when on an untrusted network is highly discouraged.\n\n## For CTF Operators\n\nToaruOS has been used in a handful of CTF competitions, which I find quite neat. If you are operating a CTF and have identified an existing vulnerability you hope competitors will find and exploit, I am happy to be informed ahead of time and won't spoil things.\n\nAdditionally, as a recommendation to CTF operators, there are many known TOCTOU vulnerabilities in ToaruOS that are only exploitable when SMP is enabled. These kinds of issues are likely to stick around for a while, so consider disabling SMP to make the attack surface smaller and more interesting.\n"
  },
  {
    "path": ".github/workflows/aarch64.yml",
    "content": "on: [push, workflow_dispatch]\nname: QEMU AArch64 virt\njobs:\n  build-image:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone Repository\n        uses: actions/checkout@v2\n        with:\n            fetch-depth: 0\n      - name: Clone Kuroko\n        uses: actions/checkout@v2\n        with:\n          repository: kuroko-lang/kuroko\n          path: kuroko\n      - name: Checkout Kuroko\n        run: git submodule update --init kuroko\n      - name: Pull Builder Image\n        run: docker pull toaruos/build-tools:aarch64\n      - name: Run Builder\n        run: docker run -v ${GITHUB_WORKSPACE}:/root/misaka -w /root/misaka -e LANG=C.UTF-8 -t toaruos/build-tools:aarch64 util/build-in-docker-aarch64.sh\n      - name: Upload virt Artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: aarch64-virt\n          path: |\n            misaka-kernel\n            ramdisk.igz\n            bootstub\n      - name: Upload rpi400 Artifacts\n        uses: actions/upload-artifact@v4\n        with:\n          name: aarch64-rpi400\n          path: |\n            kernel8.img\n"
  },
  {
    "path": ".github/workflows/x86_64.yml",
    "content": "on: [push, workflow_dispatch]\nname: x86-64 CD Image\njobs:\n  build-image:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Clone Repository\n        uses: actions/checkout@v2\n        with:\n            fetch-depth: 0\n      - name: Clone Kuroko\n        uses: actions/checkout@v2\n        with:\n          repository: kuroko-lang/kuroko\n          path: kuroko\n      - name: Checkout Kuroko\n        run: git submodule update --init kuroko\n      - name: Pull Builder Image\n        run: docker pull toaruos/build-tools:1.99.x\n      - name: Run Builder\n        run: docker run -v ${GITHUB_WORKSPACE}:/root/misaka -w /root/misaka -e LANG=C.UTF-8 -t toaruos/build-tools:1.99.x util/build-in-docker.sh\n      - name: Upload Branch Image\n        uses: actions/upload-artifact@v4\n        with:\n          name: build\n          path: |\n            image.iso\n      - name: Draft Release notes\n        if: startsWith(github.ref, 'refs/tags/v')\n        run: bash util/generate-release-notes.sh > notes.md\n      - name: Create Release\n        if: startsWith(github.ref, 'refs/tags/v')\n        uses: actions/create-release@v1\n        id: create_release\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: ToaruOS ${{ github.ref }}\n          body_path: ./notes.md\n          draft: true\n      - name: Upload Release Image\n        if: startsWith(github.ref, 'refs/tags/v')\n        uses: actions/upload-release-asset@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./image.iso\n          asset_name: image.iso\n          asset_content_type: application/x-iso9660-image\n"
  },
  {
    "path": ".gitignore",
    "content": "*.aux\n*.idx\n*.ilg\n*.ind\n*.log\n*.o\n*.a\n*.out\n*.pdf\n*.so\n*.swp\n*.swn\n*.toc\n*.ko\n*.pcap\n.gdb_history\n/ramdisk.tar\n/ramdisk.igz\n/misaka-kernel.64\n/misaka-kernel\n/kernel/symbols.S\n/util/build\n/util/local\n/util/cross\n/base/bin/*\n/base/etc/issue\n/base/etc/os-release\n/base/usr/bin/*\n/base/usr/lib/*\n/base/lib/kuroko/*\n/base/usr/share/games/doom\n/.make/\n/cdrom\n/fatbase\n/image.iso\n/boot/mbr.sys\n/bootstub\n/.arch\n/kernel8.img\n/kernel8.img.elf\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"util/binutils-gdb\"]\n\tpath = util/binutils-gdb\n\turl = ../../toaruos/binutils-gdb\n[submodule \"util/gcc\"]\n\tpath = util/gcc\n\turl = ../../toaruos/gcc\n[submodule \"kuroko\"]\n\tpath = kuroko\n\turl = ../../kuroko-lang/kuroko\n"
  },
  {
    "path": ".mailmap",
    "content": "K. Lange <klange@toaruos.org>\nK. Lange <klange@toaruos.org> <klange@dakko.us>\nK. Lange <klange@toaruos.org> <k@dakko.us>\nK. Lange <klange@toaruos.org> <kevin.lange@dakko.us>\nK. Lange <klange@toaruos.org> <kevin.lange@mujin.co.jp>\nK. Lange <klange@toaruos.org> <kevin.lange@phpwnage.com>\nK. Lange <klange@toaruos.org> <klange@yelp.com>\nMarkus Schober <schober1@bobrife.acm.uiuc.edu> <schober1@illinois.edu>\nMike Gerow <gerow@mgerow.com> <gerow.mike@gmail.com>\nTianyi Wang <wang103@illinois.edu> <tianyi@Rin.(none)>\nJozef Nagy <jozefnagy1133@yahoo.com> <38380751+jozefnagyoff@users.noreply.github.com>\nMai M. <mathew1800@gmail.com>\nOfek Lavie <ofeklavie@gmail.com>\n"
  },
  {
    "path": "AUTHORS",
    "content": "Maintainer and Author:\n\n    K. Lange\n\nMajor Contributors:\n\n    Mike Gerow\n    Dale Weiler\n    Matt White\n    Markus Schober\n\nOther Contributors:\n\n    Adam DiCarlo\n    David Hayman\n    Fabien Siron\n    Gil Mendes\n    Ivailo Monev\n    Josh Kilmer\n    Lioncash\n    Noah Rosamilia\n    Ofek\n    Patrick Lucas\n    Peter Harliman Liem\n    Shawn Anastasio\n    Steve Jenson\n    Tianyi Wang\n    Tyler Bindon\n"
  },
  {
    "path": "LICENSE",
    "content": "\nUniversity of Illinois/NCSA Open Source License\n\nCopyright (c) 2011-2022 K Lange, et al. (hereafter [fullname]). All rights reserved.\n\nDeveloped by: ToaruOS (hereafter [project])\n\nPermission is hereby granted, free of charge, to any person\nobtaining a copy of this software and associated documentation files\n(the \"Software\"), to deal with the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge,\npublish, distribute, sublicense, and/or sell copies of the Software,\nand to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimers.\n\n* Redistributions in binary form must reproduce the above copyright\n  notice, this list of conditions and the following disclaimers in the\n  documentation and/or other materials provided with the distribution.\n\n* Neither the names of [fullname], [project] nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this Software without specific prior written permission.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\nOR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nCONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH\nTHE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# ToaruOS 2.0 root Makefile\nTOOLCHAIN=util\nBASE=base\nexport PATH := $(shell $(TOOLCHAIN)/activate.sh)\n\nARCH ?= $(shell $(TOOLCHAIN)/arch.sh)\n\ninclude build/${ARCH}.mk\n\n# Cross compiler binaries\nCC = ${TARGET}-gcc\nNM = ${TARGET}-nm\nCXX= ${TARGET}-g++\nAR = ${TARGET}-ar\nAS = ${TARGET}-as\nOC = ${TARGET}-objcopy\nSTRIP= ${TARGET}-strip\n\n# CFLAGS for kernel objects and modules\nKERNEL_CFLAGS  = -ffreestanding -O2 -std=gnu11 -g -static\nKERNEL_CFLAGS += -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wstrict-prototypes\nKERNEL_CFLAGS += -pedantic -Wwrite-strings ${ARCH_KERNEL_CFLAGS}\n\n# Defined constants for the kernel\nKERNEL_CFLAGS += -D_KERNEL_ -DKERNEL_ARCH=${ARCH}\nKERNEL_CFLAGS += -DKERNEL_GIT_TAG=$(shell util/make-version)\n\n# Automatically find kernel sources from relevant paths\nKERNEL_OBJS =  $(patsubst %.c,%.o,$(wildcard kernel/*.c))\nKERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*.c))\nKERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/arch/${ARCH}/*.c))\n\n# Assembly sources only come from the arch-dependent directory\nKERNEL_ASMOBJS  = $(filter-out kernel/symbols.o,$(patsubst %.S,%.o,$(wildcard kernel/arch/${ARCH}/*.S)))\n\n# These sources are used to determine if we should update symbols.o\nKERNEL_SOURCES  = $(wildcard kernel/*.c) $(wildcard kernel/*/*.c) $(wildcard kernel/${ARCH}/*/*.c)\nKERNEL_SOURCES += $(wildcard kernel/arch/${ARCH}/*.S)\n\n# Kernel modules are one file = one module; if you want to build more complicated\n# modules, you could potentially use `ld -r` to turn multiple source objects into\n# a single relocatable object file.\nARCH_ENABLED_MODS = $(shell util/valid-modules.sh $(ARCH))\nMODULES = $(patsubst modules/%.c,$(BASE)/mod/%.ko,$(foreach mod,$(ARCH_ENABLED_MODS),modules/$(mod).c))\n\nEMU = qemu-system-${ARCH}\n\nAPPS=$(patsubst apps/%.c,%,$(wildcard apps/*.c))\nAPPS_X=$(foreach app,$(APPS),$(BASE)/bin/$(app))\nAPPS_Y=$(foreach app,$(APPS),.make/$(app).mak)\nAPPS_SH=$(patsubst apps/%.sh,%.sh,$(wildcard apps/*.sh))\nAPPS_SH_X=$(foreach app,$(APPS_SH),$(BASE)/bin/$(app))\nAPPS_KRK=$(patsubst apps/%.krk,%.krk,$(wildcard apps/*.krk))\nAPPS_KRK_X=$(foreach app,$(APPS_KRK),$(BASE)/bin/$(app))\n\nLIBS=$(patsubst lib/%.c,%,$(wildcard lib/*.c))\nLIBS_X=$(foreach lib,$(LIBS),$(BASE)/lib/libtoaru_$(lib).so)\nLIBS_Y=$(foreach lib,$(LIBS),.make/$(lib).lmak)\n\nKRK_MODS = $(patsubst kuroko/src/modules/module_%.c,$(BASE)/lib/kuroko/%.so,$(wildcard kuroko/src/modules/module_*.c))\nKRK_MODS += $(patsubst kuroko/modules/%,$(BASE)/lib/kuroko/%,$(wildcard kuroko/modules/*.krk kuroko/modules/*/*.krk kuroko/modules/*/*/.krk kuroko/modules/*/*/*.krk))\nKRK_MODS += $(patsubst lib/kuroko/%,$(BASE)/lib/kuroko/%,$(wildcard lib/kuroko/*.krk))\nKRK_MODS_X = $(patsubst lib/kuroko/%.c,$(BASE)/lib/kuroko/%.so,$(wildcard lib/kuroko/*.c))\nKRK_MODS_Y = $(patsubst lib/kuroko/%.c,.make/%.kmak,$(wildcard lib/kuroko/*.c))\n\nCFLAGS= -O2 -std=gnu11 -I. -Iapps -fplan9-extensions -Wall -Wextra -Wno-unused-parameter ${ARCH_USER_CFLAGS}\nLIBC_CFLAGS = -O2 -std=gnu11 -ffreestanding -Wall -Wextra -Wno-unused-parameter ${ARCH_USER_CFLAGS}\n\nLIBC_OBJS  = $(patsubst %.c,%.o,$(wildcard libc/*.c))\nLIBC_OBJS += $(patsubst %.c,%.o,$(wildcard libc/*/*.c))\nLIBC_OBJS += $(patsubst %.c,%.o,$(wildcard libc/arch/${ARCH}/*.c))\n\nGCC_SHARED = $(BASE)/usr/lib/libgcc_s.so.1 $(BASE)/usr/lib/libgcc_s.so\n\nCRTS  = $(BASE)/lib/crt0.o $(BASE)/lib/crti.o $(BASE)/lib/crtn.o\n\nLC = $(BASE)/lib/libc.so $(GCC_SHARED)\n\n.PHONY: all system clean run shell\n\n$(BASE)/mod/%.ko: modules/%.c | dirs\n\t${CC} -c ${KERNEL_CFLAGS} -fno-pie -mcmodel=large  -o $@ $<\n\nramdisk.igz: $(wildcard $(BASE)/* $(BASE)/*/* $(BASE)/*/*/* $(BASE)/*/*/*/* $(BASE)/*/*/*/*/*) $(APPS_X) $(LIBS_X) $(KRK_MODS_X) $(BASE)/bin/kuroko $(BASE)/lib/ld.so $(BASE)/lib/libm.so $(APPS_KRK_X) $(KRK_MODS) $(APPS_SH_X) $(MODULES) $(BASE)/etc/issue $(BASE)/etc/os-release\n\tpython3 util/createramdisk.py\n\n$(BASE)/etc/issue: kernel/sys/version.c util/generate-etc-issue.sh\n\tsh util/generate-etc-issue.sh > $@\n\n$(BASE)/etc/os-release: kernel/sys/version.c util/generate-etc-os-release.sh\n\tsh util/generate-etc-os-release.sh > $@\n\nKRK_SRC = $(sort $(wildcard kuroko/src/*.c))\n$(BASE)/bin/kuroko: $(KRK_SRC) $(CRTS)  lib/rline.c | $(LC)\n\t$(CC) $(CFLAGS) -o $@ -Wl,--export-dynamic -Ikuroko/src $(KRK_SRC) lib/rline.c\n\n$(BASE)/lib/kuroko/%.so: kuroko/src/modules/module_%.c| dirs $(LC)\n\t$(CC) $(CFLAGS) -shared -fPIC -Ikuroko/src -o $@ $<\n\n$(BASE)/lib/kuroko/%.krk: kuroko/modules/%.krk | dirs\n\tmkdir -p $(dir $@)\n\tcp $< $@\n\n$(BASE)/lib/kuroko/%.krk: lib/kuroko/%.krk | dirs\n\tmkdir -p $(dir $@)\n\tcp $< $@\n\n$(BASE)/lib/libkuroko.so: $(KRK_SRC) | $(LC)\n\t$(CC) -O2 -shared -fPIC -Ikuroko/src -o $@ $(filter-out kuroko/src/kuroko.c,$(KRK_SRC))\n\n$(BASE)/lib/ld.so: linker/linker.c $(BASE)/lib/libc.a | dirs $(LC)\n\t$(CC) -g -static -Wl,-static $(CFLAGS) -z max-page-size=0x1000 -o $@ -Os -T linker/link.ld $<\n\nkernel/sys/version.o: ${KERNEL_SOURCES}\n\nkernel/symbols.o: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} util/gensym.krk\n\t-rm -f kernel/symbols.o\n\t${NM} -g -f p ${KERNEL_ASMOBJS} ${KERNEL_OBJS} | kuroko util/gensym.krk > kernel/symbols.S\n\t${CC} -c kernel/symbols.S -o $@\n\nkernel/%.o: kernel/%.S\n\t${CC} -c $< -o $@\n\nHEADERS = $(wildcard base/usr/include/kernel/*.h) $(wildcard base/usr/include/kernel/*/*.h)\n\nkernel/%.o: kernel/%.c ${HEADERS}\n\t${CC} ${KERNEL_CFLAGS} -nostdlib -g -Iinclude -c -o $@ $<\n\nclean:\n\t-rm -f ${KERNEL_ASMOBJS}\n\t-rm -f ${KERNEL_OBJS} $(MODULES)\n\t-rm -f kernel/symbols.o kernel/symbols.S misaka-kernel misaka-kernel.64\n\t-rm -f ramdisk.tar ramdisk.igz \n\t-rm -f $(APPS_Y) $(LIBS_Y) $(KRK_MODS_Y) $(KRK_MODS)\n\t-rm -f $(APPS_X) $(LIBS_X) $(KRK_MODS_X) $(APPS_KRK_X) $(APPS_SH_X)\n\t-rm -f $(BASE)/lib/crt0.o $(BASE)/lib/crti.o $(BASE)/lib/crtn.o\n\t-rm -f $(BASE)/lib/libc.so $(BASE)/lib/libc.a\n\t-rm -f $(LIBC_OBJS) $(BASE)/lib/ld.so $(BASE)/lib/libkuroko.so $(BASE)/lib/libm.so\n\t-rm -f $(BASE)/bin/kuroko\n\t-rm -f $(GCC_SHARED)\n\t-rm -f boot/efi/*.o boot/bios/*.o\n\nlibc/%.o: libc/%.c base/usr/include/syscall.h \n\t$(CC) ${LIBC_CFLAGS} -fPIC -c -o $@ $<\n\n.PHONY: libc\nlibc: $(BASE)/lib/libc.a $(BASE)/lib/libc.so\n\n$(BASE)/lib/libc.a: ${LIBC_OBJS} $(CRTS)\n\t$(AR) cr $@ $(LIBC_OBJS)\n\n$(BASE)/lib/libc.so: ${LIBC_OBJS} | $(CRTS)\n\t${CC} -nodefaultlibs -shared -fPIC -o $@ $^ -lgcc\n\n$(BASE)/lib/crt%.o: libc/arch/${ARCH}/crt%.S\n\t${AS} -o $@ $<\n\n$(BASE)/usr/lib/%: $(TOOLCHAIN)/local/${TARGET}/lib/% | dirs\n\tcp -a $< $@\n\t-$(STRIP) $@\n\n$(BASE)/lib/libm.so: util/libm.c\n\t$(CC) -shared -nostdlib -fPIC -o $@ $<\n\n$(BASE)/dev:\n\tmkdir -p $@\n$(BASE)/tmp:\n\tmkdir -p $@\n$(BASE)/proc:\n\tmkdir -p $@\n$(BASE)/bin:\n\tmkdir -p $@\n$(BASE)/lib:\n\tmkdir -p $@\n$(BASE)/cdrom:\n\tmkdir -p $@\n$(BASE)/var:\n\tmkdir -p $@\n$(BASE)/mod:\n\tmkdir -p $@\n$(BASE)/lib/kuroko:\n\tmkdir -p $@\n$(BASE)/usr/lib:\n\tmkdir -p $@\n$(BASE)/usr/bin:\n\tmkdir -p $@\nboot/efi:\n\tmkdir -p $@\nboot/bios:\n\tmkdir -p $@\nfatbase/efi/boot:\n\tmkdir -p $@\ncdrom:\n\tmkdir -p $@\n.make:\n\tmkdir -p .make\ndirs: $(BASE)/dev $(BASE)/tmp $(BASE)/proc $(BASE)/bin $(BASE)/lib $(BASE)/cdrom $(BASE)/usr/lib $(BASE)/usr/bin $(BASE)/lib/kuroko cdrom $(BASE)/var fatbase/efi/boot .make $(BASE)/mod boot/efi boot/bios\n\nifeq (,$(findstring clean,$(MAKECMDGOALS))$(findstring $(BUILD_KRK),$(MAKECMDGOALS)))\n-include ${APPS_Y}\n-include ${LIBS_Y}\n-include ${KRK_MODS_Y}\nendif\n\n.make/%.lmak: lib/%.c util/auto-dep.krk | dirs $(CRTS)\n\tkuroko util/auto-dep.krk --makelib $< > $@\n\n.make/%.mak: apps/%.c util/auto-dep.krk | dirs $(CRTS)\n\tkuroko util/auto-dep.krk --make $< > $@\n\n.make/%.kmak: lib/kuroko/%.c util/auto-dep.krk | dirs\n\tkuroko util/auto-dep.krk --makekurokomod $< > $@\n\n$(BASE)/bin/%.sh: apps/%.sh\n\tcp $< $@\n\tchmod +x $@\n\n$(BASE)/bin/%.krk: apps/%.krk\n\tcp $< $@\n\tchmod +x $@\n\n.PHONY: libs\nlibs: $(LIBS_X)\n\n.PHONY: apps\napps: $(APPS_X)\n\nSOURCE_FILES  = $(wildcard kernel/*.c kernel/*/*.c kernel/*/*/*.c kernel/*/*/*/*.c)\nSOURCE_FILES += $(wildcard apps/*.c linker/*.c libc/*.c libc/*/*.c lib/*.c lib/kuroko/*.c)\nSOURCE_FILES += $(wildcard kuroko/src/*.c kuroko/src/*.h kuroko/src/*/*.c kuroko/src/*/*.h)\nSOURCE_FILES += $(wildcard $(BASE)/usr/include/*.h $(BASE)/usr/include/*/*.h $(BASE)/usr/include/*/*/*.h)\ntags: $(SOURCE_FILES)\n\tctags -f tags $(SOURCE_FILES)\n\n"
  },
  {
    "path": "README.md",
    "content": "# ToaruOS\n\nToaruOS is a \"complete\" operating system for x86-64 PCs and experimental support for ARMv8.\n\nWhile many independent, hobby, and research OSes aim to experiment with new designs, ToaruOS is intended as an educational resource, providing a representative microcosm of functionality found in major desktop operating systems.\n\nThe OS includes a kernel, bootloader, dynamic shared object linker, C standard library, its own composited windowing system, a dynamic bytecode-compiled programming language, advanced code editor, and dozens of other utilities and example applications.\n\nThere are no external runtime dependencies and all required source code, totalling roughly 100k lines of (primarily) C, is included in this repository, save for [Kuroko](https://github.com/kuroko-lang/kuroko), which lives separately.\n\n![Screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-38-12.png)\n*Demonstration of ToaruOS's UI and some applications.*\n\n## History\n\n> I have been working on ToaruOS for over a decade now, and my goals have changed over the years.\n>\n> When I first started the project in December 2010, my aim was to \"learn by doing\" - studying Unix-like systems by making one from scratch.\n> I had been a contributor to Compiz, one of the first widely-used compositing window managers for X11, a few years prior, and somewhat naturally ToaruOS gained a GUI built on similar concepts early on.\n>\n> For its original 1.0 release in 2015, ToaruOS was not the \"completely from scratch\" OS it has since become.\n> Newlib provided the libc, and the GUI was built on Cairo, libpng, and Freetype.\n> In the middle of 2018, I started a new project to replace these third-party components, which was eventually completed and merged to become ToaruOS 1.6.\n>\n> Through out the project, ToaruOS has also attracted quite a few beginner OS developers who have tried to use it as a reference.\n> ToaruOS's kernel, however, was a source of personal embarrassment for me, and in April 2021, after a long hiatus, I began work on a new one.\n> The result was Misaka: a new 64-bit, SMP-enabled kernel. Misaka was merged in May and started the 1.99 series of beta releases leading up to ToaruOS 2.0.\n\n## Features\n\n- **Dynamically linked userspace** with shared libraries and `dlopen`.\n- **Composited graphical UI** with software acceleration and a late-2000s design inspiration.\n- **VM integration** for absolute mouse and automatic display sizing in VirtualBox and VMware Workstation.\n- **Unix-like terminal interface** including a feature-rich terminal emulator and several familiar utilities.\n- **Optional third-party ports** including GCC 10.3, Binutils, SDL1.2, Quake, and more.\n\n### Notable Components\n\n- **Misaka** (kernel), [kernel/](kernel/), a hybrid modular kernel, and the core of the operating system.\n- **Yutani** (window compositor), [apps/compositor.c](apps/compositor.c), manages window buffers, layout, and input routing.\n- **Bim** (text editor), [apps/bim.c](apps/bim.c), is a Vim-inspired editor with syntax highlighting.\n- **Terminal**, [apps/terminal.c](apps/terminal.c), xterm-esque terminal emulator with 24-bit color support.\n- **ld.so** (dynamic linker/loader), [linker/linker.c](linker/linker.c), loads dynamically-linked ELF binaries.\n- **Esh** (shell), [apps/esh.c](apps/esh.c), supports pipes, redirections, variables, etc.\n- **Kuroko** (interpreter), [kuroko/](https://kuroko-lang.github.io/), a dynamic bytecode-compiled programming language.\n\n## Current Goals\n\nThe following projects are currently in progress:\n\n- **Rewrite the network stack** for greater throughput, stability, and server support.\n- **Improve SMP performance** with better scheduling and smarter userspace synchronization functions.\n- **Support more hardware** with new device drivers for AHCI, USB, virtio devices, etc.\n- **Bring back ports** from ToaruOS \"Legacy\", like muPDF and Mesa.\n- **Improve POSIX coverage** especially in regards to signals, synchronization primitives, as well as by providing more common utilities.\n- **Continue to improve the C library** which remains quite incomplete compared to Newlib and is a major source of issues with bringing back old ports.\n- **Replace third-party development tools** to get the OS to a state where it is self-hosting with just the addition of a C compiler.\n- **Implement a C compiler toolchain** in [toarucc](https://github.com/klange/toarucc).\n\n## Building / Installation\n\n### Building With Docker\n\nGeneral users hoping to build ToaruOS from source are recommended to fork the repository on Github and make use of the Github CI pipeline.\n\nFor those looking to build locally on an appropriately configured Linux host with Docker, a build container is available. The ToaruOS repository should be used as a bind mount at `/root/misaka` and `util/build-in-docker.sh` can be run within this container to complete the compilation process:\n\n    git clone https://github.com/klange/toaruos\n    cd toaruos\n    git submodule update --init kuroko\n    docker pull toaruos/build-tools:1.99.x\n    docker run -v `pwd`:/root/misaka -w /root/misaka -e LANG=C.UTF-8 -t toaruos/build-tools:1.99.x util/build-in-docker.sh\n\nAfter building like this, you can run the various utility targets (`make run`, etc.). Try `make shell` to run a ToaruOS shell using a serial port with QEMU.\n\n### Build Process Internals\n\nThe `Makefile` uses a Kuroko tool, `auto-dep.krk`, to generate additional Makefiles for the userspace applications and libraries, automatically resolving dependencies based on `#include` directives.\n\nIn an indeterminate order, the C library, kernel, userspace librares and applications are built, combined into a compressed archive for use as a ramdisk, and then packaged into an ISO9660 filesystem image.\n\n### Project Layout\n\n- **apps** - Userspace applications, all first-party.\n- **base** - Ramdisk root filesystem staging directory. Includes C headers in `base/usr/include`, as well as graphical resources for the compositor and window decorator.\n- **boot** - BIOS and EFI loader with interactive menus.\n- **build** - Auxiliary build scripts for platform ports.\n- **kernel** - The Misaka kernel.\n- **kuroko** - Submodule checkout of the Kuroko interpreter.\n- **lib** - Userspace libraries.\n- **libc** - C standard library implementation.\n- **linker** - Userspace dynamic linker/loader, implements shared library support.\n- **modules** - Loadable driver modules for the kernel.\n- **util** - Utility scripts, staging directory for the toolchain (binutils/gcc).\n- **.make** - Generated Makefiles.\n\n### Filesystem Layout\n\nThe root filesystem is set up as follows:\n\n- `bin`: First-party applications.\n- `cdrom`: Mount point for the CD, if available.\n- `dev`: Virtual device directory, generated by the kernel.\n  - `net`: Network interface devices.\n  - `pex`: Packet Exchange hub, lists accessible IPC services.\n  - `pts`: PTY secondaries, endpoints for TTYs.\n- `etc`: Configuration files, startup scripts.\n- `home`: User directories.\n- `lib`: First-party libraries\n  - `kuroko`: Kuroko modules.\n- `mod`: Loadable kernel modules.\n- `proc`: Virtual files that present kernel state.\n  - `1`, etc.: Virtual files with status information for individual processes.\n- `src`: Source files, see \"Project Layout\" section above.\n- `tmp`: Mounted as a read/write tmpfs normally.\n- `usr`: Userspace resources\n  - `bin`: Third-party applications, normally empty until packages are installed.\n  - `include`: Header files, including potentially ones from third-party packages.\n  - `lib`: Third-party libraries. Should have `libgcc_s.so` by default.\n  - `share`: Various resources.\n    - `bim`: Syntax highlighting and themes for the text editor.\n    - `cursor`: Mouse cursor sprites.\n    - `fonts`: TrueType font files. Live CDs ship with Deja Vu Sans.\n    - `games`: Dumping ground for game-related resource files, like Doom wads.\n    - `help`: Documentation files for the Help Browser application.\n    - `icons`: PNG icons, divided into further directories by size.\n    - `ttk`: Spritesheet resources for the window decorator and widget library.\n    - `wallpapers`: JPEG wallpapers.\n- `var`: Runtime files, including package manager manifest cache, PID files, some lock files, etc.\n\n## Running ToaruOS\n\n### VirtualBox and VMware Workstation\n\nThe best end-user experience with ToaruOS will be had in either of these virtual machines, as ToaruOS has support for their automatic display sizing and absolute mouse positioning.\n\nSet up a new VM for an \"other\" 64-bit guest, supply it with at least 1GiB of RAM, attach the CD image, remove or ignore any hard disks, and select an Intel Gigabit NIC. Two or more CPUs are recommended, as well.\n\n![VirtualBox screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-39-27.png)\n*ToaruOS running in VirtualBox.*\n\n![VMware screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-41-17.png)\n*ToaruOS running in VMware Workstation Player.*\n\nBy default, the bootloader will pass a flag to the VirtualBox device driver to disable \"Seamless\" support as the implementation has a performance overhead. To enable Seamless mode, use the bootloader menu to check the \"VirtualBox Seamless\" option before booting. The menu also has options to disable automatic guest display sizing if you experience issues with this feature.\n\n### QEMU\n\nMost development of ToaruOS happens in QEMU, as it provides the most flexibility in hardware and the best debugging experience. A recommended QEMU command line in an Ubuntu 20.04 host is:\n\n```\nqemu-system-x86_64 -enable-kvm -m 1G -device AC97 -cdrom image.iso -smp 2\n```\n\nReplace `-enable-kvm` with `-accel hvm` or `-accel haxm` as appropriate on host platforms without KVM, or remove it to try under QEMU's TCG software emulation.\n\nNote that QEMU command line options are not stable and these flags may produce warnings in newer versions.\n\nThe option `-M q35` will replace the PIIX chipset emulation with a newer one, which has the side effect of switching the IDE controller for a SATA one. This can result in faster boot times at the expense of ToaruOS not being able to read its own CD at runtime until I get around to finishing my AHCI driver.\n\n### Other\n\nToaruOS has been successfully tested on real hardware. If the native BIOS or EFI loaders fail to function, try booting with Grub. ToaruOS complies with the \"Multiboot\" and \"Multiboot 2\" specs so it may be loaded with either the `multiboot` or `multiboot2` commands as follows:\n\n```\nmultiboot2 /path/to/misaka-kernel root=/dev/ram0 migrate vid=auto start=live-session\nmodule2 /path/to/ramdisk.igz\nset gfxpayload=keep\n```\n\n![Native photo](https://klange.dev/s/IMG_8387.jpg)\n*ToaruOS running natively from a USB stick on a ThinkPad T410.*\n\n## License\n\nAll first-party parts of ToaruOS are made available under the terms of the University of Illinois / NCSA License, which is a BSD-style permissive license.\nUnless otherwise specified, this is the original and only license for all files in this repository - just because a file does not have a copyright header does not mean it isn't under this license.\nToaruOS is intended as an educational reference, and I encourage the use of my code, but please be sure you follow the requirements of the license.\nYou may redistribute code under the NCSA license, as well as make modifications to the code and sublicense it under other terms (such as the GPL, or a proprietary license), but you must always include the copyright notice specified in the license as well as make the full text of the license (it's only a couple paragraphs) available to end-users.\n\nWhile most of ToaruOS is written entirely by myself, be sure to include other authors where relevant, such as with [Mike's audio subsystem](https://github.com/klange/toaruos/blob/master/kernel/audio/snd.c) or [Dale's string functions](https://github.com/klange/toaruos/blob/master/kernel/misc/string.c).\n\nSome components of ToaruOS, such as [Kuroko](https://github.com/kuroko-lang/kuroko/blob/9f6160092ecece0f2c18b63c066151cbe0ded1bb/LICENSE) or [bim](https://github.com/klange/toaruos/blob/master/apps/bim.c#L3) have different but compatible terms.\n\n## Community\n\n### Mirrors\n\nToaruOS is regularly mirrored to multiple Git hosting sites.\n\n- Gitlab: [toaruos/toaruos](https://gitlab.com/toaruos/toaruos)\n- GitHub: [klange/toaruos](https://github.com/klange/toaruos)\n- Bitbucket: [klange/toaruos](https://bitbucket.org/klange/toaruos)\n- ToaruOS.org: [klange/toaruos](https://git.toaruos.org/klange/toaruos)\n\n### IRC\n\n`#toaruos` on Libera (`irc.libera.chat`)\n\n## FAQs\n\n### Is ToaruOS self-hosting?\n\nIndividual applications and libraries can be built by installing the `build-essential` metapackage from the repository, which will pull in `gcc` and `binutils`.\nSources are available in the `/src` directory on the live CD in a similar layout to this repository, and the `auto-dep.krk` utility script is also available.\n\nFor building ramdisks, finalized kernels, or CD images, some components are currently unavailable.\nIn particular, the [build script for ramdisks](util/createramdisk.py) is still written in Python and depends on its `tarfile` module and `zlib` support.\nPreviously, with a capable compiler toolchain, ToaruOS 1.x was able to build its own kernel, userspace, libraries, and bootloader, and turn these into a working ISO CD image through a Python script that performed a similar function to the Makefile.\n\nToaruOS is not currently capable of building most of its ports, due to a lack of a proper POSIX shell and Make implementation. These are eventual goals of the project.\n\n### Is ToaruOS a Linux distribution?\n\nNo, not at all. There is no code from Linux anywhere in ToaruOS, nor were Linux sources used as a reference material.\n\nToaruOS is a completely independent project, and all code in this repository - which is the entire codebase of the operating system, including its kernel, bootloaders, libraries, and applications - is original, written by myself and a handful of contributors over the course of ten years.\nThe complete source history, going back to when ToaruOS was nothing more than a baremetal \"hello world\" can be tracked through this git repository.\n\n### When you say \"complete\"...\n\nToaruOS is complete in the sense that it covers the whole range of functionality for an OS: It is not \"just a kernel\" or \"just a userspace\".\n\nToaruOS is _not_ complete in the sense of being \"done\".\n\n### Is ToaruOS POSIX-compliant?\n\nWhile I aim to support POSIX interfaces well enough for software to be ported, strict implementation of the standard is not a major goal of the OS, and full compliance may even be undesirable.\n\n### Are contributions accepted?\n\nToaruOS is a personal project, not a community project. Contributions in the form of code should be discussed in advance. Ports and other work outside of the repo, however, are a great way to help out.\n\nYou can also help by contributing to [Kuroko](https://github.com/kuroko-lang/kuroko) - which is part of why it's kept as a separate repository.\n"
  },
  {
    "path": "apps/about.c",
    "content": "/**\n * @brief about - Show an \"About <Application>\" dialog.\n *\n * By default, shows \"About ToaruOS\", suitable for use as an application\n * menu entry. Optionally, takes arguments specifying another application\n * to describe, suitable for the \"Help > About\" menu bar entry.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2019 K. Lange\n */\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/markup_text.h>\n\n#include <sys/utsname.h>\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\nstatic sprite_t logo;\n\nstatic int32_t width = 350;\nstatic int32_t height = 250;\nstatic char * version_str;\n\nstatic char * icon_path;\nstatic char * title_str;\nstatic char * version_str;\nstatic char * copyright_str[20] = {NULL};\n\nstatic int center_x(int x) {\n\treturn (width - x) / 2;\n}\n\nstatic void draw_string(int y, const char * string, int mode, uint32_t color) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\tstruct MarkupState * renderer = markup_setup_renderer(NULL, 0, 0, 0, 1);\n\tmarkup_set_base_font_size(renderer, 13);\n\tmarkup_set_base_state(renderer, mode);\n\tmarkup_push_string(renderer, string);\n\tint calcwidth = markup_finish_renderer(renderer);\n\n\trenderer = markup_setup_renderer(ctx, bounds.left_width + center_x(calcwidth), bounds.top_height + 10 + logo.height + 10 + y + 13, color, 0);\n\tmarkup_set_base_font_size(renderer, 13);\n\tmarkup_set_base_state(renderer, mode);\n\tmarkup_push_string(renderer, string);\n\tmarkup_finish_renderer(renderer);\n}\n\nstatic void redraw(void) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdraw_fill(ctx, rgb(204,204,204));\n\tdraw_sprite(ctx, &logo, bounds.left_width + center_x(logo.width), bounds.top_height + 10);\n\n\tdraw_string(0, version_str, MARKUP_TEXT_STATE_BOLD, rgb(0,0,0));\n\n\tint offset = 20;\n\n\tfor (char ** copy_str = copyright_str; *copy_str; ++copy_str) {\n\t\tif (**copy_str == '-') {\n\t\t\toffset += 10;\n\t\t} else if (**copy_str == '%') {\n\t\t\tdraw_string(offset, *copy_str+1, 0, rgb(0,0,255));\n\t\t\toffset += 20;\n\t\t} else {\n\t\t\tdraw_string(offset, *copy_str, 0, rgb(0,0,0));\n\t\t\toffset += 20;\n\t\t}\n\t}\n\n\twindow->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE;\n\trender_decorations(window, ctx, title_str);\n\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nstatic void init_default(void) {\n\ttitle_str = \"About ToaruOS\";\n\ticon_path = \"/usr/share/logo_login.png\";\n\n\t{\n\t\tversion_str = malloc(100);\n\t\tstruct utsname u;\n\t\tuname(&u);\n\t\tchar * tmp = strstr(u.release, \"-\");\n\t\tif (tmp) {\n\t\t\t*tmp = '\\0';\n\t\t}\n\t\tsprintf(version_str, \"ToaruOS %s\", u.release);\n\t}\n\n\tcopyright_str[0] = \"© 2011-2026 K. Lange, et al.\";\n\tcopyright_str[1] = \"-\";\n\tcopyright_str[2] = \"ToaruOS is free software released under the\";\n\tcopyright_str[3] = \"NCSA/University of Illinois license.\";\n\tcopyright_str[4] = \"-\";\n\tcopyright_str[5] = \"%https://toaruos.org\";\n\tcopyright_str[6] = \"%https://github.com/klange/toaruos\";\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\twidth  = w - bounds.width;\n\theight = h - bounds.height;\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n}\n\nint main(int argc, char * argv[]) {\n\tint req_center_x, req_center_y;\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\tmarkup_text_init();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\twindow = yutani_window_create_flags(yctx, width + bounds.width, height + bounds.height, YUTANI_WINDOW_FLAG_DIALOG_ANIMATION);\n\treq_center_x = yctx->display_width / 2;\n\treq_center_y = yctx->display_height / 2;\n\n\tif (argc < 2) {\n\t\tinit_default();\n\t} else if (argc < 5) {\n\t\tfprintf(stderr, \"Invalid arguments.\\n\");\n\t\treturn 1;\n\t} else {\n\t\ttitle_str = argv[1];\n\t\ticon_path = argv[2];\n\t\tversion_str = argv[3];\n\n\t\tint i = 0;\n\t\tchar * me = argv[4], * end;\n\t\tdo {\n\t\t\tcopyright_str[i] = me;\n\t\t\ti++;\n\t\t\tend = strchr(me,'\\n');\n\t\t\tif (end) {\n\t\t\t\t*end = '\\0';\n\t\t\t\tme = end+1;\n\t\t\t}\n\t\t} while (end);\n\n\t\tif (argc > 6) {\n\t\t\treq_center_x = atoi(argv[5]);\n\t\t\treq_center_y = atoi(argv[6]);\n\t\t}\n\t}\n\n\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\n\tyutani_window_advertise_icon(yctx, window, title_str, \"star\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\tload_sprite(&logo, icon_path);\n\n\tredraw();\n\n\tint playing = 1;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/basename.c",
    "content": "/**\n * @brief basename - print file name\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <libgen.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: expected argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tchar * c = basename(argv[1]);\n\n\tif (argc > 2) {\n\t\tchar * suffix = argv[2];\n\t\tchar * found = strstr(c + strlen(c) - strlen(suffix), suffix);\n\t\tif (found && (found - c == (int)(strlen(c)-strlen(suffix)))) {\n\t\t\t*found = '\\0';\n\t\t}\n\t}\n\n\tfprintf(stdout, \"%s\\n\", c);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/beep.c",
    "content": "/**\n * @brief Implementation of the 'beep' utility.\n *\n * I've tried to get the functionality here as close to the common\n * utility available on Linux systems so that \"beep music\" can be\n * played back.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nint spkr = 0;\n\nstruct spkr {\n\tint length;\n\tint frequency;\n};\n\nstatic void note(int length, int frequency) {\n\tstruct spkr s = {\n\t\t.length = length,\n\t\t.frequency = frequency,\n\t};\n\n\twrite(spkr, &s, sizeof(s));\n}\n\n/* Stolen from the Linux tool */\n#define DEFAULT_FREQ  440.0\n#define DEFAULT_LEN   200\n#define DEFAULT_DELAY 100\n\nstatic int repetitions = 1;\nstatic float frequency = DEFAULT_FREQ;\nstatic int length      = DEFAULT_LEN;\nstatic int delay       = DEFAULT_DELAY;\nstatic int beep_after  = 0;\n\nvoid beep(void) {\n\tfor (int i = 0; i < repetitions; ++i) {\n\t\tnote(length, frequency * 10);\n\t\tif (delay && ((i != repetitions - 1) || beep_after)) {\n\t\t\tusleep(delay * 1000);\n\t\t}\n\t}\n\n}\n\nint main(int argc, char * argv[]) {\n\n\tspkr = open(\"/dev/spkr\", O_WRONLY);\n\tif (spkr == -1) {\n\t\tfprintf(stderr, \"%s: could not open speaker\\n\", argv[0]);\n\t}\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?hr:f:l:d:D:n\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'h':\n\t\t\tcase '?':\n\t\t\t\tfprintf(stderr, \"usage: %s BEEP...\\n\"\n\t\t\t\t\t\t\"Where BEEP consists of:\\n\"\n\t\t\t\t\t\t\"  -r  REPS  \\033[3mNumber of repetitions.\\033[0m\\n\"\n\t\t\t\t\t\t\"  -f  FREQ  \\033[3mFrequency in Hz. 440 is A4. Supports fractional values.\\033[0m\\n\"\n\t\t\t\t\t\t\"  -l  TIME  \\033[3mDuration in milliseconds.\\033[0m\\n\"\n\t\t\t\t\t\t\"  -d  TIME  \\033[3mDelay between repetitions in milliseconds.\\033[0m\\n\"\n\t\t\t\t\t\t\"  -D  TIME  \\033[3mDelay between, and after, repetitions.\\033[0m\\n\"\n\t\t\t\t\t\t\"  -n        \\033[3mStart a new beep.\\033[0m\\n\"\n\t\t\t\t\t\t\"\\n\"\n\t\t\t\t\t\t\"The default values are:\\n\"\n\t\t\t\t\t\t\"   -r 1 -l %d -f %.2f -d %d\\n\"\n\t\t\t\t\t\t\"\\n\"\n\t\t\t\t\t\t\"A length of -1 will start a sustained beep without blocking.\\n\"\n\t\t\t\t\t\t\"A length of 0 will stop a currently playing sustained beep.\\n\",\n\t\t\t\t\t\targv[0], DEFAULT_LEN, DEFAULT_FREQ, DEFAULT_DELAY);\n\t\t\t\treturn 1;\n\t\t\tcase 'r':\n\t\t\t\trepetitions = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tlength = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tfrequency = atof(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tdelay = atoi(optarg);\n\t\t\t\tbeep_after = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'D':\n\t\t\t\tdelay = atoi(optarg);\n\t\t\t\tbeep_after = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tbeep();\n\t\t\t\trepetitions = 1;\n\t\t\t\tfrequency = DEFAULT_FREQ;\n\t\t\t\tlength = DEFAULT_LEN;\n\t\t\t\tdelay = DEFAULT_DELAY;\n\t\t\t\tbeep_after = 0;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tbeep();\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/bim.c",
    "content": "/* Bim - A Text Editor\n *\n * Copyright (C) 2012-2026 K. Lange\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted, provided that the above\n * copyright notice and this permission notice appear in all copies.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n */\n#include \"bim.h\"\n\n#define BIM_VERSION   \"3.2.0\" TAG\n#define BIM_COPYRIGHT \"Copyright 2012-2026 K. Lange <\\033[3mklange@toaruos.org\\033[23m>\"\n\n#include <kuroko/kuroko.h>\n#include <kuroko/vm.h>\n#include <kuroko/debug.h>\n#include <kuroko/util.h>\n#include <kuroko/scanner.h>\n\nglobal_config_t global_config = {\n\t/* State */\n\t.term_width = 0,\n\t.term_height = 0,\n\t.bottom_size = 2,\n\t.yanks = NULL,\n\t.yank_count = 0,\n\t.yank_is_full_lines = 0,\n\t.tty_in = STDIN_FILENO,\n\t.bimrc_path = \"~/.bim3rc\",\n\t.syntax_fallback = NULL, /* Syntax to fall back to if no other match applies */\n\t.search = NULL,\n\t.overlay_mode = OVERLAY_MODE_NONE,\n\t.command_buffer = NULL,\n\t.command_offset = 0,\n\t.command_col_no = 0,\n\t.history_point = -1,\n\t.search_point = -1,\n\t/* Bitset starts here */\n\t.highlight_on_open = 1,\n\t.initial_file_is_read_only = 0,\n\t.go_to_line = 1,\n\t.break_from_selection = 1,\n\t/* Terminal capabilities */\n\t.can_scroll = 1,\n\t.can_hideshow = 1,\n\t.can_altscreen = 1,\n\t.can_mouse = 1,\n\t.can_unicode = 1,\n\t.can_bright = 1,\n\t.can_title = 1,\n\t.can_bce = 1,\n\t.can_24bit = 1, /* can use 24-bit color */\n\t.can_256color = 1, /* can use 265 colors */\n\t.can_italic = 1, /* can use italics (without inverting) */\n\t.can_insert = 0, /* ^[[L */\n\t.can_bracketedpaste = 0, /* puts escapes before and after pasted stuff */\n\t.can_sgrmouse = 0, /* Whether SGR mouse mode is availabe (large coordinates) */\n\t/* Configuration options */\n\t.history_enabled = 1,\n\t.highlight_parens = 1, /* highlight parens/braces when cursor moves */\n\t.smart_case = 1, /* smart case-sensitivity while searching */\n\t.highlight_current_line = 1,\n\t.shift_scrolling = 1, /* shift rather than moving cursor*/\n\t.check_git = 0,\n\t.color_gutter = 1, /* shows modified lines */\n\t.relative_lines = 0,\n\t.numbers = 1,\n\t.horizontal_shift_scrolling = 0, /* Whether to shift the whole screen when scrolling horizontally */\n\t.hide_statusbar = 0,\n\t.tabs_visible = 1,\n\t.autohide_tabs = 0,\n\t.smart_complete = 0,\n\t.has_terminal = 0,\n\t.search_wraps = 1,\n\t.had_error = 0,\n\t.use_biminfo = 1,\n\t/* Integer config values */\n\t.cursor_padding = 4,\n\t.split_percent = 50,\n\t.scroll_amount = 5,\n\t.tab_offset = 0,\n\t.background_task = NULL,\n\t.tail_task = NULL,\n\t.paren_pairs = NULL,\n};\n\nstruct key_name_map KeyNames[] = {\n\t{KEY_TIMEOUT, \"[timeout]\"},\n\t{KEY_BACKSPACE, \"<backspace>\"},\n\t{KEY_ENTER, \"<enter>\"},\n\t{KEY_ESCAPE, \"<escape>\"},\n\t{KEY_TAB, \"<tab>\"},\n\t{' ', \"<space>\"},\n\t/* These are mostly here for markdown output. */\n\t{'`', \"<backtick>\"},\n\t{'|', \"<pipe>\"},\n\t{KEY_DELETE, \"<del>\"},\n\t{KEY_MOUSE, \"<mouse>\"},\n\t{KEY_MOUSE_SGR, \"<mouse-sgr>\"},\n\t{KEY_F1, \"<f1>\"},{KEY_F2, \"<f2>\"},{KEY_F3, \"<f3>\"},{KEY_F4, \"<f4>\"},\n\t{KEY_F5, \"<f5>\"},{KEY_F6, \"<f6>\"},{KEY_F7, \"<f7>\"},{KEY_F8, \"<f8>\"},\n\t{KEY_F9, \"<f9>\"},{KEY_F10, \"<f10>\"},{KEY_F11, \"<f11>\"},{KEY_F12, \"<f12>\"},\n\t{KEY_HOME,\"<home>\"},{KEY_END,\"<end>\"},{KEY_PAGE_UP,\"<page-up>\"},{KEY_PAGE_DOWN,\"<page-down>\"},\n\t{KEY_UP, \"<up>\"},{KEY_DOWN, \"<down>\"},{KEY_RIGHT, \"<right>\"},{KEY_LEFT, \"<left>\"},\n\t{KEY_SHIFT_UP, \"<shift-up>\"},{KEY_SHIFT_DOWN, \"<shift-down>\"},{KEY_SHIFT_RIGHT, \"<shift-right>\"},{KEY_SHIFT_LEFT, \"<shift-left>\"},\n\t{KEY_CTRL_UP, \"<ctrl-up>\"},{KEY_CTRL_DOWN, \"<ctrl-down>\"},{KEY_CTRL_RIGHT, \"<ctrl-right>\"},{KEY_CTRL_LEFT, \"<ctrl-left>\"},\n\t{KEY_ALT_UP, \"<alt-up>\"},{KEY_ALT_DOWN, \"<alt-down>\"},{KEY_ALT_RIGHT, \"<alt-right>\"},{KEY_ALT_LEFT, \"<alt-left>\"},\n\t{KEY_ALT_SHIFT_UP, \"<alt-shift-up>\"},{KEY_ALT_SHIFT_DOWN, \"<alt-shift-down>\"},{KEY_ALT_SHIFT_RIGHT, \"<alt-shift-right>\"},{KEY_ALT_SHIFT_LEFT, \"<alt-shift-left>\"},\n\t{KEY_SHIFT_TAB,\"<shift-tab>\"},\n\t{KEY_PASTE_BEGIN,\"<paste-begin>\"},{KEY_PASTE_END,\"<paste-end>\"},\n};\n\nchar * name_from_key(enum Key keycode) {\n\tfor (unsigned int i = 0;  i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) {\n\t\tif (KeyNames[i].keycode == keycode) return KeyNames[i].name;\n\t}\n\tstatic char keyNameTmp[8] = {0};\n\tif (keycode <= KEY_CTRL_UNDERSCORE) {\n\t\tkeyNameTmp[0] = '^';\n\t\tkeyNameTmp[1] = '@' + keycode;\n\t\tkeyNameTmp[2] = 0;\n\t\treturn keyNameTmp;\n\t}\n\tto_eight(keycode, keyNameTmp);\n\treturn keyNameTmp;\n}\n\n#define S(c) (krk_copyString(c,sizeof(c)-1))\n\n#define CURRENT_NAME self\nstatic KrkClass * syntaxStateClass = NULL;\n\nstruct SyntaxState {\n\tKrkInstance inst;\n\tstruct syntax_state state;\n};\n\nstatic void schedule_complete_recalc(void);\n\n/**\n * Theming data\n *\n * This default set is pretty simple \"default foreground on default background\"\n * except for search and selections which are black-on-white specifically.\n *\n * The theme colors get set by separate configurable theme scripts.\n */\nconst char * COLOR_FG        = \"@9\";\nconst char * COLOR_BG        = \"@9\";\nconst char * COLOR_ALT_FG    = \"@9\";\nconst char * COLOR_ALT_BG    = \"@9\";\nconst char * COLOR_NUMBER_FG = \"@9\";\nconst char * COLOR_NUMBER_BG = \"@9\";\nconst char * COLOR_STATUS_FG = \"@9\";\nconst char * COLOR_STATUS_BG = \"@9\";\nconst char * COLOR_STATUS_ALT= \"@9\";\nconst char * COLOR_TABBAR_BG = \"@9\";\nconst char * COLOR_TAB_BG    = \"@9\";\nconst char * COLOR_ERROR_FG  = \"@9\";\nconst char * COLOR_ERROR_BG  = \"@9\";\nconst char * COLOR_SEARCH_FG = \"@0\";\nconst char * COLOR_SEARCH_BG = \"@17\";\nconst char * COLOR_KEYWORD   = \"@9\";\nconst char * COLOR_STRING    = \"@9\";\nconst char * COLOR_COMMENT   = \"@9\";\nconst char * COLOR_TYPE      = \"@9\";\nconst char * COLOR_PRAGMA    = \"@9\";\nconst char * COLOR_NUMERAL   = \"@9\";\nconst char * COLOR_SELECTFG  = \"@0\";\nconst char * COLOR_SELECTBG  = \"@17\";\nconst char * COLOR_RED       = \"@1\";\nconst char * COLOR_GREEN     = \"@2\";\nconst char * COLOR_BOLD      = \"@9\";\nconst char * COLOR_LINK      = \"@9\";\nconst char * COLOR_ESCAPE    = \"@9\";\nconst char * current_theme = \"none\";\n\nstruct ColorName color_names[] = {\n\t{\"text-fg\", &COLOR_FG},\n\t{\"text-bg\", &COLOR_BG},\n\t{\"alternate-fg\", &COLOR_ALT_FG},\n\t{\"alternate-bg\", &COLOR_ALT_BG},\n\t{\"number-fg\", &COLOR_NUMBER_FG},\n\t{\"number-bg\", &COLOR_NUMBER_BG},\n\t{\"status-fg\", &COLOR_STATUS_FG},\n\t{\"status-bg\", &COLOR_STATUS_BG},\n\t{\"status-alt\", &COLOR_STATUS_ALT},\n\t{\"tabbar-bg\", &COLOR_TABBAR_BG},\n\t{\"tab-bg\", &COLOR_TAB_BG},\n\t{\"error-fg\", &COLOR_ERROR_FG},\n\t{\"error-bg\", &COLOR_ERROR_BG},\n\t{\"search-fg\", &COLOR_SEARCH_FG},\n\t{\"search-bg\", &COLOR_SEARCH_BG},\n\t{\"keyword\", &COLOR_KEYWORD},\n\t{\"string\", &COLOR_STRING},\n\t{\"comment\", &COLOR_COMMENT},\n\t{\"type\", &COLOR_TYPE},\n\t{\"pragma\", &COLOR_PRAGMA},\n\t{\"numeral\", &COLOR_NUMERAL},\n\t{\"select-fg\", &COLOR_SELECTFG},\n\t{\"select-bg\", &COLOR_SELECTBG},\n\t{\"red\", &COLOR_RED},\n\t{\"green\", &COLOR_GREEN},\n\t{\"bold\", &COLOR_BOLD},\n\t{\"link\", &COLOR_LINK},\n\t{\"escape\", &COLOR_ESCAPE},\n\t{NULL,NULL},\n};\n\n\n#define FLEXIBLE_ARRAY(name, add_name, type, zero) \\\n\tint flex_ ## name ## _count = 0; \\\n\tint flex_ ## name ## _space = 0; \\\n\ttype * name = NULL; \\\n\tvoid add_name (type input) { \\\n\t\tif (flex_ ## name ## _space == 0) { \\\n\t\t\tflex_ ## name ## _space = 4; \\\n\t\t\tname = calloc(sizeof(type), flex_ ## name ## _space); \\\n\t\t} else if (flex_ ## name ## _count + 1 == flex_ ## name ## _space) { \\\n\t\t\tflex_ ## name ## _space *= 2; \\\n\t\t\tname = realloc(name, sizeof(type) * flex_ ## name ## _space); \\\n\t\t\tfor (int i = flex_ ## name ## _count; i < flex_ ## name ## _space; ++i) name[i] = zero; \\\n\t\t} \\\n\t\tname[flex_ ## name ## _count] = input; \\\n\t\tflex_ ## name ## _count ++; \\\n\t}\n\nFLEXIBLE_ARRAY(mappable_actions, add_action, struct action_def, ((struct action_def){NULL,0,0,NULL}))\nFLEXIBLE_ARRAY(regular_commands, add_command, struct command_def, ((struct command_def){NULL,NULL,NULL}))\nFLEXIBLE_ARRAY(prefix_commands, add_prefix_command, struct command_def, ((struct command_def){NULL,NULL,NULL}))\nFLEXIBLE_ARRAY(themes, add_colorscheme, struct theme_def, ((struct theme_def){NULL,NULL}))\n\n/**\n * Special implementation of getch with a timeout\n */\nint _bim_unget = -1;\n\nvoid bim_unget(int c) {\n\t_bim_unget = c;\n}\n\nvoid redraw_statusbar(void);\nint bim_getch_timeout(int timeout) {\n\tfflush(stdout);\n\tif (_bim_unget != -1) {\n\t\tint out = _bim_unget;\n\t\t_bim_unget = -1;\n\t\treturn out;\n\t}\n\tstruct pollfd fds[1];\n\tfds[0].fd = global_config.tty_in;\n\tfds[0].events = POLLIN;\n\tint ret = poll(fds,1,timeout);\n\tif (ret > 0 && fds[0].revents & POLLIN) {\n\t\tunsigned char buf[1];\n\t\tread(global_config.tty_in, buf, 1);\n\t\treturn buf[0];\n\t} else {\n\t\tbackground_task_t * task = global_config.background_task;\n\t\tif (task) {\n\t\t\tglobal_config.background_task = task->next;\n\t\t\ttask->func(task);\n\t\t\tfree(task);\n\t\t\tif (!global_config.background_task) {\n\t\t\t\tglobal_config.tail_task = NULL;\n\t\t\t\tredraw_statusbar();\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n}\n\n/**\n * UTF-8 parser state\n *\n * TODO Why is this state global? Should be per-buffer or otherwise managed by add_buffer, and probably\n *      reset a lot more often than we are currently resetting it.\n */\nstatic uint32_t codepoint_r;\nstatic uint32_t state = 0;\n\n#define UTF8_ACCEPT 0\n#define UTF8_REJECT 1\n\nstatic inline uint32_t decode(uint32_t* state, uint32_t* codep, unsigned char byte) {\n\tstatic int state_table[32] = {\n\t\t0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */\n\t\t1,1,1,1,1,1,1,1,                 /* 10xxxxxx */\n\t\t2,2,2,2,                         /* 110xxxxx */\n\t\t3,3,                             /* 1110xxxx */\n\t\t4,                               /* 11110xxx */\n\t\t1                                /* 11111xxx */\n\t};\n\n\tstatic int mask_bytes[32] = {\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n\t\t0x1F,0x1F,0x1F,0x1F,\n\t\t0x0F,0x0F,\n\t\t0x07,\n\t\t0x00\n\t};\n\n\tstatic int next[5] = {\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t2,\n\t\t3\n\t};\n\n\tif (*state == UTF8_ACCEPT) {\n\t\t*codep = byte & mask_bytes[byte >> 3];\n\t\t*state = state_table[byte >> 3];\n\t} else if (*state > 0) {\n\t\t*codep = (byte & 0x3F) | (*codep << 6);\n\t\t*state = next[*state];\n\t}\n\treturn *state;\n}\n\n#define shift_key(i) _shift_key((i), this_buf, &timeout);\nint _shift_key(int i, int this_buf[20], int *timeout) {\n\tint thing = this_buf[*timeout-1];\n\t(*timeout) = 0;\n\tswitch (thing) {\n\t\t/* There are other combinations we can handle... */\n\t\tcase '2': return i + 4;\n\t\tcase '5': return i + 8;\n\t\tcase '3': return i + 12;\n\t\tcase '4': return i + 16;\n\t\tdefault: return i;\n\t}\n}\n\nint bim_getkey(int read_timeout) {\n\n\tint timeout = 0;\n\tint this_buf[20];\n\n\tint cin;\n\tuint32_t c;\n\tuint32_t istate = 0;\n\n\twhile ((cin = bim_getch_timeout((timeout == 1) ? 50 : read_timeout))) {\n\t\tif (cin == -1) {\n\t\t\tif (timeout && this_buf[timeout-1] == '\\033')\n\t\t\t\treturn KEY_ESCAPE;\n\t\t\treturn KEY_TIMEOUT;\n\t\t}\n\n\t\tif (!decode(&istate, &c, cin)) {\n\t\t\tif (timeout == 0) {\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase '\\033':\n\t\t\t\t\t\tif (timeout == 0) {\n\t\t\t\t\t\t\tthis_buf[timeout] = c;\n\t\t\t\t\t\t\ttimeout++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\tcase KEY_LINEFEED: return KEY_ENTER;\n\t\t\t\t\tcase KEY_DELETE: return KEY_BACKSPACE;\n\t\t\t\t}\n\t\t\t\treturn c;\n\t\t\t} else {\n\t\t\t\tif (timeout >= 1 && this_buf[timeout-1] == '\\033' && c == '\\033') {\n\t\t\t\t\tbim_unget(c);\n\t\t\t\t\ttimeout = 0;\n\t\t\t\t\treturn KEY_ESCAPE;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 1 && this_buf[0] == '\\033' && c == 'O') {\n\t\t\t\t\tthis_buf[timeout] = c;\n\t\t\t\t\ttimeout++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 2 && this_buf[0] == '\\033' && this_buf[1] == 'O') {\n\t\t\t\t\tswitch (c) {\n\t\t\t\t\t\tcase 'P': return KEY_F1;\n\t\t\t\t\t\tcase 'Q': return KEY_F2;\n\t\t\t\t\t\tcase 'R': return KEY_F3;\n\t\t\t\t\t\tcase 'S': return KEY_F4;\n\t\t\t\t\t}\n\t\t\t\t\ttimeout = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 1 && this_buf[timeout-1] == '\\033' && c != '[') {\n\t\t\t\t\ttimeout = 0;\n\t\t\t\t\tbim_unget(c);\n\t\t\t\t\treturn KEY_ESCAPE;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 1 && this_buf[timeout-1] == '\\033' && c == '[') {\n\t\t\t\t\ttimeout = 1;\n\t\t\t\t\tthis_buf[timeout] = c;\n\t\t\t\t\ttimeout++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 2 && this_buf[0] == '\\033' && this_buf[1] == '[' &&\n\t\t\t\t    (isdigit(c) || (c == ';'))) {\n\t\t\t\t\tthis_buf[timeout] = c;\n\t\t\t\t\ttimeout++;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (timeout >= 2 && this_buf[0] == '\\033' && this_buf[1] == '[') {\n\t\t\t\t\tswitch (c) {\n\t\t\t\t\t\tcase 'M': return KEY_MOUSE;\n\t\t\t\t\t\tcase '<': return KEY_MOUSE_SGR;\n\t\t\t\t\t\tcase 'A': return shift_key(KEY_UP);\n\t\t\t\t\t\tcase 'B': return shift_key(KEY_DOWN);\n\t\t\t\t\t\tcase 'C': return shift_key(KEY_RIGHT);\n\t\t\t\t\t\tcase 'D': return shift_key(KEY_LEFT);\n\t\t\t\t\t\tcase 'H': return KEY_HOME;\n\t\t\t\t\t\tcase 'F': return KEY_END;\n\t\t\t\t\t\tcase 'I': return KEY_PAGE_UP;\n\t\t\t\t\t\tcase 'G': return KEY_PAGE_DOWN;\n\t\t\t\t\t\tcase 'Z': return KEY_SHIFT_TAB;\n\t\t\t\t\t\tcase '~':\n\t\t\t\t\t\t\tif (timeout == 3) {\n\t\t\t\t\t\t\t\tswitch (this_buf[2]) {\n\t\t\t\t\t\t\t\t\tcase '1': return KEY_HOME;\n\t\t\t\t\t\t\t\t\tcase '3': return KEY_DELETE;\n\t\t\t\t\t\t\t\t\tcase '4': return KEY_END;\n\t\t\t\t\t\t\t\t\tcase '5': return KEY_PAGE_UP;\n\t\t\t\t\t\t\t\t\tcase '6': return KEY_PAGE_DOWN;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (timeout == 5) {\n\t\t\t\t\t\t\t\tif (this_buf[2] == '2' && this_buf[3] == '0' && this_buf[4] == '0') {\n\t\t\t\t\t\t\t\t\treturn KEY_PASTE_BEGIN;\n\t\t\t\t\t\t\t\t} else if (this_buf[2] == '2' && this_buf[3] == '0' && this_buf[4] == '1') {\n\t\t\t\t\t\t\t\t\treturn KEY_PASTE_END;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (this_buf[2] == '1') {\n\t\t\t\t\t\t\t\tswitch (this_buf[3]) {\n\t\t\t\t\t\t\t\t\tcase '5': return KEY_F5;\n\t\t\t\t\t\t\t\t\tcase '7': return KEY_F6;\n\t\t\t\t\t\t\t\t\tcase '8': return KEY_F7;\n\t\t\t\t\t\t\t\t\tcase '9': return KEY_F8;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (this_buf[2] == '2') {\n\t\t\t\t\t\t\t\tswitch (this_buf[3]) {\n\t\t\t\t\t\t\t\t\tcase '0': return KEY_F9;\n\t\t\t\t\t\t\t\t\tcase '1': return KEY_F10;\n\t\t\t\t\t\t\t\t\tcase '3': return KEY_F11;\n\t\t\t\t\t\t\t\t\tcase '4': return KEY_F12;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttimeout = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (istate == UTF8_REJECT) {\n\t\t\tistate = 0;\n\t\t}\n\t}\n\n\treturn KEY_TIMEOUT;\n}\n\nenum Key key_from_name(const char * name) {\n\tfor (unsigned int i = 0;  i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) {\n\t\tif (!strcmp(KeyNames[i].name, name)) return KeyNames[i].keycode;\n\t}\n\tif (name[0] == '^' && name[1] && !name[2]) {\n\t\treturn name[1] - '@';\n\t}\n\tif (!name[1]) return name[0];\n\t/* Try decoding */\n\tuint32_t c, state = 0;\n\tint candidate = -1;\n\twhile (*name) {\n\t\tif (!decode(&state, &c, (unsigned char)*name)) {\n\t\t\tif (candidate == -1) candidate = c;\n\t\t\telse return -1; /* Reject `name` if it is multiple codepoints */\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\treturn -1;\n\t\t}\n\t}\n\treturn candidate;\n}\n\n\n/**\n * Pointer to current active buffer\n */\nbuffer_t * env = NULL;\n\nbuffer_t * left_buffer = NULL;\nbuffer_t * right_buffer = NULL;\n\n/**\n * A buffer for holding a number (line, repetition count)\n */\n#define NAV_BUFFER_MAX 10\nchar nav_buf[NAV_BUFFER_MAX+1];\nint nav_buffer = 0;\n\n/**\n * Available buffers\n */\nint    buffers_len = 0;\nint    buffers_avail = 0;\nbuffer_t ** buffers = NULL;\n\n/**\n * Create a new buffer\n */\nbuffer_t * buffer_new(void) {\n\tif (buffers_len == buffers_avail) {\n\t\t/* If we are out of buffer space, expand the buffers vector */\n\t\tbuffers_avail *= 2;\n\t\tbuffers = realloc(buffers, sizeof(buffer_t *) * buffers_avail);\n\t}\n\n\t/* TODO: Clean up split support and support multiple splits... */\n\tif (left_buffer) {\n\t\tleft_buffer->left = 0;\n\t\tleft_buffer->width = global_config.term_width;\n\t\tright_buffer->left = 0;\n\t\tright_buffer->width = global_config.term_width;\n\t\tleft_buffer = NULL;\n\t\tright_buffer = NULL;\n\t}\n\n\t/* Allocate a new buffer */\n\tbuffers[buffers_len] = malloc(sizeof(buffer_t));\n\tmemset(buffers[buffers_len], 0x00, sizeof(buffer_t));\n\tbuffers[buffers_len]->left = 0;\n\tbuffers[buffers_len]->width = global_config.term_width;\n\tbuffers[buffers_len]->highlighting_paren = -1;\n\tbuffers[buffers_len]->numbers = global_config.numbers;\n\tbuffers[buffers_len]->gutter = 1;\n\tbuffers_len++;\n\tglobal_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1);\n\n\treturn buffers[buffers_len-1];\n}\n\n/**\n * Open the biminfo file.\n */\nFILE * open_biminfo(void) {\n\tif (!global_config.use_biminfo) return NULL;\n\n\tchar * home = getenv(\"HOME\");\n\tif (!home) {\n\t\t/* ... but since it's not, we need $HOME, so fail if it isn't set. */\n\t\treturn NULL;\n\t}\n\n\t/* biminfo lives at ~/.biminfo */\n\tchar biminfo_path[PATH_MAX+1] = {0};\n\tsprintf(biminfo_path,\"%s/.biminfo\",home);\n\n\t/* Try to open normally first... */\n\tFILE * biminfo = fopen(biminfo_path,\"r+\");\n\tif (!biminfo) {\n\t\t/* Otherwise, try to create it. */\n\t\tbiminfo = fopen(biminfo_path,\"w+\");\n\t}\n\treturn biminfo;\n}\n\n/**\n * Check if a file is open by examining the biminfo file\n */\nint file_is_open(char * file) {\n\t/* Get the absolute path of the file to normalize for lookup */\n\tchar * _file = file;\n\tif (file[0] == '~') {\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (home) {\n\t\t\t_file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */\n\t\t\tsprintf(_file, \"%s%s\", home, file+1);\n\t\t}\n\t}\n\n\tchar tmp_path[PATH_MAX+2];\n\tif (!realpath(_file, tmp_path)) {\n\t\tif (_file != file) free(_file);\n\t\treturn 0; /* Assume not */\n\t}\n\tif (_file != file) free(_file);\n\tstrcat(tmp_path,\" \");\n\n\tFILE * biminfo = open_biminfo();\n\tif (!biminfo) return 0; /* Assume not */\n\n\t/* Scan */\n\tchar line[PATH_MAX+64];\n\n\twhile (!feof(biminfo)) {\n\t\tfpos_t start_of_line;\n\t\tfgetpos(biminfo, &start_of_line);\n\t\tfgets(line, PATH_MAX+63, biminfo);\n\t\tif (line[0] != '%') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strncmp(&line[1],tmp_path, strlen(tmp_path))) {\n\t\t\t/* File is currently open */\n\t\t\tint pid = -1;\n\t\t\tsscanf(line+1+strlen(tmp_path)+1,\"%d\",&pid);\n\t\t\tif (pid != -1 && pid != getpid()) {\n\t\t\t\tif (!kill(pid, 0)) {\n\t\t\t\t\tint key = 0;\n\t\t\t\t\trender_error(\"biminfo indicates another instance may already be editing this file\");\n\t\t\t\t\trender_commandline_message(\"\\n\");\n\t\t\t\t\trender_commandline_message(\"file path = %s\\n\", tmp_path);\n\t\t\t\t\trender_commandline_message(\"pid = %d (still running)\\n\", pid);\n\t\t\t\t\trender_commandline_message(\"Open file anyway? (y/N)\");\n\t\t\t\t\twhile ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\t\t\t\t\tif (key != 'y') {\n\t\t\t\t\t\tfclose(biminfo);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfclose(biminfo);\n\t\t\treturn 0;\n\t\t}\n\t}\n\tfclose(biminfo);\n\treturn 0;\n}\n\n/**\n * Fetch the cursor position from a biminfo file\n */\nint fetch_from_biminfo(buffer_t * buf) {\n\t/* Can't fetch if we don't have a filename */\n\tif (!buf->file_name) return 1;\n\n\tchar * file = buf->file_name;\n\tchar * _file = file;\n\tif (file[0] == '~') {\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (home) {\n\t\t\t_file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */\n\t\t\tsprintf(_file, \"%s%s\", home, file+1);\n\t\t}\n\t}\n\n\t/* Get the absolute path of the file to normalize for lookup */\n\tchar tmp_path[PATH_MAX+2];\n\tif (!realpath(_file, tmp_path)) {\n\t\tif (_file != file) free(_file);\n\t\treturn 1;\n\t}\n\tif (_file != file) free(_file);\n\tstrcat(tmp_path,\" \");\n\n\tFILE * biminfo = open_biminfo();\n\tif (!biminfo) return 1;\n\n\t/* Scan */\n\tchar line[PATH_MAX+64];\n\n\twhile (!feof(biminfo)) {\n\t\tfpos_t start_of_line;\n\t\tfgetpos(biminfo, &start_of_line);\n\t\tfgets(line, PATH_MAX+63, biminfo);\n\t\tif (line[0] != '>') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strncmp(&line[1],tmp_path, strlen(tmp_path))) {\n\t\t\t/* Read */\n\t\t\tsscanf(line+1+strlen(tmp_path)+1,\"%d\",&buf->line_no);\n\t\t\tsscanf(line+1+strlen(tmp_path)+21,\"%d\",&buf->col_no);\n\n\t\t\tif (buf->line_no > buf->line_count) buf->line_no = buf->line_count;\n\t\t\tif (buf->col_no > buf->lines[buf->line_no-1]->actual) buf->col_no = buf->lines[buf->line_no-1]->actual;\n\t\t\ttry_to_center();\n\n\t\t\tfclose(biminfo);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tfclose(biminfo);\n\treturn 0;\n}\n\n/**\n * Write a file containing the last cursor position of a buffer.\n */\nint update_biminfo(buffer_t * buf, int is_open) {\n\tif (!buf->file_name) return 1;\n\n\tchar * file = buf->file_name;\n\tchar * _file = file;\n\tif (file[0] == '~') {\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (home) {\n\t\t\t_file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */\n\t\t\tsprintf(_file, \"%s%s\", home, file+1);\n\t\t}\n\t}\n\n\t/* Get the absolute path of the file to normalize for lookup */\n\tchar tmp_path[PATH_MAX+1];\n\tif (!realpath(_file, tmp_path)) {\n\t\tif (_file != file) free(_file);\n\t\treturn 1;\n\t}\n\tif (_file != file) free(_file);\n\tstrcat(tmp_path,\" \");\n\n\tFILE * biminfo = open_biminfo();\n\tif (!biminfo) return 1;\n\n\t/* Scan */\n\tchar line[PATH_MAX+64];\n\n\twhile (!feof(biminfo)) {\n\t\tfpos_t start_of_line;\n\t\tfgetpos(biminfo, &start_of_line);\n\t\tfgets(line, PATH_MAX+63, biminfo);\n\t\tif (line[0] != '>' && line[0] != '%') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strncmp(&line[1],tmp_path, strlen(tmp_path))) {\n\t\t\t/* Update */\n\t\t\tfsetpos(biminfo, &start_of_line);\n\t\t\tfprintf(biminfo,\"%c%s %20d %20d\\n\", is_open ? '%' : '>', tmp_path,\n\t\t\t\tis_open ? getpid() : buf->line_no, buf->col_no);\n\t\t\tgoto _done;\n\t\t}\n\t}\n\n\tif (ftell(biminfo) == 0) {\n\t\t/* New biminfo */\n\t\tfprintf(biminfo, \"# This is a biminfo file.\\n\");\n\t\tfprintf(biminfo, \"# It was generated by bim. Do not edit it by hand!\\n\");\n\t\tfprintf(biminfo, \"# Cursor positions and other state are stored here.\\n\");\n\t}\n\n\t/* If we reach this point, we didn't find a record for this file\n\t * and the write cursor should be at the end, so just add a new line */\n\tfprintf(biminfo,\"%c%s %20d %20d\\n\", is_open ? '%' : '>', tmp_path,\n\t\tis_open ? getpid() : buf->line_no, buf->col_no);\n\n_done:\n\tfclose(biminfo);\n\treturn 0;\n}\n\nvoid cancel_background_tasks(buffer_t * buf) {\n\tbackground_task_t * t = global_config.background_task;\n\tbackground_task_t * last = NULL;\n\twhile (t) {\n\t\tif (t->env == buf) {\n\t\t\tif (last) {\n\t\t\t\tlast->next = t->next;\n\t\t\t} else {\n\t\t\t\tglobal_config.background_task = t->next;\n\t\t\t}\n\t\t\tif (!t->next) {\n\t\t\t\tglobal_config.tail_task = last;\n\t\t\t}\n\t\t\tbackground_task_t * tmp = t->next;\n\t\t\tfree(t);\n\t\t\tt = tmp;\n\t\t} else {\n\t\t\tlast = t;\n\t\t\tt = t->next;\n\t\t}\n\t}\n}\n\n/**\n * Close a buffer\n */\nbuffer_t * buffer_close(buffer_t * buf) {\n\tint i;\n\n\t/* Locate the buffer in the buffer list */\n\tfor (i = 0; i < buffers_len; i++) {\n\t\tif (buf == buffers[i])\n\t\t\tbreak;\n\t}\n\n\t/* This buffer doesn't exist? */\n\tif (i == buffers_len) {\n\t\treturn NULL;\n\t}\n\n\t/* Cancel any background tasks for this env */\n\tcancel_background_tasks(buf);\n\n\tupdate_biminfo(buf, 0);\n\n\t/* Clean up lines used by old buffer */\n\tfor (int i = 0; i < buf->line_count; ++i) {\n\t\tfree(buf->lines[i]);\n\t}\n\n\tfree(buf->lines);\n\n\tif (buf->file_name) {\n\t\tfree(buf->file_name);\n\t}\n\n\thistory_t * h = buf->history;\n\twhile (h->next) {\n\t\th = h->next;\n\t}\n\twhile (h) {\n\t\thistory_t * x = h->previous;\n\t\tfree(h);\n\t\th = x;\n\t}\n\n\t/* Clean up the old buffer */\n\tfree(buf);\n\n\t/* Remove the buffer from the vector, moving others up */\n\tif (i != buffers_len - 1) {\n\t\tmemmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i - 1));\n\t}\n\n\t/* There is one less buffer */\n\tbuffers_len--;\n\tif (buffers_len && global_config.tab_offset >= buffers_len) global_config.tab_offset--;\n\tglobal_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1);\n\tif (!buffers_len) { \n\t\t/* There are no more buffers. */\n\t\treturn NULL;\n\t}\n\n\t/* If this was the last buffer in the list, return the previous last buffer */\n\tif (i == buffers_len) {\n\t\treturn buffers[buffers_len-1];\n\t}\n\n\t/* Otherwise return the buffer in the same location */\n\treturn buffers[i];\n}\n\n/**\n * Convert syntax highlighting flag to color code\n */\nconst char * flag_to_color(int _flag) {\n\tint flag = _flag & FLAG_MASK_COLORS;\n\tswitch (flag) {\n\t\tcase FLAG_KEYWORD:\n\t\t\treturn COLOR_KEYWORD;\n\t\tcase FLAG_STRING:\n\t\t\treturn COLOR_STRING;\n\t\tcase FLAG_COMMENT:\n\t\t\treturn COLOR_COMMENT;\n\t\tcase FLAG_TYPE:\n\t\t\treturn COLOR_TYPE;\n\t\tcase FLAG_NUMERAL:\n\t\t\treturn COLOR_NUMERAL;\n\t\tcase FLAG_PRAGMA:\n\t\t\treturn COLOR_PRAGMA;\n\t\tcase FLAG_DIFFPLUS:\n\t\t\treturn COLOR_GREEN;\n\t\tcase FLAG_DIFFMINUS:\n\t\t\treturn COLOR_RED;\n\t\tcase FLAG_SELECT:\n\t\t\treturn COLOR_FG;\n\t\tcase FLAG_BOLD:\n\t\t\treturn COLOR_BOLD;\n\t\tcase FLAG_LINK_COLOR:\n\t\t\treturn COLOR_LINK;\n\t\tcase FLAG_ESCAPE:\n\t\t\treturn COLOR_ESCAPE;\n\t\tdefault:\n\t\t\treturn COLOR_FG;\n\t}\n}\n\n/**\n * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise,\n * so it can be used for prefix checking for things that need further special handling.\n */\nstatic int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) {\n\tif (keyword_qualifier(lastchar())) return 0;\n\tif (!keyword_qualifier(charat())) return 0;\n\tint i = state->i;\n\tint slen = 0;\n\twhile (i < state->line->actual || *keyword == '\\0') {\n\t\tif (*keyword == '\\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) {\n\t\t\tfor (int j = 0; j < slen; ++j) {\n\t\t\t\tpaint(1, flag);\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\t\tif (*keyword != state->line->text[i].codepoint) return 0;\n\n\t\ti++;\n\t\tkeyword++;\n\t\tslen++;\n\t}\n\treturn 0;\n}\n\n/**\n * This is a basic character matcher for \"keyword\" characters.\n */\nstatic int simple_keyword_qualifier(int c) {\n\treturn isalnum(c) || (c == '_');\n}\n\n/**\n * These words can appear in comments and should be highlighted.\n * Since there are a lot of comment highlighters, this is provided\n * as a common function that can be used by multiple highlighters.\n */\nstatic int common_comment_buzzwords(struct syntax_state * state) {\n\tif (match_and_paint(state, \"TODO\", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; }\n\telse if (match_and_paint(state, \"XXX\", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; }\n\telse if (match_and_paint(state, \"FIXME\", FLAG_ERROR, simple_keyword_qualifier)) { return 1; }\n\treturn 0;\n}\n\n/**\n * Paint a comment until end of line; assumes this comment can not continue.\n * (Some languages have comments that can continue with a \\ - don't use this!)\n * Assumes you've already painted your comment start characters.\n */\nstatic int paint_comment(struct syntax_state * state) {\n\twhile (charat() != -1) {\n\t\tif (common_comment_buzzwords(state)) continue;\n\t\telse { paint(1, FLAG_COMMENT); }\n\t}\n\treturn -1;\n}\n\n/**\n * Find and return a highlighter by name, or return NULL if none was found.\n */\nstatic struct syntax_definition * find_syntax_calculator(const char * name) {\n\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\tif (!strcmp(s->name, name)) {\n\t\t\treturn s;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nint syntax_count = 0;\nint syntax_space = 0;\nstruct syntax_definition * syntaxes = NULL;\n\nvoid add_syntax(struct syntax_definition def) {\n\t/* See if a name match already exists for this def. */\n\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\tif (!strcmp(def.name,s->name)) {\n\t\t\t*s = def;\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (syntax_space == 0) {\n\t\tsyntax_space = 4;\n\t\tsyntaxes = calloc(sizeof(struct syntax_definition), syntax_space);\n\t} else if (syntax_count +1 == syntax_space) {\n\t\tsyntax_space *= 2;\n\t\tsyntaxes = realloc(syntaxes, sizeof(struct syntax_definition) * syntax_space);\n\t\tfor (int i = syntax_count; i < syntax_space; ++i) syntaxes[i].name = NULL;\n\t}\n\tsyntaxes[syntax_count] = def;\n\tsyntax_count++;\n}\n\nvoid redraw_all(void);\n\n/**\n * Calculate syntax highlighting for the given line, and lines after\n * if their initial syntax state has changed by this recalculation.\n *\n * If `line_no` is -1, this line is taken to be a special line and not\n * part of a buffer; search highlighting will not be processed and syntax\n * highlighting will halt after the line is finished.\n *\n * If `env->slowop` is currently enabled, recalculation is skipped.\n */\nvoid recalculate_syntax(line_t * line, int line_no) {\n\tif (env->slowop) return;\n\t/* Clear syntax for this line first */\n\tint is_original = 1;\n\twhile (1) {\n\t\tfor (int i = 0; i < line->actual; ++i) {\n\t\t\tline->text[i].flags = line->text[i].flags & (3 << 5);\n\t\t}\n\n\t\tif (!env->syntax) {\n\t\t\tif (line_no != -1) rehighlight_search(line);\n\t\t\treturn;\n\t\t}\n\n\t\t/* Start from the line's stored in initial state */\n\t\tstruct SyntaxState * s = (void*)krk_newInstance(env->syntax->krkClass);\n\t\ts->state.env = env;\n\t\ts->state.line = line;\n\t\ts->state.line_no = line_no;\n\t\ts->state.state = line->istate;\n\t\ts->state.i = 0;\n\n\t\twhile (1) {\n\t\t\tstruct termios old, new;\n\t\t\ttcgetattr(global_config.tty_in, &old);\n\t\t\tnew = old; new.c_lflag |= ISIG;\n\t\t\ttcsetattr(global_config.tty_in, TCSANOW, &new);\n\t\t\tptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack;\n\t\t\tkrk_push(OBJECT_VAL(env->syntax->krkFunc));\n\t\t\tkrk_push(OBJECT_VAL(s));\n\t\t\tKrkValue result = krk_callStack(1);\n\t\t\ttcsetattr(global_config.tty_in, TCSANOW, &old);\n\t\t\tkrk_currentThread.stackTop = krk_currentThread.stack + before;\n\t\t\tif (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) {\n\t\t\t\trender_error(\"Exception occurred in plugin: %s\", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars);\n\t\t\t\trender_commandline_message(\"\\n\");\n\t\t\t\tkrk_dumpTraceback();\n\t\t\t\tgoto _syntaxError;\n\t\t\t} else if (!IS_NONE(result) && !IS_INTEGER(result)) {\n\t\t\t\trender_error(\"Instead of an integer, got %s\", krk_typeName(result));\n\t\t\t\trender_commandline_message(\"\\n\");\n\t\t\t\tgoto _syntaxError;\n\t\t\t}\n\t\t\ts->state.state = IS_NONE(result) ? -1 : AS_INTEGER(result);\n\n\t\t\tif (s->state.state != 0) {\n\t\t\t\tif (line_no == -1) return;\n\t\t\t\trehighlight_search(line);\n\t\t\t\tif (!is_original) {\n\t\t\t\t\tredraw_line(line_no);\n\t\t\t\t}\n\t\t\t\tif (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != s->state.state) {\n\t\t\t\t\tline_no++;\n\t\t\t\t\tline = env->lines[line_no];\n\t\t\t\t\tline->istate = s->state.state;\n\t\t\t\t\tif (env->loading) return;\n\t\t\t\t\tis_original = 0;\n\t\t\t\t\tgoto _next;\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n_next:\n\t\t(void)0;\n\t}\n\n_syntaxError:\n\tkrk_resetStack();\n\tfprintf(stderr,\"This syntax highlighter will be disabled in this environment.\");\n\tenv->syntax = NULL;\n\tcancel_background_tasks(env);\n\tpause_for_key();\n\tredraw_all();\n}\n\n/**\n * Recalculate tab widths.\n */\nvoid recalculate_tabs(line_t * line) {\n\tif (env->loading) return;\n\n\tint j = 0;\n\tfor (int i = 0; i < line->actual; ++i) {\n\t\tif (line->text[i].codepoint == '\\t') {\n\t\t\tline->text[i].display_width = env->tabstop - (j % env->tabstop);\n\t\t}\n\t\tj += line->text[i].display_width;\n\t}\n}\n\n/**\n * The next section contains the basic editing primitives. All other\n * actions are built out of these primitives, and they are the basic\n * instructions that get stored in history to be undone (or redone).\n *\n * Primitives may recalculate syntax or redraw lines, if needed, but only\n * when conditions for redrawing are met (such as not being in `slowop`,\n * or loading the file; also replaying history, or when loading files).\n *\n * At the moment, primitives and most other functions do not take the current\n * buffer (environment) as an argument and instead rely on a global variable;\n * this should definitely be fixed at some point...\n */\n\n/**\n * When a new action that produces history happens and there is forward\n * history that can be redone, we need to erase it as our tree has branched.\n * If we wanted, we could actually story things in a tree structure so that\n * the new branch and the old branch can both stick around, and that should\n * probably be explored in the future...\n */\nvoid history_free(history_t * root) {\n\tif (!root->next) return;\n\n\t/* Find the last entry so we can free backwards */\n\thistory_t * n = root->next;\n\twhile (n->next) {\n\t\tn = n->next;\n\t}\n\n\t/* Free everything after the root, including stored content. */\n\twhile (n != root) {\n\t\thistory_t * p = n->previous;\n\t\tswitch (n->type) {\n\t\t\tcase HISTORY_REPLACE_LINE:\n\t\t\t\tfree(n->contents.remove_replace_line.contents);\n\t\t\t\t/* fall-through */\n\t\t\tcase HISTORY_REMOVE_LINE:\n\t\t\t\tfree(n->contents.remove_replace_line.old_contents);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* Nothing extra to free */\n\t\t\t\tbreak;\n\t\t}\n\t\tfree(n);\n\t\tn = p;\n\t}\n\n\troot->next = NULL;\n}\n\n/**\n * This macro is called by primitives to insert history elements for each\n * primitive action when performing in edit modes.\n */\n#define HIST_APPEND(e) do { \\\n\t\te->col = env->col_no; \\\n\t\te->line = env->line_no; \\\n\t\tif (env->history) { \\\n\t\t\te->previous = env->history; \\\n\t\t\thistory_free(env->history); \\\n\t\t\tenv->history->next = e; \\\n\t\t\te->next = NULL; \\\n\t\t} \\\n\t\tenv->history = e; \\\n\t} while (0)\n\n/**\n * Mark a point where a complete set of actions has ended.\n * Individual history entries include things like \"insert one character\"\n * but a user action that should be undone is \"insert several characters\";\n * breaks should be inserted after a series of primitives when there is\n * a clear \"end\", such as when switching out of insert mode after typing.\n */\nvoid set_history_break(void) {\n\tif (!global_config.history_enabled) return;\n\n\t/* Do not produce duplicate breaks, or add breaks if we are at a sentinel. */\n\tif (env->history->type != HISTORY_BREAK && env->history->type != HISTORY_SENTINEL) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_BREAK;\n\t\tHIST_APPEND(e);\n\t}\n}\n\n/**\n * (Primitive) Insert a character into an existing line.\n *\n * This is the most basic primitive action: Take a line, and insert a codepoint\n * into it at a given offset. If `lineno` is not -1, the line is assumed to\n * be part of the active buffer. If inserting a character means the line needs\n * to grow, then it will be reallocated, so the return value of the new line\n * must ALWAYS be used. This primitive will NOT automatically update the\n * buffer with the new pointer, so if you are calling insert on a buffer you\n * MUST update env->lines[lineno-1] yourself.\n */\n__attribute__((warn_unused_result)) line_t * line_insert(line_t * line, char_t c, int offset, int lineno) {\n\n\tif (!env->loading && global_config.history_enabled && lineno != -1) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_INSERT;\n\t\te->contents.insert_delete_replace.lineno = lineno;\n\t\te->contents.insert_delete_replace.offset = offset;\n\t\te->contents.insert_delete_replace.codepoint = c.codepoint;\n\t\tHIST_APPEND(e);\n\t}\n\n\t/* If there is not enough space... */\n\tif (line->actual == line->available) {\n\t\t/* Expand the line buffer */\n\t\tif (line->available == 0) {\n\t\t\tline->available = 8;\n\t\t} else {\n\t\t\tline->available *= 2;\n\t\t}\n\t\tline = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available);\n\t}\n\n\t/* If this was not the last character, then shift remaining characters forward. */\n\tif (offset < line->actual) {\n\t\tmemmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset));\n\t}\n\n\t/* Insert the new character */\n\tline->text[offset] = c;\n\n\t/* There is one new character in the line */\n\tline->actual += 1;\n\n\tif (!env->loading) {\n\t\tline->rev_status = 2; /* Modified */\n\t\trecalculate_tabs(line);\n\t\trecalculate_syntax(line, lineno);\n\t}\n\n\treturn line;\n}\n\n/**\n * (Primitive) Delete a character from a line.\n *\n * Remove the character at the given offset. We never shrink lines, so this\n * does not have a return value, and delete should never be called during\n * a loading operation (though it may be called during a history replay).\n */\nvoid line_delete(line_t * line, int offset, int lineno) {\n\n\t/* Can't delete character before start of line. */\n\tif (offset == 0) return;\n\t/* Can't delete past end of line either */\n\tif (offset > line->actual) return;\n\n\tif (!env->loading && global_config.history_enabled && lineno != -1) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_DELETE;\n\t\te->contents.insert_delete_replace.lineno = lineno;\n\t\te->contents.insert_delete_replace.offset = offset;\n\t\te->contents.insert_delete_replace.old_codepoint = line->text[offset-1].codepoint;\n\t\tHIST_APPEND(e);\n\t}\n\n\t/* If this isn't the last character, we need to move all subsequent characters backwards */\n\tif (offset < line->actual) {\n\t\tmemmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset));\n\t}\n\n\t/* The line is one character shorter */\n\tline->actual -= 1;\n\tline->rev_status = 2;\n\n\trecalculate_tabs(line);\n\trecalculate_syntax(line, lineno);\n}\n\n/**\n * (Primitive) Replace a character in a line.\n *\n * Replaces the codepoint at the given offset with a new character. Since this\n * does not involve any size changes, it does not have a return value.\n * Since a replacement character may be a tab, we do still need to recalculate\n * character widths for tabs as they may change.\n */\nvoid line_replace(line_t * line, char_t _c, int offset, int lineno) {\n\n\tif (!env->loading && global_config.history_enabled && lineno != -1) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_REPLACE;\n\t\te->contents.insert_delete_replace.lineno = lineno;\n\t\te->contents.insert_delete_replace.offset = offset;\n\t\te->contents.insert_delete_replace.codepoint = _c.codepoint;\n\t\te->contents.insert_delete_replace.old_codepoint = line->text[offset].codepoint;\n\t\tHIST_APPEND(e);\n\t}\n\n\tline->text[offset] = _c;\n\n\tif (!env->loading) {\n\t\tline->rev_status = 2; /* Modified */\n\t\trecalculate_tabs(line);\n\t\trecalculate_syntax(line, lineno);\n\t}\n}\n\n/**\n * (Primitive) Remove a line from the active buffer\n *\n * This primitive is only valid for a buffer. Delete a line, or if this is the\n * only line in the buffer, clear it but keep the line around with no\n * characters. We use the `line_delete` primitive to clear that line,\n * otherwise we are our own primitive and produce history entries.\n *\n * While we do not shrink the `lines` array, it is returned here anyway.\n */\nline_t ** remove_line(line_t ** lines, int offset) {\n\n\t/* If there is only one line, clear it instead of removing it. */\n\tif (env->line_count == 1) {\n\t\twhile (lines[offset]->actual > 0) {\n\t\t\tline_delete(lines[offset], lines[offset]->actual, offset);\n\t\t}\n\t\treturn lines;\n\t}\n\n\t/* When a line is removed, we need to keep its contents so we\n\t * can un-remove it on redo... */\n\tif (!env->loading && global_config.history_enabled) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_REMOVE_LINE;\n\t\te->contents.remove_replace_line.lineno = offset;\n\t\te->contents.remove_replace_line.old_contents = malloc(sizeof(line_t) + sizeof(char_t) * lines[offset]->available);\n\t\tmemcpy(e->contents.remove_replace_line.old_contents, lines[offset], sizeof(line_t) + sizeof(char_t) * lines[offset]->available);\n\t\tHIST_APPEND(e);\n\t}\n\n\t/* Otherwise, free the data used by the line */\n\tfree(lines[offset]);\n\n\t/* Move other lines up */\n\tif (offset < env->line_count-1) {\n\t\tmemmove(&lines[offset], &lines[offset+1], sizeof(line_t *) * (env->line_count - (offset - 1)));\n\t\tlines[env->line_count-1] = NULL;\n\t}\n\n\t/* There is one less line */\n\tenv->line_count -= 1;\n\treturn lines;\n}\n\n/**\n * (Primitive) Add a new line to the active buffer.\n *\n * Inserts a new line into a buffer at the given line offset.\n * Since this grows the buffer, it will return the new line array\n * after reallocation if needed.\n */\nline_t ** add_line(line_t ** lines, int offset) {\n\n\t/* Invalid offset? */\n\tif (offset > env->line_count) return lines;\n\n\tif (!env->loading && global_config.history_enabled) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_ADD_LINE;\n\t\te->contents.add_merge_split_lines.lineno = offset;\n\t\tHIST_APPEND(e);\n\t}\n\n\t/* Not enough space */\n\tif (env->line_count == env->line_avail) {\n\t\t/* Allocate more space */\n\t\tenv->line_avail *= 2;\n\t\tlines = realloc(lines, sizeof(line_t *) * env->line_avail);\n\t}\n\n\t/* If this isn't the last line, move other lines down */\n\tif (offset < env->line_count) {\n\t\tmemmove(&lines[offset+1], &lines[offset], sizeof(line_t *) * (env->line_count - offset));\n\t}\n\n\t/* Allocate the new line */\n\tlines[offset] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1);\n\tlines[offset]->available = 32;\n\n\t/* There is one new line */\n\tenv->line_count += 1;\n\tenv->lines = lines;\n\n\tif (!env->loading) {\n\t\tlines[offset]->rev_status = 2; /* Modified */\n\t}\n\n\tif (offset > 0 && !env->loading) {\n\t\trecalculate_syntax(lines[offset-1],offset-1);\n\t}\n\treturn lines;\n}\n\n/**\n * (Primitive) Replace a line with data from another line.\n *\n * This is only called when pasting yanks after calling `add_line`,\n * but it allows us to have simpler history for that action.\n */\nvoid replace_line(line_t ** lines, int offset, line_t * replacement) {\n\n\tif (!env->loading && global_config.history_enabled) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_REPLACE_LINE;\n\t\te->contents.remove_replace_line.lineno = offset;\n\t\te->contents.remove_replace_line.old_contents = malloc(sizeof(line_t) + sizeof(char_t) * lines[offset]->available);\n\t\tmemcpy(e->contents.remove_replace_line.old_contents, lines[offset], sizeof(line_t) + sizeof(char_t) * lines[offset]->available);\n\t\te->contents.remove_replace_line.contents = malloc(sizeof(line_t) + sizeof(char_t) * replacement->available);\n\t\tmemcpy(e->contents.remove_replace_line.contents, replacement, sizeof(line_t) + sizeof(char_t) * replacement->available);\n\t\tHIST_APPEND(e);\n\t}\n\n\tif (lines[offset]->available < replacement->actual) {\n\t\tlines[offset] = realloc(lines[offset], sizeof(line_t) + sizeof(char_t) * replacement->available);\n\t\tlines[offset]->available = replacement->available;\n\t}\n\tlines[offset]->actual = replacement->actual;\n\tmemcpy(&lines[offset]->text, &replacement->text, sizeof(char_t) * replacement->actual);\n\n\tif (!env->loading) {\n\t\tlines[offset]->rev_status = 2;\n\t\trecalculate_syntax(lines[offset],offset);\n\t}\n}\n\n/**\n * (Primitive) Merge two consecutive lines.\n *\n * Take two lines in a buffer and turn them into one line.\n * `lineb` is the offset of the second line... or the\n * line number of the first line, depending on which indexing\n * system you prefer to think about. This won't grow `lines`,\n * but it will likely modify it and can reallocate individual\n * lines as well.\n */\nline_t ** merge_lines(line_t ** lines, int lineb) {\n\n\t/* linea is the line immediately before lineb */\n\tint linea = lineb - 1;\n\n\tif (!env->loading && global_config.history_enabled) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_MERGE_LINES;\n\t\te->contents.add_merge_split_lines.lineno = lineb;\n\t\te->contents.add_merge_split_lines.split = env->lines[linea]->actual;\n\t\tHIST_APPEND(e);\n\t}\n\n\t/* If there isn't enough space in linea hold both... */\n\tif (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) {\n\t\twhile (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) {\n\t\t\t/* ... allocate more space until it fits */\n\t\t\tif (lines[linea]->available == 0) {\n\t\t\t\tlines[linea]->available = 8;\n\t\t\t} else {\n\t\t\t\tlines[linea]->available *= 2;\n\t\t\t}\n\t\t}\n\t\tlines[linea] = realloc(lines[linea], sizeof(line_t) + sizeof(char_t) * lines[linea]->available);\n\t}\n\n\t/* Copy the second line into the first line */\n\tmemcpy(&lines[linea]->text[lines[linea]->actual], &lines[lineb]->text, sizeof(char_t) * lines[lineb]->actual);\n\n\t/* The first line is now longer */\n\tlines[linea]->actual = lines[linea]->actual + lines[lineb]->actual;\n\n\tif (!env->loading) {\n\t\tlines[linea]->rev_status = 2;\n\t\trecalculate_tabs(lines[linea]);\n\t\trecalculate_syntax(lines[linea], linea);\n\t}\n\n\t/* Remove the second line */\n\tfree(lines[lineb]);\n\n\t/* Move other lines up */\n\tif (lineb < env->line_count) {\n\t\tmemmove(&lines[lineb], &lines[lineb+1], sizeof(line_t *) * (env->line_count - (lineb - 1)));\n\t\tlines[env->line_count-1] = NULL;\n\t}\n\n\t/* There is one less line */\n\tenv->line_count -= 1;\n\treturn lines;\n}\n\n/**\n * (Primitive) Split a line into two lines at the given column.\n *\n * Takes one line and makes it two lines. There are some optimizations\n * if you are trying to \"split\" at the first or last column, which\n * are both just treated as add_line.\n */\nline_t ** split_line(line_t ** lines, int line, int split) {\n\n\t/* If we're trying to split from the start, just add a new blank line before */\n\tif (split == 0) {\n\t\treturn add_line(lines, line);\n\t}\n\n\tif (!env->loading && global_config.history_enabled) {\n\t\thistory_t * e = malloc(sizeof(history_t));\n\t\te->type = HISTORY_SPLIT_LINE;\n\t\te->contents.add_merge_split_lines.lineno = line;\n\t\te->contents.add_merge_split_lines.split  = split;\n\t\tHIST_APPEND(e);\n\t}\n\n\tif (!env->loading) {\n\t\tunhighlight_matching_paren();\n\t}\n\n\t/* Allocate more space as needed */\n\tif (env->line_count == env->line_avail) {\n\t\tenv->line_avail *= 2;\n\t\tlines = realloc(lines, sizeof(line_t *) * env->line_avail);\n\t}\n\n\t/* Shift later lines down */\n\tif (line < env->line_count) {\n\t\tmemmove(&lines[line+2], &lines[line+1], sizeof(line_t *) * (env->line_count - line));\n\t}\n\n\tint remaining = lines[line]->actual - split;\n\n\t/* This is some wacky math to get a good power-of-two */\n\tint v = remaining;\n\tv--;\n\tv |= v >> 1;\n\tv |= v >> 2;\n\tv |= v >> 4;\n\tv |= v >> 8;\n\tv |= v >> 16;\n\tv++;\n\n\t/* Allocate space for the new line */\n\tlines[line+1] = calloc(sizeof(line_t) + sizeof(char_t) * v, 1);\n\tlines[line+1]->available = v;\n\tlines[line+1]->actual = remaining;\n\n\t/* Move the data from the old line into the new line */\n\tmemmove(lines[line+1]->text, &lines[line]->text[split], sizeof(char_t) * remaining);\n\tlines[line]->actual = split;\n\n\tif (!env->loading) {\n\t\tlines[line]->rev_status = 2;\n\t\tlines[line+1]->rev_status = 2;\n\t\trecalculate_tabs(lines[line]);\n\t\trecalculate_tabs(lines[line+1]);\n\t\trecalculate_syntax(lines[line], line);\n\t\trecalculate_syntax(lines[line+1], line+1);\n\t}\n\n\t/* There is one new line */\n\tenv->line_count += 1;\n\n\t/* We may have reallocated lines */\n\treturn lines;\n}\n\n/**\n * Primitives end here. Everything after this point that wants to\n * perform modifications must be built on these primitives and\n * should never directly modify env->lines or the contents thereof,\n * outside of syntax highlighting flag changes.\n */\n\n/**\n * The following section is where we implement \"smart\" automatic\n * indentation. A lot of this is hacked-together nonsense and \"smart\"\n * is a bit of an overstatement.\n */\n\n/**\n * Understand spaces and comments and check if the previous line\n * ended with a brace or a colon.\n */\nint line_ends_with_brace(line_t * line) {\n\tint i = line->actual-1;\n\twhile (i >= 0) {\n\t\tif ((line->text[i].flags & 0x1F) == FLAG_COMMENT || line->text[i].codepoint == ' ') {\n\t\t\ti--;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (i < 0) return 0;\n\treturn (line->text[i].codepoint == '{' || line->text[i].codepoint == ':') ? i+1 : 0;\n}\n\n/**\n * Determine if a given line is a comment by checking its initial\n * syntax highlighting state value. This is used by automatic indentation\n * to continue block comments in languages like C.\n *\n * TODO: Why isn't this a syntax-> method?\n */\nint line_is_comment(line_t * line) {\n\tif (!env->syntax) return 0;\n\n\tif (!strcmp(env->syntax->name,\"c\")) {\n\t\tif (line->istate == 1) return 1;\n\t} else if (!strcmp(env->syntax->name,\"java\")) {\n\t\tif (line->istate == 1) return 1;\n\t} else if (!strcmp(env->syntax->name,\"kotlin\")) {\n\t\tif (line->istate == 1) return 1;\n\t} else if (!strcmp(env->syntax->name,\"rust\")) {\n\t\tif (line->istate > 0) return 1;\n\t}\n\n\treturn 0;\n}\n\n/**\n * Figure out where the indentation for a brace belongs by finding\n * where the start of the line is after whitespace. This is called\n * by default when we insert } and tries to align it with the indentation\n * of the matching opening {.\n */\nint find_brace_line_start(int line, int col) {\n\tint ncol = col - 1;\n\twhile (ncol > 0) {\n\t\tif (env->lines[line-1]->text[ncol-1].codepoint == ')') {\n\t\t\tint t_line_no = env->line_no;\n\t\t\tint t_col_no = env->col_no;\n\t\t\tenv->line_no = line;\n\t\t\tenv->col_no = ncol;\n\t\t\tint paren_match_line = -1, paren_match_col = -1;\n\t\t\tfind_matching_paren(&paren_match_line, &paren_match_col, 1);\n\n\t\t\tif (paren_match_line != -1) {\n\t\t\t\tline = paren_match_line;\n\t\t\t}\n\n\t\t\tenv->line_no = t_line_no;\n\t\t\tenv->col_no = t_col_no;\n\t\t\tbreak;\n\t\t} else if (env->lines[line-1]->text[ncol-1].codepoint == ' ') {\n\t\t\tncol--;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn line;\n}\n\n/**\n * Add indentation from the previous (temporally) line.\n *\n * By \"temporally\", we mean not necessarily the line above, but\n * potentially the line below if we are inserting a line above\n * the cursor.\n */\nvoid add_indent(int new_line, int old_line, int ignore_brace) {\n\tif (env->indent) {\n\t\tint changed = 0;\n\t\tif (old_line < new_line && line_is_comment(env->lines[new_line])) {\n\t\t\tfor (int i = 0; i < env->lines[old_line]->actual; ++i) {\n\t\t\t\tif (env->lines[old_line]->text[i].codepoint == '/') {\n\t\t\t\t\tif (env->lines[old_line]->text[i+1].codepoint == '*') {\n\t\t\t\t\t\t/* Insert ' * ' */\n\t\t\t\t\t\tchar_t space = {1,FLAG_COMMENT,' '};\n\t\t\t\t\t\tchar_t asterisk = {1,FLAG_COMMENT,'*'};\n\t\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line);\n\t\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line);\n\t\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line);\n\t\t\t\t\t\tenv->col_no += 3;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (env->lines[old_line]->text[i].codepoint == ' ' && env->lines[old_line]->text[i+1].codepoint == '*') {\n\t\t\t\t\t/* Insert ' * ' */\n\t\t\t\t\tchar_t space = {1,FLAG_COMMENT,' '};\n\t\t\t\t\tchar_t asterisk = {1,FLAG_COMMENT,'*'};\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line);\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line);\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line);\n\t\t\t\t\tenv->col_no += 3;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (env->lines[old_line]->text[i].codepoint == ' ' ||\n\t\t\t\t\tenv->lines[old_line]->text[i].codepoint == '\\t' ||\n\t\t\t\t\tenv->lines[old_line]->text[i].codepoint == '*') {\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],env->lines[old_line]->text[i],i,new_line);\n\t\t\t\t\tenv->col_no++;\n\t\t\t\t\tchanged = 1;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tint line_to_copy_from = old_line;\n\t\t\tint col;\n\t\t\tif (old_line < new_line &&\n\t\t\t\t!ignore_brace &&\n\t\t\t\t(col = line_ends_with_brace(env->lines[old_line])) &&\n\t\t\t\tenv->lines[old_line]->text[col-1].codepoint == '{') {\n\t\t\t\tline_to_copy_from = find_brace_line_start(old_line+1, col)-1;\n\t\t\t}\n\t\t\tfor (int i = 0; i < env->lines[line_to_copy_from]->actual; ++i) {\n\t\t\t\tif (line_to_copy_from < new_line && i == env->lines[line_to_copy_from]->actual - 3 &&\n\t\t\t\t\tenv->lines[line_to_copy_from]->text[i].codepoint == ' ' &&\n\t\t\t\t\tenv->lines[line_to_copy_from]->text[i+1].codepoint == '*' &&\n\t\t\t\t\tenv->lines[line_to_copy_from]->text[i+2].codepoint == '/') {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (env->lines[line_to_copy_from]->text[i].codepoint == ' ' ||\n\t\t\t\t\tenv->lines[line_to_copy_from]->text[i].codepoint == '\\t') {\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line],env->lines[line_to_copy_from]->text[i],i,new_line);\n\t\t\t\t\tenv->col_no++;\n\t\t\t\t\tchanged = 1;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (old_line < new_line && !ignore_brace && line_ends_with_brace(env->lines[old_line])) {\n\t\t\tif (env->tabs) {\n\t\t\t\tchar_t c = {0};\n\t\t\t\tc.codepoint = '\\t';\n\t\t\t\tc.display_width = env->tabstop;\n\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line);\n\t\t\t\tenv->col_no++;\n\t\t\t\tchanged = 1;\n\t\t\t} else {\n\t\t\t\tfor (int j = 0; j < env->tabstop; ++j) {\n\t\t\t\t\tchar_t c = {0};\n\t\t\t\t\tc.codepoint = ' ';\n\t\t\t\t\tc.display_width = 1;\n\t\t\t\t\tenv->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line);\n\t\t\t\t\tenv->col_no++;\n\t\t\t\t}\n\t\t\t\tchanged = 1;\n\t\t\t}\n\t\t}\n\t\tint was_whitespace = 1;\n\t\tfor (int i = 0; i < env->lines[old_line]->actual; ++i) {\n\t\t\tif (env->lines[old_line]->text[i].codepoint != ' ' &&\n\t\t\t\tenv->lines[old_line]->text[i].codepoint != '\\t') {\n\t\t\t\twas_whitespace = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (was_whitespace) {\n\t\t\twhile (env->lines[old_line]->actual) {\n\t\t\t\tline_delete(env->lines[old_line], env->lines[old_line]->actual, old_line);\n\t\t\t}\n\t\t}\n\t\tif (changed) {\n\t\t\trecalculate_syntax(env->lines[new_line],new_line);\n\t\t}\n\t}\n}\n\n/**\n * Initialize a buffer with default values.\n *\n * Should be called after creating a buffer.\n */\nvoid setup_buffer(buffer_t * env) {\n\t/* If this buffer was already initialized, clear out its line data */\n\tif (env->lines) {\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tfree(env->lines[i]);\n\t\t}\n\t\tfree(env->lines);\n\t}\n\n\t/* Default state parameters */\n\tenv->line_no     = 1; /* Default cursor position */\n\tenv->col_no      = 1;\n\tenv->line_count  = 1; /* Buffers always have at least one line */\n\tenv->modified    = 0;\n\tenv->readonly    = 0;\n\tenv->offset      = 0;\n\tenv->line_avail  = 8; /* Default line buffer capacity */\n\tenv->tabs        = 1; /* Tabs by default */\n\tenv->tabstop     = 4; /* Tab stop width */\n\tenv->indent      = 1; /* Auto-indent by default */\n\tenv->history     = malloc(sizeof(struct history));\n\tmemset(env->history, 0, sizeof(struct history));\n\tenv->last_save_history = env->history;\n\n\t/* Allocate line buffer */\n\tenv->lines = malloc(sizeof(line_t *) * env->line_avail);\n\n\t/* Initialize the first line */\n\tenv->lines[0] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1);\n\tenv->lines[0]->available = 32;\n}\n\n/**\n * Toggle buffered / unbuffered modes\n */\nstruct termios old;\nvoid get_initial_termios(void) {\n\ttcgetattr(STDOUT_FILENO, &old);\n}\n\nvoid set_unbuffered(void) {\n\tstruct termios new = old;\n\tnew.c_iflag &= (~ICRNL) & (~IXON);\n\tnew.c_lflag &= (~ICANON) & (~ECHO) & (~ISIG);\n#ifdef VLNEXT\n\tnew.c_cc[VLNEXT] = 0;\n#endif\n#ifdef VDISCARD\n\tnew.c_cc[VDISCARD] = 0;\n#endif\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);\n}\n\nvoid set_buffered(void) {\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);\n}\n\n/**\n * Convert codepoint to utf-8 string\n */\nint to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\n/**\n * Get the presentation width of a codepoint\n */\nint codepoint_width(wchar_t codepoint) {\n\tif (codepoint == '\\t') {\n\t\treturn 1; /* Recalculate later */\n\t}\n\tif (codepoint < 32) {\n\t\t/* We render these as ^@ */\n\t\treturn 2;\n\t}\n\tif (codepoint == 0x7F) {\n\t\t/* Renders as ^? */\n\t\treturn 2;\n\t}\n\tif (codepoint > 0x7f && codepoint < 0xa0) {\n\t\t/* Upper control bytes <xx> */\n\t\treturn 4;\n\t}\n\tif (codepoint == 0xa0) {\n\t\t/* Non-breaking space _ */\n\t\treturn 1;\n\t}\n\t/* Skip wcwidth for anything under 256 */\n\tif (codepoint > 127) {\n\t\tif (global_config.can_unicode) {\n\t\t\t/* Higher codepoints may be wider (eg. Japanese) */\n\t\t\tint out = wcwidth(codepoint);\n\t\t\tif (out >= 1) return out;\n\t\t}\n\t\t/* Invalid character, render as [U+ABCD] or [U+ABCDEF] */\n\t\treturn (codepoint < 0x10000) ? 8 : 10;\n\t}\n\treturn 1;\n}\n\n/**\n * The following section contains methods for crafting terminal escapes\n * for rendering the display. We do not use curses or any similar\n * library, so we have to generate these sequences ourself based on\n * some assumptions about the target terminal.\n */\n\n/**\n * Move the terminal cursor\n */\nvoid place_cursor(int x, int y) {\n\tprintf(\"\\033[%d;%dH\", y, x);\n}\n\n/**\n * Given two color codes from a theme, convert them to an escape\n * sequence to set the foreground and background color. This allows\n * us to support basic 16, xterm 256, and true color in themes.\n */\nchar * color_string(const char * fg, const char * bg) {\n\tstatic char output[100];\n\tchar * t = output;\n\tt += sprintf(t,\"\\033[22;23;\");\n\tif (*bg == '@') {\n\t\tint _bg = atoi(bg+1);\n\t\tif (_bg < 10) {\n\t\t\tt += sprintf(t, \"4%d;\", _bg);\n\t\t} else {\n\t\t\tt += sprintf(t, \"10%d;\", _bg-10);\n\t\t}\n\t} else {\n\t\tt += sprintf(t, \"48;%s;\", bg);\n\t}\n\tif (*fg == '@') {\n\t\tint _fg = atoi(fg+1);\n\t\tif (_fg < 10) {\n\t\t\tt += sprintf(t, \"3%dm\", _fg);\n\t\t} else {\n\t\t\tt += sprintf(t, \"9%dm\", _fg-10);\n\t\t}\n\t} else {\n\t\tt += sprintf(t, \"38;%sm\", fg);\n\t}\n\treturn output;\n}\n\n/**\n * Set text colors\n *\n * Normally, text colors are just strings, but if they\n * start with @ they will be parsed as integers\n * representing one of the 16 standard colors, suitable\n * for terminals without support for the 256- or 24-bit\n * color modes.\n */\nvoid set_colors(const char * fg, const char * bg) {\n\tprintf(\"%s\", color_string(fg, bg));\n}\n\n/**\n * Set just the foreground color\n *\n * (See set_colors above)\n */\nvoid set_fg_color(const char * fg) {\n\tprintf(\"\\033[22;23;\");\n\tif (*fg == '@') {\n\t\tint _fg = atoi(fg+1);\n\t\tif (_fg < 10) {\n\t\t\tprintf(\"3%dm\", _fg);\n\t\t} else {\n\t\t\tprintf(\"9%dm\", _fg-10);\n\t\t}\n\t} else {\n\t\tprintf(\"38;%sm\", fg);\n\t}\n}\n\n/**\n * Clear the rest of this line\n */\nvoid clear_to_end(void) {\n\tif (global_config.can_bce) {\n\t\tprintf(\"\\033[K\");\n\t}\n}\n\n/**\n * For terminals without bce, prepaint the whole line, so we don't have to track\n * where the cursor is for everything. Inefficient, but effective.\n */\nvoid paint_line(const char * bg) {\n\tif (!global_config.can_bce) {\n\t\tset_colors(COLOR_FG, bg);\n\t\tfor (int i = 0; i < global_config.term_width; ++i) {\n\t\t\tprintf(\" \");\n\t\t}\n\t\tprintf(\"\\r\");\n\t}\n}\n\n/**\n * Enable bold text display\n */\nvoid set_bold(void) {\n\tprintf(\"\\033[1m\");\n}\n\n/**\n * Disable bold\n */\nvoid unset_bold(void) {\n\tprintf(\"\\033[22m\");\n}\n\n/**\n * Enable underlined text display\n */\nvoid set_underline(void) {\n\tprintf(\"\\033[4m\");\n}\n\n/**\n * Disable underlined text display\n */\nvoid unset_underline(void) {\n\tprintf(\"\\033[24m\");\n}\n\n/**\n * Reset text display attributes\n */\nvoid reset(void) {\n\tprintf(\"\\033[0m\");\n}\n\n/**\n * Clear the entire screen\n */\nvoid clear_screen(void) {\n\tprintf(\"\\033[H\\033[2J\");\n}\n\n/**\n * Hide the cursor\n */\nvoid hide_cursor(void) {\n\tif (global_config.can_hideshow) {\n\t\tprintf(\"\\033[?25l\");\n\t}\n}\n\n/**\n * Show the cursor\n */\nvoid show_cursor(void) {\n\tif (global_config.can_hideshow) {\n\t\tprintf(\"\\033[?25h\");\n\t}\n}\n\n/**\n * Store the cursor position\n */\nvoid store_cursor(void) {\n\tprintf(\"\\0337\");\n}\n\n/**\n * Restore the cursor position.\n */\nvoid restore_cursor(void) {\n\tprintf(\"\\0338\");\n}\n\n/**\n * Request mouse events\n */\nvoid mouse_enable(void) {\n\tif (global_config.can_mouse) {\n\t\tprintf(\"\\033[?1000h\");\n\t\tif (global_config.can_sgrmouse) {\n\t\t\tprintf(\"\\033[?1006h\");\n\t\t}\n\t}\n}\n\n/**\n * Stop mouse events\n */\nvoid mouse_disable(void) {\n\tif (global_config.can_mouse) {\n\t\tif (global_config.can_sgrmouse) {\n\t\t\tprintf(\"\\033[?1006l\");\n\t\t}\n\t\tprintf(\"\\033[?1000l\");\n\t}\n}\n\n/**\n * Shift the screen up one line\n */\nvoid shift_up(int amount) {\n\tprintf(\"\\033[%dS\", amount);\n}\n\n/**\n * Shift the screen down one line.\n */\nvoid shift_down(int amount) {\n\tprintf(\"\\033[%dT\", amount);\n}\n\nvoid insert_lines_at(int line, int count) {\n\tplace_cursor(1, line);\n\tprintf(\"\\033[%dL\", count);\n}\n\nvoid delete_lines_at(int line, int count) {\n\tplace_cursor(1, line);\n\tprintf(\"\\033[%dM\", count);\n}\n\n/**\n * Switch to the alternate terminal screen.\n */\nvoid set_alternate_screen(void) {\n\tif (global_config.can_altscreen) {\n\t\tprintf(\"\\033[?1049h\");\n\t}\n}\n\n/**\n * Restore the standard terminal screen.\n */\nvoid unset_alternate_screen(void) {\n\tif (global_config.can_altscreen) {\n\t\tprintf(\"\\033[?1049l\");\n\t}\n}\n\n/**\n * Enable bracketed paste mode.\n */\nvoid set_bracketed_paste(void) {\n\tif (global_config.can_bracketedpaste) {\n\t\tprintf(\"\\033[?2004h\");\n\t}\n}\n\n/**\n * Disable bracketed paste mode.\n */\nvoid unset_bracketed_paste(void) {\n\tif (global_config.can_bracketedpaste) {\n\t\tprintf(\"\\033[?2004l\");\n\t}\n}\n\n/**\n * Get the name of just a file from a full path.\n * Returns a pointer within the original string.\n *\n * Called in a few different places where the name of a file\n * is needed from its full path, such as drawing tab names or\n * building HTML files.\n */\nchar * file_basename(char * file) {\n\tchar * c = strrchr(file, '/');\n\tif (!c) return file;\n\treturn (c+1);\n}\n\n/**\n * Print a tab name with fixed width and modifiers\n * into an output buffer and return the written width.\n */\nint draw_tab_name(buffer_t * _env, char * out, int max_width, int * width) {\n\tuint32_t c, state = 0;\n\tchar * t = _env->file_name ? file_basename(_env->file_name) : \"[No Name]\";\n\n#define ADD(c) do { \\\n\t*o = c; \\\n\to++; \\\n\t*o = '\\0'; \\\n\tbytes++; \\\n} while (0)\n\n\tchar * o = out;\n\t*o = '\\0';\n\n\tint bytes = 0;\n\n\tif (max_width < 2) return 1;\n\n\tADD(' ');\n\t(*width)++;\n\n\tif (_env->modified) {\n\t\tif (max_width < 4) return 1;\n\t\tADD('+');\n\t\t(*width)++;\n\t\tADD(' ');\n\t\t(*width)++;\n\t}\n\n\twhile (*t) {\n\t\t/* File names can definitely by UTF-8, and we need to\n\t\t * understand their display width... */\n\t\tif (!decode(&state, &c, (unsigned char)*t)) {\n\n\t\t\t/* But our displayed tab name is also just stored\n\t\t\t * as UTF-8 again, so we essentially rebuild it... */\n\t\t\tchar tmp[7];\n\t\t\tint size = to_eight(c, tmp);\n\t\t\tif (bytes + size > 62) break;\n\t\t\tif (*width + size >= max_width) return 1;\n\n\t\t\tfor (int i = 0; i < size; ++i) {\n\t\t\t\tADD(tmp[i]);\n\t\t\t}\n\n\t\t\t(*width) += codepoint_width(c);\n\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t\tt++;\n\t}\n\n\tif (max_width == *width + 1) return 1;\n\n\tADD(' ');\n\t(*width)++;\n\n#undef ADD\n\treturn 0;\n}\n\n/**\n * Redraw the tabbar, with a tab for each buffer.\n *\n * The active buffer is highlighted.\n */\nvoid redraw_tabbar(void) {\n\tif (!global_config.tabs_visible) return;\n\t/* Hide cursor while rendering UI */\n\thide_cursor();\n\n\t/* Move to upper left */\n\tplace_cursor(1,1);\n\n\tpaint_line(COLOR_TABBAR_BG);\n\n\t/* For each buffer... */\n\tint offset = 0;\n\n\tif (global_config.tab_offset) {\n\t\tset_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG);\n\t\tprintf(\"<\");\n\t\toffset++;\n\t}\n\n\tfor (int i = global_config.tab_offset; i < buffers_len; i++) {\n\t\tbuffer_t * _env = buffers[i];\n\n\t\tif (_env == env) {\n\t\t\t/* If this is the active buffer, highlight it */\n\t\t\treset();\n\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t\tset_bold();\n\t\t} else {\n\t\t\t/* Otherwise use default tab color */\n\t\t\treset();\n\t\t\tset_colors(COLOR_FG, COLOR_TAB_BG);\n\t\t\tset_underline();\n\t\t}\n\n\t\tif (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) {\n\t\t\tif (global_config.command_buffer->actual) {\n\t\t\t\tchar * f = _env->file_name ? file_basename(_env->file_name) : \"\";\n\t\t\t\t/* TODO: Support unicode input here; needs conversion */\n\t\t\t\tint i = 0;\n\t\t\t\tfor (; i < global_config.command_buffer->actual &&\n\t\t\t\t      f[i] == global_config.command_buffer->text[i].codepoint; ++i);\n\t\t\t\tif (global_config.command_buffer->actual == i) {\n\t\t\t\t\tset_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n\t\tchar title[64];\n\t\tint size = 0;\n\t\tint filled = draw_tab_name(_env, title, global_config.term_width - offset, &size);\n\n\t\tif (filled) {\n\t\t\toffset += size;\n\t\t\tprintf(\"%s\", title);\n\t\t\tset_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG);\n\t\t\twhile (offset != global_config.term_width - 1) {\n\t\t\t\tprintf(\" \");\n\t\t\t\toffset++;\n\t\t\t}\n\t\t\tprintf(\">\");\n\t\t\tbreak;\n\t\t}\n\n\t\tprintf(\"%s\", title);\n\n\t\toffset += size;\n\t}\n\n\t/* Reset bold/underline */\n\treset();\n\t/* Fill the rest of the tab bar */\n\tset_colors(COLOR_FG, COLOR_TABBAR_BG);\n\tclear_to_end();\n}\n\n/**\n * Braindead log10 implementation for figuring out the width of the\n * line number column.\n */\nint log_base_10(unsigned int v) {\n\tint r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 :\n\t\t(v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 :\n\t\t(v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0;\n\treturn r;\n}\n\n/**\n * Render a line of text.\n *\n * This handles rendering the actual text content. A full line of text\n * also includes a line number and some padding, but those elements\n * are rendered elsewhere; this method can be used for lines that are\n * not attached to a buffer such as command input lines.\n *\n * width: width of the text display region (term width - line number width)\n * offset: how many cells into the line to start rendering at\n */\nvoid render_line(line_t * line, int width, int offset, int line_no) {\n\tint i = 0; /* Offset in char_t line data entries */\n\tint j = 0; /* Offset in terminal cells */\n\n\tconst char * last_color = NULL;\n\tint was_selecting = 0, was_searching = 0, was_underlining = 0;\n\n\t/* Set default text colors */\n\tset_colors(COLOR_FG, line->is_current ? COLOR_ALT_BG : COLOR_BG);\n\n\t/*\n\t * When we are rendering in the middle of a wide character,\n\t * we render -'s to fill the remaining amount of the character's width.\n\t */\n\tint remainder = 0;\n\n\tint is_spaces = 1;\n\n\t/* For each character in the line ... */\n\twhile (i < line->actual) {\n\n\t\t/* If there is remaining text... */\n\t\tif (remainder) {\n\n\t\t\t/* If we should be drawing by now... */\n\t\t\tif (j >= offset) {\n\t\t\t\tif (was_underlining) printf(\"\\033[24m\");\n\t\t\t\t/* Fill remainder with -'s */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"-\");\n\t\t\t\tset_colors(COLOR_FG, line->is_current ? COLOR_ALT_BG : COLOR_BG);\n\t\t\t}\n\n\t\t\t/* One less remaining width cell to fill */\n\t\t\tremainder--;\n\n\t\t\t/* Terminal offset moves forward */\n\t\t\tj++;\n\n\t\t\t/*\n\t\t\t * If this was the last remaining character, move to\n\t\t\t * the next codepoint in the line\n\t\t\t */\n\t\t\tif (remainder == 0) {\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Get the next character to draw */\n\t\tchar_t c = line->text[i];\n\n\t\tif (c.codepoint != ' ') is_spaces = 0;\n\n\t\t/* If we should be drawing by now... */\n\t\tif (j >= offset) {\n\n\t\t\t/* If this character is going to fall off the edge of the screen... */\n\t\t\tif (j - offset + c.display_width >= width) {\n\t\t\t\t/* We draw this with special colors so it isn't ambiguous */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tif (was_underlining) printf(\"\\033[24m\");\n\n\t\t\t\t/* If it's wide, draw ---> as needed */\n\t\t\t\twhile (j - offset < width - 1) {\n\t\t\t\t\tprintf(\"-\");\n\t\t\t\t\tj++;\n\t\t\t\t}\n\n\t\t\t\t/* End the line with a > to show it overflows */\n\t\t\t\tprintf(\">\");\n\t\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* Syntax highlighting */\n\t\t\tconst char * color = flag_to_color(c.flags);\n\n\t\t\tif (c.flags & FLAG_SELECT) {\n\t\t\t\tif ((c.flags & FLAG_MASK_COLORS) == FLAG_NONE) color = COLOR_SELECTFG;\n\t\t\t\tset_colors(color, COLOR_SELECTBG);\n\t\t\t\twas_selecting = 1;\n\t\t\t} else if ((c.flags & FLAG_SEARCH) || (c.flags == FLAG_NOTICE)) {\n\t\t\t\tset_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG);\n\t\t\t\twas_searching = 1;\n\t\t\t} else if (c.flags == FLAG_ERROR) {\n\t\t\t\tset_colors(COLOR_ERROR_FG, COLOR_ERROR_BG);\n\t\t\t\twas_searching = 1; /* co-opting this should work... */\n\t\t\t} else {\n\t\t\t\tif (was_selecting || was_searching) {\n\t\t\t\t\twas_selecting = 0;\n\t\t\t\t\twas_searching = 0;\n\t\t\t\t\tset_colors(color, line->is_current ? COLOR_ALT_BG : COLOR_BG);\n\t\t\t\t\tlast_color = color;\n\t\t\t\t} else if (!last_color || strcmp(color, last_color)) {\n\t\t\t\t\tset_fg_color(color);\n\t\t\t\t\tlast_color = color;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ((c.flags & FLAG_UNDERLINE) && !was_underlining) {\n\t\t\t\tprintf(\"\\033[4m\");\n\t\t\t\twas_underlining = 1;\n\t\t\t} else if (!(c.flags & FLAG_UNDERLINE) && was_underlining) {\n\t\t\t\tprintf(\"\\033[24m\");\n\t\t\t\twas_underlining = 0;\n\t\t\t}\n\n\t\t\tif ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) &&\n\t\t\t\tline_no >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) &&\n\t\t\t\tline_no <= ((env->start_line < env->line_no) ? env->line_no : env->start_line) &&\n\t\t\t\t((j == env->sel_col) ||\n\t\t\t\t(j < env->sel_col && j + c.display_width > env->sel_col))) {\n\t\t\t\tset_colors(COLOR_SELECTFG, COLOR_SELECTBG);\n\t\t\t\twas_selecting = 1;\n\t\t\t}\n\n#define _set_colors(fg,bg) \\\n\tif (!(c.flags & FLAG_SELECT) && !(c.flags & FLAG_SEARCH) && !(was_selecting)) { \\\n\t\tset_colors(fg,(line->is_current && bg == COLOR_BG) ? COLOR_ALT_BG : bg); \\\n\t}\n\n\t\t\t/* Render special characters */\n\t\t\tif (c.codepoint == '\\t') {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"%s\", global_config.tab_indicator);\n\t\t\t\tfor (int i = 1; i < c.display_width; ++i) {\n\t\t\t\t\tprintf(\"%s\" ,global_config.space_indicator);\n\t\t\t\t}\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint < 32) {\n\t\t\t\t/* Codepoints under 32 to get converted to ^@ escapes */\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"^%c\", '@' + c.codepoint);\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint == 0x7f) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"^?\");\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint > 0x7f && c.codepoint < 0xa0) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"<%2x>\", c.codepoint);\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint == 0xa0) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"_\");\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.display_width == 8) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"[U+%04x]\", c.codepoint);\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.display_width == 10) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"[U+%06x]\", c.codepoint);\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % env->tabstop)) {\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */\n\t\t\t\tif (global_config.can_unicode) {\n\t\t\t\t\tprintf(\"▏\");\n\t\t\t\t} else {\n\t\t\t\t\tprintf(\"|\");\n\t\t\t\t}\n\t\t\t\t_set_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint == ' ' && i == line->actual - 1) {\n\t\t\t\t/* Special case: space at end of line */\n\t\t\t\t_set_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"%s\",global_config.space_indicator);\n\t\t\t\t_set_colors(COLOR_FG, COLOR_BG);\n\t\t\t} else {\n\t\t\t\t/* Normal characters get output */\n\t\t\t\tchar tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */\n\t\t\t\tto_eight(c.codepoint, tmp);\n\t\t\t\tprintf(\"%s\", tmp);\n\t\t\t}\n\n\t\t\t/* Advance the terminal cell offset by the render width of this character */\n\t\t\tj += c.display_width;\n\n\t\t\t/* Advance to the next character */\n\t\t\ti++;\n\t\t} else if (c.display_width > 1) {\n\t\t\t/*\n\t\t\t * If this is a wide character but we aren't ready to render yet,\n\t\t\t * we may need to draw some filler text for the remainder of its\n\t\t\t * width to ensure we don't jump around when horizontally scrolling\n\t\t\t * past wide characters.\n\t\t\t */\n\t\t\tremainder = c.display_width - 1;\n\t\t\tj++;\n\t\t} else {\n\t\t\t/* Regular character, not ready to draw, advance without doing anything */\n\t\t\tj++;\n\t\t\ti++;\n\t\t}\n\t}\n\n\tif (was_underlining) printf(\"\\033[24m\");\n\n\t/**\n\t * Determine what color the rest of the line should be.\n\t */\n\tif (env->mode != MODE_LINE_SELECTION) {\n\t\t/* If we are not selecting, then use the normal background or highlight\n\t\t * the current line if that feature is enabled. */\n\t\tif (line->is_current) {\n\t\t\tset_colors(COLOR_FG, COLOR_ALT_BG);\n\t\t} else {\n\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t}\n\t} else {\n\t\t/* If this line was empty but was part of the selection, we didn't\n\t\t * set the selection color already, so we need to do that here. */\n\t\tif (!line->actual) {\n\t\t\tif (env->line_no == line_no ||\n\t\t\t\t(env->start_line > env->line_no && \n\t\t\t\t\t(line_no >= env->line_no && line_no <= env->start_line)) ||\n\t\t\t\t(env->start_line < env->line_no &&\n\t\t\t\t\t(line_no >= env->start_line && line_no <= env->line_no))) {\n\t\t\t\tset_colors(COLOR_SELECTFG, COLOR_SELECTBG);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * In column modes, we may need to draw a column select beyond the end\n\t * of a given line, so we need to draw up to that point first.\n\t */\n\tif ((env->mode == MODE_COL_SELECTION  || env->mode == MODE_COL_INSERT) &&\n\t\tline_no >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) &&\n\t\tline_no <= ((env->start_line < env->line_no) ? env->line_no : env->start_line) &&\n\t\tj <= env->sel_col &&\n\t\tenv->sel_col < width) {\n\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\twhile (j < env->sel_col) {\n\t\t\tprintf(\" \");\n\t\t\tj++;\n\t\t}\n\t\tset_colors(COLOR_SELECTFG, COLOR_SELECTBG);\n\t\tprintf(\" \");\n\t\tj++;\n\t\tset_colors(COLOR_FG, COLOR_BG);\n\t}\n\n\t/*\n\t * `maxcolumn` renders the background outside of the requested line length\n\t * in a different color, with a line at the border between the two.\n\t */\n\tif (env->maxcolumn && line_no > -1 /* ensures we don't do this for command line */) {\n\n\t\t/* Fill out the normal background */\n\t\tif (j < offset) j = offset;\n\t\tfor (; j < width + offset && j < env->maxcolumn; ++j) {\n\t\t\tprintf(\" \");\n\t\t}\n\n\t\t/* Draw the line */\n\t\tif (j < width + offset && j == env->maxcolumn) {\n\t\t\tj++;\n\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\tif (global_config.can_unicode) {\n\t\t\t\tprintf(\"▏\"); /* Should this be configurable? */\n\t\t\t} else {\n\t\t\t\tprintf(\"|\");\n\t\t\t}\n\t\t}\n\n\t\t/* Fill the rest with the alternate background color */\n\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t}\n\n\t/**\n\t * Clear out the rest of the line. If we are the only buffer or the right split,\n\t * and our terminal supports `bce`, we can just bce; otherwise write spaces\n\t * until we reach the right side of the screen.\n\t */\n\tif (global_config.can_bce && (line_no == -1 || env->left + env->width == global_config.term_width)) {\n\t\tclear_to_end();\n\t} else {\n\t\t/* Paint the rest of the line */\n\t\tif (j < offset) j = offset;\n\t\tfor (; j < width + offset; ++j) {\n\t\t\tprintf(\" \");\n\t\t}\n\t}\n}\n\n/**\n * Get the width of the line number region\n */\nint num_width(void) {\n\tif (!env->numbers) return 0;\n\tint w = log_base_10(env->line_count) + 3;\n\tif (w < 4) return 4;\n\treturn w;\n}\n\n/**\n * Display width of the revision status gutter.\n */\nint gutter_width(void) {\n\treturn env->gutter;\n}\n\n/**\n * Draw the gutter and line numbers.\n */\nvoid draw_line_number(int x) {\n\tif (!env->numbers) return;\n\t/* Draw the line number */\n\tif (env->lines[x]->is_current) {\n\t\tset_colors(COLOR_NUMBER_BG, COLOR_NUMBER_FG);\n\t} else {\n\t\tset_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG);\n\t}\n\tif (global_config.relative_lines && x+1 != env->line_no) {\n\t\tx = x+1 - env->line_no;\n\t\tx = ((x < 0) ? -x : x)-1;\n\t}\n\tint num_size = num_width() - 2; /* Padding */\n\tfor (int y = 0; y < num_size - log_base_10(x + 1); ++y) {\n\t\tprintf(\" \");\n\t}\n\tprintf(\"%d%c\", x + 1, ((x+1 == env->line_no || global_config.horizontal_shift_scrolling) && env->coffset > 0) ? '<' : ' ');\n}\n\n/**\n * Used to highlight the current line after moving the cursor.\n */\nvoid recalculate_current_line(void) {\n\tint something_changed = 0;\n\tif (global_config.highlight_current_line) {\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tif (env->lines[i]->is_current && i != env->line_no-1) {\n\t\t\t\tenv->lines[i]->is_current = 0;\n\t\t\t\tsomething_changed = 1;\n\t\t\t\tredraw_line(i);\n\t\t\t} else if (i == env->line_no-1 && !env->lines[i]->is_current) {\n\t\t\t\tenv->lines[i]->is_current = 1;\n\t\t\t\tsomething_changed = 1;\n\t\t\t\tredraw_line(i);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tsomething_changed = 1;\n\t}\n\tif (something_changed && global_config.relative_lines) {\n\t\tfor (int i = env->offset; i < env->offset + global_config.term_height - global_config.bottom_size - 1 && i < env->line_count; ++i) {\n\t\t\t/* Place cursor for line number */\n\t\t\tplace_cursor(1 + gutter_width() + env->left, (i)-env->offset + 1 + global_config.tabs_visible);\n\t\t\tdraw_line_number(i);\n\t\t}\n\t}\n}\n\n/**\n * Redraw line.\n *\n * This draws the line number as well as the actual text.\n */\nvoid redraw_line(int x) {\n\tif (env->loading) return;\n\n\t/* Determine if this line is visible. */\n\tif (x - env->offset < 0 || x - env->offset > global_config.term_height - global_config.bottom_size - 1 - global_config.tabs_visible) {\n\t\treturn;\n\t}\n\n\t/* Calculate offset in screen */\n\tint j = x - env->offset;\n\n\t/* Hide cursor when drawing */\n\thide_cursor();\n\n\t/* Move cursor to upper left most cell of this line */\n\tplace_cursor(1 + env->left,1 + global_config.tabs_visible + j);\n\n\t/* Draw a gutter on the left. */\n\tif (env->gutter) {\n\t\tswitch (env->lines[x]->rev_status) {\n\t\t\tcase 1:\n\t\t\t\tset_colors(COLOR_NUMBER_FG, COLOR_GREEN);\n\t\t\t\tprintf(\" \");\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tset_colors(COLOR_NUMBER_FG, global_config.color_gutter ? COLOR_SEARCH_BG : COLOR_ALT_FG);\n\t\t\t\tprintf(\" \");\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tset_colors(COLOR_NUMBER_FG, COLOR_KEYWORD);\n\t\t\t\tprintf(\" \");\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_RED);\n\t\t\t\tprintf(\"▆\");\n\t\t\t\tbreak;\n\t\t\tcase 5:\n\t\t\t\tset_colors(COLOR_KEYWORD, COLOR_RED);\n\t\t\t\tprintf(\"▆\");\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tset_colors(COLOR_NUMBER_FG, COLOR_ALT_FG);\n\t\t\t\tprintf(\" \");\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tdraw_line_number(x);\n\n\tint should_shift = x + 1 == env->line_no || global_config.horizontal_shift_scrolling || \n\t\t\t((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) &&\n\t\t\tx + 1 >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) &&\n\t\t\tx + 1 <= ((env->start_line < env->line_no) ? env->line_no : env->start_line));\n\t\n\n\t/*\n\t * Draw the line text \n\t * If this is the active line, the current character cell offset should be used.\n\t * (Non-active lines are not shifted and always render from the start of the line)\n\t */\n\trender_line(env->lines[x], env->width - gutter_width() - num_width(), should_shift ? env->coffset : 0, x+1);\n\n}\n\n/**\n * Draw a ~ line where there is no buffer text.\n */\nvoid draw_excess_line(int j) {\n\tplace_cursor(1+env->left,1 + global_config.tabs_visible + j);\n\tpaint_line(COLOR_ALT_BG);\n\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\tprintf(\"~\");\n\tif (env->left + env->width == global_config.term_width && global_config.can_bce) {\n\t\tclear_to_end();\n\t} else {\n\t\t/* Paint the rest of the line */\n\t\tfor (int x = 1; x < env->width; ++x) {\n\t\t\tprintf(\" \");\n\t\t}\n\t}\n}\n\n/**\n * Redraw the entire text area\n */\nvoid redraw_text(void) {\n\tif (!env) return;\n\tif (!global_config.has_terminal) return;\n\n\t/* Hide cursor while rendering */\n\thide_cursor();\n\n\t/* Figure out the available size of the text region */\n\tint l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible;\n\tint j = 0;\n\n\t/* Draw each line */\n\tfor (int x = env->offset; j < l && x < env->line_count; x++) {\n\t\tredraw_line(x);\n\t\tj++;\n\t}\n\n\t/* Draw the rest of the text region as ~ lines */\n\tfor (; j < l; ++j) {\n\t\tdraw_excess_line(j);\n\t}\n}\n\nstatic int view_left_offset = 0;\nstatic int view_right_offset = 0;\n\n/**\n * When in split view, draw the other buffer.\n * Has special handling for when the split is\n * on a single buffer.\n */\nvoid redraw_alt_buffer(buffer_t * buf) {\n\tif (left_buffer == right_buffer) {\n\t\t/* draw the opposite view */\n\t\tint left, width, offset;\n\t\tleft = env->left;\n\t\twidth = env->width;\n\t\toffset = env->offset;\n\t\tif (left == 0) {\n\t\t\t/* Draw the right side */\n\n\t\t\tenv->left = width;\n\t\t\tenv->width = global_config.term_width - width;\n\t\t\tenv->offset = view_right_offset;\n\t\t\tview_left_offset = offset;\n\t\t} else {\n\t\t\tenv->left = 0;\n\t\t\tenv->width = global_config.term_width * global_config.split_percent / 100;\n\t\t\tenv->offset = view_left_offset;\n\t\t\tview_right_offset = offset;\n\t\t}\n\t\tredraw_text();\n\n\t\tenv->left = left;\n\t\tenv->width = width;\n\t\tenv->offset = offset;\n\t}\n\t/* Swap out active buffer */\n\tbuffer_t * tmp = env;\n\tenv = buf;\n\t/* Redraw text */\n\tredraw_text();\n\t/* Return original active buffer */\n\tenv = tmp;\n}\n\n/**\n * Basically wcswidth() but implemented internally using our\n * own utf-8 decoder to ensure it works properly.\n */\nint display_width_of_string(const char * str) {\n\tuint8_t * s = (uint8_t *)str;\n\n\tint out = 0;\n\tuint32_t c, state = 0;\n\twhile (*s) {\n\t\tif (!decode(&state, &c, *s)) {\n\t\t\tout += codepoint_width(c);\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t\ts++;\n\t}\n\n\treturn out;\n}\n\nvoid statusbar_append_status(int *remaining_width, size_t *filled, char * output, char * base, ...) {\n\tva_list args;\n\tva_start(args, base);\n\tchar tmp[100] = {0}; /* should be big enough */\n\tvsnprintf(tmp, 100, base, args);\n\tva_end(args);\n\n\tint width = display_width_of_string(tmp) + 2;\n\n\tsize_t totalWidth = strlen(tmp);\n\ttotalWidth += strlen(color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\ttotalWidth += strlen(color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\ttotalWidth += strlen(color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\ttotalWidth += 3;\n\n\tif (totalWidth + *filled >= 2047) {\n\t\treturn;\n\t}\n\n\tif (width < *remaining_width) {\n\t\tstrcat(output,color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\t\tstrcat(output,\"[\");\n\t\tstrcat(output,color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\t\tstrcat(output, tmp);\n\t\tstrcat(output,color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\t\tstrcat(output,\"]\");\n\t\t(*remaining_width) -= width;\n\t\t(*filled) += totalWidth;\n\t}\n}\n\nint statusbar_build_right(char * right_hand) {\n\tchar tmp[1024] = {0};\n\tsprintf(tmp, \" Line %d/%d Col: %d \", env->line_no, env->line_count, env->col_no);\n\tint out = display_width_of_string(tmp);\n\tchar * s = right_hand;\n\n\ts += sprintf(s, \"%s\", color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\ts += sprintf(s, \" Line \");\n\ts += sprintf(s, \"%s\", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\ts += sprintf(s, \"%d/%d \", env->line_no, env->line_count);\n\ts += sprintf(s, \"%s\", color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG));\n\ts += sprintf(s, \" Col: \");\n\ts += sprintf(s, \"%s\", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\ts += sprintf(s, \"%d \", env->col_no);\n\n\treturn out;\n}\n\n/**\n * Draw the status bar\n *\n * The status bar shows the name of the file, whether it has modifications,\n * and (in the future) what syntax highlighting mode is enabled.\n *\n * The right side of the status bar shows the line number and column.\n */\nvoid redraw_statusbar(void) {\n\tif (global_config.hide_statusbar) return;\n\tif (!global_config.has_terminal) return;\n\tif (!env) return;\n\t/* Hide cursor while rendering */\n\thide_cursor();\n\n\t/* Move cursor to the status bar line (second from bottom */\n\tplace_cursor(1, global_config.term_height - 1);\n\n\t/* Set background colors for status line */\n\tpaint_line(COLOR_STATUS_BG);\n\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\n\n\t/* Pre-render the right hand side of the status bar */\n\tchar right_hand[1024] = {0};\n\tint right_width = statusbar_build_right(right_hand);\n\n\tchar status_bits[2048] = {0}; /* Sane maximum */\n\tsize_t filled = 0;\n\n\tint remaining_width = global_config.term_width - right_width;\n\n#define ADD(...) do { statusbar_append_status(&remaining_width, &filled, status_bits, __VA_ARGS__); } while (0)\n\tif (env->syntax) {\n\t\tADD(\"%s\",env->syntax->name);\n\t}\n\n\t/* Print file status indicators */\n\tif (env->modified) {\n\t\tADD(\"+\");\n\t}\n\n\tif (env->readonly) {\n\t\tADD(\"ro\");\n\t}\n\n\tif (env->crnl) {\n\t\tADD(\"crnl\");\n\t}\n\n\tif (env->tabs) {\n\t\tADD(\"tabs\");\n\t} else {\n\t\tADD(\"spaces=%d\", env->tabstop);\n\t}\n\n\tif (global_config.yanks) {\n\t\tADD(\"y:%ld\", global_config.yank_count);\n\t}\n\n\tif (env->indent) {\n\t\tADD(\"indent\");\n\t}\n\n\tif (global_config.smart_complete) {\n\t\tADD(\"complete\");\n\t}\n\n\tif (global_config.background_task) {\n\t\tADD(\"working\");\n\t}\n\n#undef ADD\n\n\tuint8_t * file_name = (uint8_t *)(env->file_name ? env->file_name : \"[No Name]\");\n\tint file_name_width = display_width_of_string((char*)file_name);\n\n\tif (remaining_width > 3) {\n\t\tint is_chopped = 0;\n\t\twhile (remaining_width < file_name_width + 3) {\n\t\t\tis_chopped = 1;\n\t\t\tif ((*file_name & 0xc0) == 0xc0) { /* First byte of a multibyte character */\n\t\t\t\tfile_name++;\n\t\t\t\twhile ((*file_name & 0xc0) == 0x80) file_name++;\n\t\t\t} else {\n\t\t\t\tfile_name++;\n\t\t\t}\n\t\t\tfile_name_width = display_width_of_string((char*)file_name);\n\t\t}\n\t\tif (is_chopped) {\n\t\t\tset_colors(COLOR_ALT_FG, COLOR_STATUS_BG);\n\t\t\tprintf(\"<\");\n\t\t}\n\t\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\t\tprintf(\"%s \", file_name);\n\t}\n\n\tprintf(\"%s\", status_bits);\n\n\t/* Clear the rest of the status bar */\n\tclear_to_end();\n\n\t/* Move the cursor appropriately to draw it */\n\tplace_cursor(global_config.term_width - right_width, global_config.term_height - 1);\n\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\tprintf(\"%s\",right_hand);\n}\n\n/**\n * Redraw the navigation numbers on the right side of the command line\n */\nvoid redraw_nav_buffer(void) {\n\tif (!global_config.has_terminal) return;\n\tif (nav_buffer) {\n\t\tstore_cursor();\n\t\tplace_cursor(global_config.term_width - nav_buffer - 2, global_config.term_height);\n\t\tprintf(\"%s\", nav_buf);\n\t\tclear_to_end();\n\t\trestore_cursor();\n\t}\n}\n\n/**\n * Draw the command line\n *\n * The command line either has input from the user (:quit, :!make, etc.)\n * or shows the INSERT (or VISUAL in the future) mode name.\n */\nvoid redraw_commandline(void) {\n\tif (!global_config.has_terminal) return;\n\tif (!env) return;\n\n\t/* Hide cursor while rendering */\n\thide_cursor();\n\n\t/* Move cursor to the last line */\n\tplace_cursor(1, global_config.term_height);\n\n\t/* Set background color */\n\tpaint_line(COLOR_BG);\n\tset_colors(COLOR_FG, COLOR_BG);\n\n\t/* If we are in an edit mode, note that. */\n\tif (env->mode == MODE_INSERT) {\n\t\tset_bold();\n\t\tprintf(\"-- INSERT --\");\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_LINE_SELECTION) {\n\t\tset_bold();\n\t\tprintf(\"-- LINE SELECTION -- (%d:%d)\",\n\t\t\t(env->start_line < env->line_no) ? env->start_line : env->line_no,\n\t\t\t(env->start_line < env->line_no) ? env->line_no : env->start_line\n\t\t);\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_COL_SELECTION) {\n\t\tset_bold();\n\t\tprintf(\"-- COL SELECTION -- (%d:%d %d)\",\n\t\t\t(env->start_line < env->line_no) ? env->start_line : env->line_no,\n\t\t\t(env->start_line < env->line_no) ? env->line_no : env->start_line,\n\t\t\t(env->sel_col)\n\t\t);\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_COL_INSERT) {\n\t\tset_bold();\n\t\tprintf(\"-- COL INSERT -- (%d:%d %d)\",\n\t\t\t(env->start_line < env->line_no) ? env->start_line : env->line_no,\n\t\t\t(env->start_line < env->line_no) ? env->line_no : env->start_line,\n\t\t\t(env->sel_col)\n\t\t);\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_REPLACE) {\n\t\tset_bold();\n\t\tprintf(\"-- REPLACE --\");\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_CHAR_SELECTION) {\n\t\tset_bold();\n\t\tprintf(\"-- CHAR SELECTION -- \");\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else if (env->mode == MODE_DIRECTORY_BROWSE) {\n\t\tset_bold();\n\t\tprintf(\"-- DIRECTORY BROWSE --\");\n\t\tclear_to_end();\n\t\tunset_bold();\n\t} else {\n\t\tclear_to_end();\n\t}\n\n\tredraw_nav_buffer();\n}\n\n/**\n * Draw a message on the command line.\n */\nvoid render_commandline_message(char * message, ...) {\n\t/* varargs setup */\n\tva_list args;\n\tva_start(args, message);\n\n\t/* Hide cursor while rendering */\n\thide_cursor();\n\n\t/* Move cursor to the last line */\n\tplace_cursor(1, global_config.term_height);\n\n\t/* Set background color */\n\tpaint_line(COLOR_BG);\n\tset_colors(COLOR_FG, COLOR_BG);\n\n\tvprintf(message, args);\n\tva_end(args);\n\n\t/* Clear the rest of the status bar */\n\tclear_to_end();\n\n\tredraw_nav_buffer();\n}\n\nBIM_ACTION(redraw_all, 0,\n\t\"Repaint the screen.\"\n,void) {\n\tif (!env) return;\n\tredraw_tabbar();\n\tredraw_text();\n\tif (left_buffer) {\n\t\tredraw_alt_buffer(left_buffer == env ? right_buffer : left_buffer);\n\t}\n\tredraw_statusbar();\n\tredraw_commandline();\n\tif (global_config.overlay_mode == OVERLAY_MODE_COMMAND ||\n\t    global_config.overlay_mode == OVERLAY_MODE_SEARCH ||\n\t    global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) {\n\t\trender_command_input_buffer();\n\t}\n}\n\nvoid pause_for_key(void) {\n\tint c;\n\twhile ((c = bim_getch())== -1);\n\tbim_unget(c);\n\tredraw_all();\n}\n\n/**\n * Redraw all screen elements except the other split view.\n */\nvoid redraw_most(void) {\n\tredraw_tabbar();\n\tredraw_text();\n\tredraw_statusbar();\n\tredraw_commandline();\n}\n\n/**\n * Disable screen splitting.\n */\nvoid unsplit(void) {\n\tif (left_buffer) {\n\t\tleft_buffer->left = 0;\n\t\tleft_buffer->width = global_config.term_width;\n\t}\n\tif (right_buffer) {\n\t\tright_buffer->left = 0;\n\t\tright_buffer->width = global_config.term_width;\n\t}\n\tleft_buffer = NULL;\n\tright_buffer = NULL;\n\n\tredraw_all();\n}\n\n/**\n * Update the terminal title bar\n */\nvoid update_title(void) {\n\tif (!global_config.can_title) return;\n\n\tchar cwd[1024] = {'/',0};\n\tgetcwd(cwd, 1024);\n\n\tfor (int i = 1; i < 3; ++i) {\n\t\tprintf(\"\\033]%d;%s%s (%s) - Bim\\007\", i, env->file_name ? env->file_name : \"[No Name]\", env->modified ? \" +\" : \"\", cwd);\n\t}\n}\n\n/**\n * Mark this buffer as modified and\n * redraw the status and tabbar if needed.\n */\nvoid set_modified(void) {\n\t/* If it was already marked modified, no need to do anything */\n\tif (env->modified) return;\n\n\t/* Mark as modified */\n\tenv->modified = 1;\n\n\t/* Redraw some things */\n\tupdate_title();\n\tredraw_tabbar();\n\tredraw_statusbar();\n}\n\n/**\n * Draw a message on the status line\n */\nvoid render_status_message(char * message, ...) {\n\tif (!env) return; /* Don't print when there's no active environment; this usually means a bimrc command tried to print something */\n\t/* varargs setup */\n\tva_list args;\n\tva_start(args, message);\n\n\t/* Hide cursor while rendering */\n\thide_cursor();\n\n\t/* Move cursor to the status bar line (second from bottom */\n\tplace_cursor(1, global_config.term_height - 1);\n\n\t/* Set background colors for status line */\n\tpaint_line(COLOR_STATUS_BG);\n\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\n\t/* Process format string */\n\tvprintf(message, args);\n\tva_end(args);\n\n\t/* Clear the rest of the status bar */\n\tclear_to_end();\n}\n\n/**\n * Draw an error message to the command line.\n */\nvoid render_error(char * message, ...) {\n\t/* varargs setup */\n\tva_list args;\n\tva_start(args, message);\n\n\tif (env) {\n\t\t/* Hide cursor while rendering */\n\t\thide_cursor();\n\n\t\t/* Move cursor to the command line */\n\t\tplace_cursor(1, global_config.term_height);\n\n\t\t/* Set appropriate error message colors */\n\t\tset_colors(COLOR_ERROR_FG, COLOR_ERROR_BG);\n\n\t\t/* Draw the message */\n\t\tvprintf(message, args);\n\t\tva_end(args);\n\t\tglobal_config.had_error = 1;\n\t} else {\n\t\tprintf(\"bim: error during startup: \");\n\t\tvprintf(message, args);\n\t\tva_end(args);\n\t\tprintf(\"\\n\");\n\t}\n\n}\n\nint is_paren(int c) {\n\tuint32_t * p = global_config.paren_pairs;\n\twhile (*p) {\n\t\tif ((uint32_t)c == *p) return 1;\n\t\tp++;\n\t}\n\treturn 0;\n}\n\n#define _rehighlight_parens() do { \\\n\tif (i < 0 || i >= env->line_count) break; \\\n\tfor (int j = 0; j < env->lines[i]->actual; ++j) { \\\n\t\tif (i == line-1 && j == col-1) { \\\n\t\t\tenv->lines[line-1]->text[col-1].flags |= FLAG_SELECT; \\\n\t\t\tcontinue; \\\n\t\t} else { \\\n\t\t\tenv->lines[i]->text[j].flags &= (~FLAG_SELECT); \\\n\t\t} \\\n\t} \\\n\tredraw_line(i); \\\n} while (0)\n\n/**\n * If the config option is enabled, find the matching\n * paren character and highlight it with the SELECT\n * colors, clearing out other SELECT values. As we\n * co-opt the SELECT flag, don't do this in selection\n * modes - only in normal and insert modes.\n */\nvoid highlight_matching_paren(void) {\n\tif (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION) return;\n\tif (!global_config.highlight_parens) return;\n\tint line = -1, col = -1;\n\tif (env->line_no <= env->line_count && env->col_no <= env->lines[env->line_no-1]->actual &&\n\t\tis_paren(env->lines[env->line_no-1]->text[env->col_no-1].codepoint)) {\n\t\tfind_matching_paren(&line, &col, 1);\n\t} else if (env->line_no <= env->line_count && env->col_no > 1 && is_paren(env->lines[env->line_no-1]->text[env->col_no-2].codepoint)) {\n\t\tfind_matching_paren(&line, &col, 2);\n\t}\n\tif (env->highlighting_paren == -1 && line == -1) return;\n\tif (env->highlighting_paren > 0) {\n\t\tint i = env->highlighting_paren - 1;\n\t\t_rehighlight_parens();\n\t}\n\tif (env->highlighting_paren != line && line != -1) {\n\t\tint i = line - 1;\n\t\t_rehighlight_parens();\n\t}\n\tenv->highlighting_paren = line;\n}\n\n/**\n * Recalculate syntax for the matched paren.\n * Useful when entering selection modes.\n */\nvoid unhighlight_matching_paren(void) {\n\tif (env->highlighting_paren > 0 && env->highlighting_paren <= env->line_count) {\n\t\tfor (int i = 0; i < env->line_count; i++) {\n\t\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\t\tenv->lines[i]->text[j].flags &= ~(FLAG_SELECT);\n\t\t\t}\n\t\t}\n\t\tenv->highlighting_paren = -1;\n\t}\n}\n\n/**\n * Move the cursor to the appropriate location based\n * on where it is in the text region.\n *\n * This does some additional math to set the text\n * region horizontal offset.\n */\nvoid place_cursor_actual(void) {\n\n\t/* Invalid positions */\n\tif (env->line_no < 1) env->line_no = 1;\n\tif (env->col_no  < 1) env->col_no  = 1;\n\n\t/* Account for the left hand gutter */\n\tint num_size = num_width() + gutter_width();\n\tint x = num_size + 1 - env->coffset;\n\n\t/* Determine where the cursor is physically */\n\tfor (int i = 0; i < env->col_no - 1; ++i) {\n\t\tchar_t * c = &env->lines[env->line_no-1]->text[i];\n\t\tx += c->display_width;\n\t}\n\n\t/* y is a bit easier to calculate */\n\tint y = env->line_no - env->offset + 1;\n\n\tint needs_redraw = 0;\n\n\twhile (y < 2 + global_config.cursor_padding && env->offset > 0) {\n\t\ty++;\n\t\tenv->offset--;\n\t\tneeds_redraw = 1;\n\t}\n\n\twhile (y > 1 + global_config.term_height - global_config.bottom_size - global_config.cursor_padding - global_config.tabs_visible) {\n\t\ty--;\n\t\tenv->offset++;\n\t\tneeds_redraw = 1;\n\t}\n\n\tif (needs_redraw) {\n\t\tredraw_text();\n\t\tredraw_tabbar();\n\t\tredraw_statusbar();\n\t\tredraw_commandline();\n\t}\n\n\t/* If the cursor has gone off screen to the right... */\n\tif (x > env->width - 1) {\n\t\t/* Adjust the offset appropriately to scroll horizontally */\n\t\tint diff = x - (env->width - 1);\n\t\tenv->coffset += diff;\n\t\tx -= diff;\n\t\tredraw_text();\n\t}\n\n\t/* Same for scrolling horizontally to the left */\n\tif (x < num_size + 1) {\n\t\tint diff = (num_size + 1) - x;\n\t\tenv->coffset -= diff;\n\t\tx += diff;\n\t\tredraw_text();\n\t}\n\n\thighlight_matching_paren();\n\trecalculate_current_line();\n\n\t/* Move the actual terminal cursor */\n\tplace_cursor(x+env->left,y - !global_config.tabs_visible);\n\n\t/* Show the cursor */\n\tshow_cursor();\n}\n\n/**\n * If the screen is split, update the split sizes based\n * on the new terminal width and the user's split_percent setting.\n */\nvoid update_split_size(void) {\n\tif (!left_buffer) return;\n\tif (left_buffer == right_buffer) {\n\t\tif (left_buffer->left == 0) {\n\t\t\tleft_buffer->width = global_config.term_width * global_config.split_percent / 100;\n\t\t} else {\n\t\t\tright_buffer->left = global_config.term_width * global_config.split_percent / 100;\n\t\t\tright_buffer->width = global_config.term_width - right_buffer->left;\n\t\t}\n\t\treturn;\n\t}\n\tleft_buffer->left = 0;\n\tleft_buffer->width = global_config.term_width * global_config.split_percent / 100;\n\tright_buffer->left = left_buffer->width;\n\tright_buffer->width = global_config.term_width - left_buffer->width;\n}\n\n/**\n * Update screen size\n */\nvoid update_screen_size(void) {\n\tstruct winsize w;\n\tioctl(STDOUT_FILENO, TIOCGWINSZ, &w);\n\tglobal_config.term_width = w.ws_col;\n\tglobal_config.term_height = w.ws_row;\n\tif (env) {\n\t\tif (left_buffer) {\n\t\t\tupdate_split_size();\n\t\t} else if (env != left_buffer && env != right_buffer) {\n\t\t\tenv->width = w.ws_col;\n\t\t}\n\t}\n\tfor (int i = 0; i < buffers_len; ++i) {\n\t\tif (buffers[i] != left_buffer && buffers[i] != right_buffer) {\n\t\t\tbuffers[i]->width = w.ws_col;\n\t\t}\n\t}\n}\n\n/**\n * Handle terminal size changes\n */\nvoid SIGWINCH_handler(int sig) {\n\t(void)sig;\n\tupdate_screen_size();\n\tredraw_all();\n\n\tsignal(SIGWINCH, SIGWINCH_handler);\n}\n\n/**\n * Handle suspend\n */\nvoid SIGTSTP_handler(int sig) {\n\t(void)sig;\n\tmouse_disable();\n\tset_buffered();\n\treset();\n\tclear_screen();\n\tshow_cursor();\n\tunset_bracketed_paste();\n\tunset_alternate_screen();\n\tfflush(stdout);\n\n\tsignal(SIGTSTP, SIG_DFL);\n\traise(SIGTSTP);\n}\n\nvoid SIGCONT_handler(int sig) {\n\t(void)sig;\n\tset_alternate_screen();\n\tset_bracketed_paste();\n\tset_unbuffered();\n\tupdate_screen_size();\n\tmouse_enable();\n\tredraw_all();\n\tupdate_title();\n\tsignal(SIGCONT, SIGCONT_handler);\n\tsignal(SIGTSTP, SIGTSTP_handler);\n}\n\nvoid SIGINT_handler(int sig) {\n\tkrk_currentThread.flags |= KRK_THREAD_SIGNALLED;\n\tsignal(SIGINT,   SIGINT_handler);\n}\n\nvoid try_to_center(void) {\n\tint half_a_screen = (global_config.term_height - 3) / 2;\n\tif (half_a_screen < env->line_no) {\n\t\tenv->offset = env->line_no - half_a_screen;\n\t} else {\n\t\tenv->offset = 0;\n\t}\n}\n\nBIM_ACTION(suspend, 0,\n\t\"Suspend bim and the rest of the job it was run in.\"\n,void) {\n\tkill(0, SIGTSTP);\n}\n\n/**\n * Move the cursor to a specific line.\n */\nBIM_ACTION(goto_line, ARG_IS_CUSTOM,\n\t\"Jump to the requested line.\"\n,int line) {\n\n\tif (line == -1) line = env->line_count;\n\n\t/* Respect file bounds */\n\tif (line < 1) line = 1;\n\tif (line > env->line_count) line = env->line_count;\n\n\t/* Move the cursor / text region offsets */\n\tenv->coffset = 0;\n\tenv->line_no = line;\n\tenv->col_no  = 1;\n\n\tif (!env->loading) {\n\t\tif (line > env->offset && line < env->offset + global_config.term_height - global_config.bottom_size) {\n\t\t\tplace_cursor_actual();\n\t\t} else {\n\t\t\ttry_to_center();\n\t\t}\n\t\tredraw_most();\n\t} else {\n\t\ttry_to_center();\n\t}\n}\n\n\n/**\n * Process (part of) a file and add it to a buffer.\n */\nvoid add_buffer(uint8_t * buf, int size) {\n\tfor (int i = 0; i < size; ++i) {\n\t\tif (!decode(&state, &codepoint_r, buf[i])) {\n\t\t\tuint32_t c = codepoint_r;\n\t\t\tif (c == '\\n') {\n\t\t\t\tif (!env->crnl && env->lines[env->line_no-1]->actual && env->lines[env->line_no-1]->text[env->lines[env->line_no-1]->actual-1].codepoint == '\\r') {\n\t\t\t\t\tenv->lines[env->line_no-1]->actual--;\n\t\t\t\t\tenv->crnl = 1;\n\t\t\t\t}\n\t\t\t\tenv->lines = add_line(env->lines, env->line_no);\n\t\t\t\tenv->col_no = 1;\n\t\t\t\tenv->line_no += 1;\n\t\t\t} else if (env->crnl && c == '\\r') {\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tchar_t _c;\n\t\t\t\t_c.codepoint = (uint32_t)c;\n\t\t\t\t_c.flags = 0;\n\t\t\t\t_c.display_width = codepoint_width((wchar_t)c);\n\t\t\t\tline_t * line  = env->lines[env->line_no - 1];\n\t\t\t\tline_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no-1);\n\t\t\t\tif (line != nline) {\n\t\t\t\t\tenv->lines[env->line_no - 1] = nline;\n\t\t\t\t}\n\t\t\t\tenv->col_no += 1;\n\t\t\t}\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t}\n}\n\n/**\n * Add a raw string to a buffer. Convenience wrapper\n * for add_buffer for nil-terminated strings.\n */\nvoid add_string(char * string) {\n\tadd_buffer((uint8_t*)string,strlen(string));\n}\n\nint str_ends_with(const char * haystack, const char * needle) {\n\tint i = strlen(haystack);\n\tint j = strlen(needle);\n\n\tdo {\n\t\tif (haystack[i] != needle[j]) return 0;\n\t\tif (j == 0) return 1;\n\t\tif (i == 0) return 0;\n\t\ti--;\n\t\tj--;\n\t} while (1);\n}\n\n/**\n * Find a syntax highlighter for the given filename.\n */\nstruct syntax_definition * match_syntax(char * file) {\n\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\tfor (char ** ext = s->ext; *ext; ++ext) {\n\t\t\tif (str_ends_with(file, *ext)) return s;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/**\n * Set the syntax configuration by the name of the syntax highlighter.\n */\nvoid set_syntax_by_name(const char * name) {\n\tif (!strcmp(name,\"none\")) {\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tenv->lines[i]->istate = -1;\n\t\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\t\tenv->lines[i]->text[j].flags &= (3 << 5);\n\t\t\t}\n\t\t}\n\t\tenv->syntax = NULL;\n\t\tredraw_all();\n\t\treturn;\n\t}\n\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\tif (!strcmp(name,s->name)) {\n\t\t\tenv->syntax = s;\n\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\tenv->lines[i]->istate = -1;\n\t\t\t}\n\t\t\tschedule_complete_recalc();\n\t\t\tredraw_all();\n\t\t\treturn;\n\t\t}\n\t}\n\trender_error(\"unrecognized syntax type\");\n}\n\n\n/**\n * Check if a string is all numbers.\n */\nint is_all_numbers(const char * c) {\n\twhile (*c) {\n\t\tif (!isdigit(*c)) return 0;\n\t\tc++;\n\t}\n\treturn 1;\n}\n\nstruct file_listing {\n\tint type;\n\tchar * filename;\n};\n\nint sort_files(const void * a, const void * b) {\n\tstruct file_listing * _a = (struct file_listing *)a;\n\tstruct file_listing * _b = (struct file_listing *)b;\n\n\tif (_a->type == _b->type) {\n\t\treturn strcmp(_a->filename, _b->filename);\n\t} else {\n\t\treturn _a->type - _b->type;\n\t}\n}\n\nvoid read_directory_into_buffer(char * file) {\n\tDIR * dirp = opendir(file);\n\tif (!dirp) {\n\t\tenv->loading = 0;\n\t\treturn;\n\t}\n\n\tadd_string(\"# Directory listing for `\");\n\tadd_string(file);\n\tadd_string(\"`\\n\");\n\n\t/* Flexible array to hold directory contents */\n\tint available = 32;\n\tint count = 0;\n\tstruct file_listing * files = calloc(sizeof(struct file_listing), available);\n\n\t/* Read directory */\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent) {\n\t\tstruct stat statbuf;\n\t\tchar * tmp = malloc(strlen(file) + 1 + strlen(ent->d_name) + 1);\n\t\tsnprintf(tmp, strlen(file) + 1 + strlen(ent->d_name) + 1, \"%s/%s\", file, ent->d_name);\n\t\tstat(tmp, &statbuf);\n\t\tint type = (S_ISDIR(statbuf.st_mode)) ? 'd' : 'f';\n\t\tif (count + 1 == available) {\n\t\t\tavailable *= 2; \\\n\t\t\tfiles = realloc(files, sizeof(struct file_listing) * available); \\\n\t\t} \\\n\t\tfiles[count].type = type;\n\t\tfiles[count].filename = strdup(ent->d_name);\n\t\tcount++;\n\t\tent = readdir(dirp);\n\t\tfree(tmp);\n\t}\n\tclosedir(dirp);\n\n\t/* Sort directory entries */\n\tqsort(files, count, sizeof(struct file_listing), sort_files);\n\n\tfor (int i = 0; i < count; ++i) {\n\t\tadd_string(files[i].type == 'd' ? \"d\" : \"f\");\n\t\tadd_string(\" \");\n\t\tadd_string(files[i].filename);\n\t\tadd_string(\"\\n\");\n\n\t\tfree(files[i].filename);\n\t}\n\n\tfree(files);\n\n\tenv->file_name = strdup(file);\n\tenv->syntax = find_syntax_calculator(\"dirent\");\n\tschedule_complete_recalc();\n\tenv->readonly = 1;\n\tenv->loading = 0;\n\tenv->mode = MODE_DIRECTORY_BROWSE;\n\tenv->line_no = 1;\n\tredraw_all();\n}\n\nBIM_ACTION(open_file_from_line, 0,\n\t\"When browsing a directory, open the file under the cursor.\"\n,void) {\n\tif (env->lines[env->line_no-1]->actual < 1) return;\n\tif (env->lines[env->line_no-1]->text[0].codepoint != 'd' &&\n\t    env->lines[env->line_no-1]->text[0].codepoint != 'f') return;\n\t/* Collect file name */\n\tchar * tmp = malloc(strlen(env->file_name) + 1 + env->lines[env->line_no-1]->actual * 7); /* Should be enough */\n\tmemset(tmp, 0, strlen(env->file_name) + 1 + env->lines[env->line_no-1]->actual * 7);\n\tchar * t = tmp;\n\t/* Start by copying the filename */\n\tt += sprintf(t, \"%s/\", env->file_name);\n\t/* Start from character 2 to skip d/f and space */\n\tfor (int i = 2; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\tt += to_eight(env->lines[env->line_no-1]->text[i].codepoint, t);\n\t}\n\t*t = '\\0';\n\t/* Normalize */\n\tchar tmp_path[PATH_MAX+1];\n\tif (!realpath(tmp, tmp_path)) {\n\t\tfree(tmp);\n\t\treturn;\n\t}\n\tfree(tmp);\n\t/* Open file */\n\tbuffer_t * old_buffer = env;\n\topen_file(tmp_path);\n\tbuffer_close(old_buffer);\n\tupdate_title();\n\tredraw_all();\n}\n\nint line_matches(line_t * line, char * string) {\n\tuint32_t c = 0, state = 0;\n\tint i = 0;\n\twhile (*string) {\n\t\tif (!decode(&state, &c, *string)) {\n\t\t\tif (i >= line->actual) return 0;\n\t\t\tif (line->text[i].codepoint != c) return 0;\n\t\t\tstring++;\n\t\t\ti++;\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t}\n\treturn 1;\n}\n\nvoid run_onload(buffer_t * env) {\n\tKrkValue onLoad;\n\tif (krk_tableGet_fast(&krk_currentThread.module->fields, S(\"onload\"), &onLoad)) {\n\t\tkrk_push(onLoad);\n\n\t\tint args = 0;\n\t\tif (env->file_name) {\n\t\t\tkrk_push(OBJECT_VAL(S(\"filename\")));\n\t\t\tkrk_push(OBJECT_VAL(krk_copyString(env->file_name,strlen(env->file_name))));\n\t\t\targs++;\n\t\t}\n\t\tif (env->syntax) {\n\t\t\tkrk_push(OBJECT_VAL(S(\"lang\")));\n\t\t\tkrk_push(OBJECT_VAL(krk_copyString(env->syntax->name,strlen(env->syntax->name))));\n\t\t\targs++;\n\n\t\t\tif (env->syntax->krkClass) {\n\t\t\t\tkrk_push(OBJECT_VAL(S(\"highlighter\")));\n\t\t\t\tkrk_push(OBJECT_VAL(env->syntax->krkClass));\n\t\t\t\targs++;\n\t\t\t}\n\t\t}\n\n\t\tif (IS_CLOSURE(onLoad) && AS_CLOSURE(onLoad)->function->requiredArgs == 1) {\n\t\t\t/* Use old ABI if the function accepts one required argument. */\n\t\t\tkrk_push(krk_callNativeOnStack(args * 2, &krk_currentThread.stackTop[-args*2], 0, krk_dict_of));\n\t\t\tif (args) {\n\t\t\t\tkrk_swap(args * 2);\n\t\t\t\twhile (args--) {\n\t\t\t\t\tkrk_pop();\n\t\t\t\t\tkrk_pop();\n\t\t\t\t}\n\t\t\t}\n\t\t\tkrk_callStack(1);\n\t\t} else {\n\t\t\t/* Otherwise use the new API where the function can accepts keyword args. */\n\t\t\tkrk_push(KWARGS_VAL(args));\n\t\t\tkrk_callStack(args * 2 + 1);\n\t\t}\n\t\tkrk_resetStack();\n\t}\n}\n\nstatic void render_syntax_async(background_task_t * task) {\n\tbuffer_t * old_env = env;\n\tenv = task->env;\n\tint line_no = task->_private_i;\n\n\tif (env->line_count && line_no < env->line_count) {\n\t\tint tmp = env->loading;\n\t\tenv->loading = 1;\n\t\trecalculate_syntax(env->lines[line_no], line_no);\n\t\tenv->loading = tmp;\n\t\tif (env == old_env) {\n\t\t\tredraw_line(line_no);\n\t\t}\n\t}\n\tenv = old_env;\n}\n\nstatic void schedule_complete_recalc(void) {\n\tif (env->line_count < 1000) {\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\trecalculate_syntax(env->lines[i], i);\n\t\t}\n\t\treturn;\n\t}\n\n\t/* TODO see if there's already a redraw scheduled */\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\tbackground_task_t * task = malloc(sizeof(background_task_t));\n\t\ttask->env  = env;\n\t\ttask->_private_i = i;\n\t\ttask->func = render_syntax_async;\n\t\ttask->next = NULL;\n\t\tif (global_config.tail_task) {\n\t\t\tglobal_config.tail_task->next = task;\n\t\t}\n\t\tglobal_config.tail_task = task;\n\t\tif (!global_config.background_task) {\n\t\t\tglobal_config.background_task = task;\n\t\t}\n\t}\n\tredraw_statusbar();\n}\n\n/**\n * Create a new buffer from a file.\n */\nvoid open_file(char * file) {\n\tenv = buffer_new();\n\tenv->width = global_config.term_width;\n\tenv->left = 0;\n\tenv->loading = 1;\n\n\tsetup_buffer(env);\n\n\tFILE * f;\n\tint init_line = -1;\n\n\tif (!strcmp(file,\"-\")) {\n\t\t/**\n\t\t * Read file from stdin. stderr provides terminal input.\n\t\t */\n\t\tif (isatty(STDIN_FILENO)) {\n\t\t\tif (buffers_len == 1) {\n\t\t\t\tquit(\"stdin is a terminal and you tried to open -; not letting you do that\");\n\t\t\t}\n\t\t\tclose_buffer();\n\t\t\trender_error(\"stdin is a terminal and you tried to open -; not letting you do that\");\n\t\t\treturn;\n\t\t}\n\t\tf = stdin;\n\t\tenv->modified = 1;\n\t} else {\n\t\tchar * l = strrchr(file, ':');\n\t\tif (l && is_all_numbers(l+1)) {\n\t\t\t*l = '\\0';\n\t\t\tl++;\n\t\t\tinit_line = atoi(l);\n\t\t}\n\n\t\tchar * _file = file;\n\n\t\tif (file[0] == '~') {\n\t\t\tchar * home = getenv(\"HOME\");\n\t\t\tif (home) {\n\t\t\t\t_file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */\n\t\t\t\tsprintf(_file, \"%s%s\", home, file+1);\n\t\t\t}\n\t\t}\n\n\t\tif (file_is_open(_file)) {\n\t\t\tif (file != _file) free(_file);\n\t\t\tclose_buffer();\n\t\t\treturn;\n\t\t}\n\n\t\tstruct stat statbuf;\n\t\tif (!stat(_file, &statbuf) && S_ISDIR(statbuf.st_mode)) {\n\t\t\tread_directory_into_buffer(_file);\n\t\t\tif (file != _file) free(_file);\n\t\t\treturn;\n\t\t}\n\t\tf = fopen(_file, \"r\");\n\t\tif (file != _file) free(_file);\n\t\tif (!f && errno != ENOENT) {\n\t\t\trender_error(\"%s: %s\", file, strerror(errno));\n\t\t\tpause_for_key();\n\t\t\tclose_buffer();\n\t\t\treturn;\n\t\t}\n\t\tenv->file_name = strdup(file);\n\t}\n\n\tif (!f) {\n\t\tif (global_config.highlight_on_open) {\n\t\t\tenv->syntax = match_syntax(file);\n\t\t}\n\t\tenv->loading = 0;\n\t\tif (global_config.go_to_line) {\n\t\t\tgoto_line(1);\n\t\t}\n\t\tif (env->syntax && env->syntax->prefers_spaces) {\n\t\t\tenv->tabs = 0;\n\t\t}\n\t\tupdate_biminfo(env, 1);\n\t\trun_onload(env);\n\t\treturn;\n\t}\n\n\tuint8_t buf[BLOCK_SIZE];\n\n\tstate = 0;\n\n\twhile (!feof(f) && !ferror(f)) {\n\t\tsize_t r = fread(buf, 1, BLOCK_SIZE, f);\n\t\tadd_buffer(buf, r);\n\t}\n\n\tif (ferror(f)) {\n\t\tenv->loading = 0;\n\t\treturn;\n\t}\n\n\tif (env->line_no && env->lines[env->line_no-1] && env->lines[env->line_no-1]->actual == 0) {\n\t\t/* Remove blank line from end */\n\t\tenv->lines = remove_line(env->lines, env->line_no-1);\n\t}\n\n\tif (global_config.highlight_on_open) {\n\t\tenv->syntax = match_syntax(file);\n\t\tif (!env->syntax) {\n\t\t\tif (line_matches(env->lines[0], \"<?xml\")) set_syntax_by_name(\"xml\");\n\t\t\telse if (line_matches(env->lines[0], \"<!doctype\")) set_syntax_by_name(\"xml\");\n\t\t\telse if (line_matches(env->lines[0], \"#!/usr/bin/env bash\")) set_syntax_by_name(\"bash\");\n\t\t\telse if (line_matches(env->lines[0], \"#!/bin/bash\")) set_syntax_by_name(\"bash\");\n\t\t\telse if (line_matches(env->lines[0], \"#!/bin/sh\")) set_syntax_by_name(\"bash\");\n\t\t\telse if (line_matches(env->lines[0], \"#!/usr/bin/env python\")) set_syntax_by_name(\"py\");\n\t\t\telse if (line_matches(env->lines[0], \"#!/usr/bin/env groovy\")) set_syntax_by_name(\"groovy\");\n\t\t}\n\t\tif (!env->syntax && global_config.syntax_fallback) {\n\t\t\tset_syntax_by_name(global_config.syntax_fallback);\n\t\t}\n\t\tschedule_complete_recalc();\n\t}\n\n\t/* Try to automatically figure out tabs vs. spaces */\n\tint tabs = 0, spaces = 0;\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\tif (env->lines[i]->actual > 1) { /* Make sure line has at least some text on it */\n\t\t\tif (env->lines[i]->text[0].codepoint == '\\t') tabs++;\n\t\t\tif (env->lines[i]->text[0].codepoint == ' ' &&\n\t\t\t\tenv->lines[i]->text[1].codepoint == ' ') /* Ignore spaces at the start of asterisky C comments */\n\t\t\t\tspaces++;\n\t\t}\n\t}\n\tif (spaces > tabs) {\n\t\tenv->tabs = 0;\n\t} else if (spaces == tabs && env->syntax) {\n\t\tenv->tabs = env->syntax->prefers_spaces;\n\t}\n\n\tif (spaces > tabs) {\n\t\tint one = 0, two = 0, three = 0, four = 0; /* If you use more than that, I don't like you. */\n\t\tint lastCount = 0;\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tif (env->lines[i]->actual > 1 && !line_is_comment(env->lines[i])) {\n\t\t\t\t/* Count spaces at beginning */\n\t\t\t\tint c = 0, diff = 0;\n\t\t\t\twhile (c < env->lines[i]->actual && env->lines[i]->text[c].codepoint == ' ') c++;\n\t\t\t\tif (c > lastCount) {\n\t\t\t\t\tdiff = c - lastCount;\n\t\t\t\t} else if (c < lastCount) {\n\t\t\t\t\tdiff = lastCount - c;\n\t\t\t\t}\n\t\t\t\tif (diff == 1) one++;\n\t\t\t\tif (diff == 2) two++;\n\t\t\t\tif (diff == 3) three++;\n\t\t\t\tif (diff == 4) four++;\n\t\t\t\tlastCount = c;\n\t\t\t}\n\t\t}\n\t\tif (four > three && four > two && four > one) {\n\t\t\tenv->tabstop = 4;\n\t\t} else if (three > two && three > one) {\n\t\t\tenv->tabstop = 3;\n\t\t} else if (two > one) {\n\t\t\tenv->tabstop = 2;\n\t\t} else {\n\t\t\tenv->tabstop = 1;\n\t\t}\n\t}\n\n\tenv->loading = 0;\n\n\tif (global_config.check_git) {\n\t\tenv->checkgitstatusonwrite = 1;\n\t\tgit_examine(file);\n\t}\n\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\trecalculate_tabs(env->lines[i]);\n\t}\n\n\tif (global_config.go_to_line) {\n\t\tif (init_line != -1) {\n\t\t\tgoto_line(init_line);\n\t\t} else {\n\t\t\tenv->line_no = 1;\n\t\t\tenv->col_no = 1;\n\t\t\tfetch_from_biminfo(env);\n\t\t\tplace_cursor_actual();\n\t\t\tredraw_all();\n\t\t\tset_preferred_column();\n\t\t}\n\t}\n\n\tupdate_biminfo(env, 1);\n\n\tfclose(f);\n\n\trun_onload(env);\n}\n\n/**\n * Clean up the terminal and exit the editor.\n */\nvoid quit(const char * message) {\n\tmouse_disable();\n\tset_buffered();\n\treset();\n\tclear_screen();\n\tshow_cursor();\n\tunset_bracketed_paste();\n\tunset_alternate_screen();\n\tkrk_freeVM();\n\tif (message) {\n\t\tfprintf(stdout, \"%s\\n\", message);\n\t}\n\texit(0);\n}\n\n/**\n * Try to quit, but don't continue if there are\n * modified buffers open.\n */\nvoid try_quit(void) {\n\tfor (int i = 0; i < buffers_len; i++ ) {\n\t\tbuffer_t * _env = buffers[i];\n\t\tif (_env->modified) {\n\t\t\tif (_env->file_name) {\n\t\t\t\trender_error(\"Modifications made to file `%s` in tab %d. Aborting.\", _env->file_name, i+1);\n\t\t\t} else {\n\t\t\t\trender_error(\"Unsaved new file in tab %d. Aborting.\", i+1);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\t/* Close all buffers */\n\twhile (buffers_len) {\n\t\tbuffer_close(buffers[0]);\n\t}\n\tquit(NULL);\n}\n\n/**\n * Switch to the previous buffer\n */\nBIM_ACTION(previous_tab, 0,\n\t\"Switch the previous tab\"\n,void) {\n\tbuffer_t * last = NULL;\n\tfor (int i = 0; i < buffers_len; i++) {\n\t\tbuffer_t * _env = buffers[i];\n\t\tif (_env == env) {\n\t\t\tif (last) {\n\t\t\t\t/* Wrap around */\n\t\t\t\tenv = last;\n\t\t\t\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\t\t\t\tredraw_all();\n\t\t\t\tupdate_title();\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tenv = buffers[buffers_len-1];\n\t\t\t\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\t\t\t\tredraw_all();\n\t\t\t\tupdate_title();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tlast = _env;\n\t}\n}\n\n/**\n * Switch to the next buffer\n */\nBIM_ACTION(next_tab, 0,\n\t\"Switch to the next tab\"\n,void) {\n\tfor (int i = 0; i < buffers_len; i++) {\n\t\tbuffer_t * _env = buffers[i];\n\t\tif (_env == env) {\n\t\t\tif (i != buffers_len - 1) {\n\t\t\t\tenv = buffers[i+1];\n\t\t\t\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\t\t\t\tredraw_all();\n\t\t\t\tupdate_title();\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t/* Wrap around */\n\t\t\t\tenv = buffers[0];\n\t\t\t\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\t\t\t\tredraw_all();\n\t\t\t\tupdate_title();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Check for modified lines in a file by examining `git diff` output.\n * This can be enabled globally in bimrc or per environment with the 'git' option.\n */\nint git_examine(char * filename) {\n\tif (env->modified) return 1;\n\tint fds[2];\n\tpipe(fds);\n\tint child = fork();\n\tif (child == 0) {\n\t\tFILE * dev_null = fopen(\"/dev/null\",\"w\");\n\t\tclose(fds[0]);\n\t\tdup2(fds[1], STDOUT_FILENO);\n\t\tdup2(fileno(dev_null), STDERR_FILENO);\n\t\tchar * args[] = {\"git\",\"--no-pager\",\"diff\",\"-U0\",\"--no-color\",\"--\",filename,NULL};\n\t\texit(execvp(\"git\",args));\n\t} else if (child < 0) {\n\t\treturn 1;\n\t}\n\n\tclose(fds[1]);\n\tFILE * f = fdopen(fds[0],\"r\");\n\n\tint line_offset = 0;\n\twhile (!feof(f)) {\n\t\tint c = fgetc(f);\n\t\tif (c < 0) break;\n\n\t\tif (c == '@' && line_offset == 0) {\n\t\t\t/* Read line offset, count */\n\t\t\tif (fgetc(f) == '@' && fgetc(f) == ' ' && fgetc(f) == '-') {\n\t\t\t\t/* This algorithm is borrowed from Kakoune and only requires us to parse the @@ line */\n\t\t\t\tint from_line = 0;\n\t\t\t\tint from_count = 0;\n\t\t\t\tint to_line = 0;\n\t\t\t\tint to_count = 0;\n\t\t\t\tfscanf(f,\"%d\",&from_line);\n\t\t\t\tif (fgetc(f) == ',') {\n\t\t\t\t\tfscanf(f,\"%d\",&from_count);\n\t\t\t\t} else {\n\t\t\t\t\tfrom_count = 1;\n\t\t\t\t}\n\t\t\t\tfscanf(f,\"%d\",&to_line);\n\t\t\t\tif (fgetc(f) == ',') {\n\t\t\t\t\tfscanf(f,\"%d\",&to_count);\n\t\t\t\t} else {\n\t\t\t\t\tto_count = 1;\n\t\t\t\t}\n\n\t\t\t\tif (to_line > env->line_count) continue;\n\n\t\t\t\tif (from_count == 0 && to_count > 0) {\n\t\t\t\t\t/* No -, all + means all of to_count is green */\n\t\t\t\t\tfor (int i = 0; i < to_count; ++i) {\n\t\t\t\t\t\tenv->lines[to_line+i-1]->rev_status = 1; /* Green */\n\t\t\t\t\t}\n\t\t\t\t} else if (from_count > 0 && to_count == 0) {\n\t\t\t\t\t/*\n\t\t\t\t\t * No +, all - means we have a deletion. We mark the next line such that it has a red bar at the top\n\t\t\t\t\t * Note that to_line is one lower than the affected line, so we don't need to mess with indexes.\n\t\t\t\t\t */\n\t\t\t\t\tif (to_line >= env->line_count) continue;\n\t\t\t\t\tenv->lines[to_line]->rev_status = 4; /* Red */\n\t\t\t\t} else if (from_count > 0 && from_count == to_count) {\n\t\t\t\t\t/* from = to, all modified */\n\t\t\t\t\tfor (int i = 0; i < to_count; ++i) {\n\t\t\t\t\t\tenv->lines[to_line+i-1]->rev_status = 3; /* Blue */\n\t\t\t\t\t}\n\t\t\t\t} else if (from_count > 0 && from_count < to_count) {\n\t\t\t\t\t/* from < to, some modified, some added */\n\t\t\t\t\tfor (int i = 0; i < from_count; ++i) {\n\t\t\t\t\t\tenv->lines[to_line+i-1]->rev_status = 3; /* Blue */\n\t\t\t\t\t}\n\t\t\t\t\tfor (int i = from_count; i < to_count; ++i) {\n\t\t\t\t\t\tenv->lines[to_line+i-1]->rev_status = 1; /* Green */\n\t\t\t\t\t}\n\t\t\t\t} else if (to_count > 0 && from_count > to_count) {\n\t\t\t\t\t/* from > to, we deleted but also modified some lines */\n\t\t\t\t\tenv->lines[to_line-1]->rev_status = 5; /* Red + Blue */\n\t\t\t\t\tfor (int i = 1; i < to_count; ++i) {\n\t\t\t\t\t\tenv->lines[to_line+i-1]->rev_status = 3; /* Blue */\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (c == '\\n') {\n\t\t\tline_offset = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tline_offset++;\n\t}\n\n\tfclose(f);\n\twaitpid(-1,NULL,WNOHANG);\n\treturn 0;\n}\n\n/**\n * Write file contents to FILE\n */\nvoid output_file(buffer_t * env, FILE * f) {\n\tint i, j;\n\tfor (i = 0; i < env->line_count; ++i) {\n\t\tline_t * line = env->lines[i];\n\t\tline->rev_status = 0;\n\t\tfor (j = 0; j < line->actual; j++) {\n\t\t\tchar_t c = line->text[j];\n\t\t\tif (c.codepoint == 0) {\n\t\t\t\tchar buf[1] = {0};\n\t\t\t\tfwrite(buf, 1, 1, f);\n\t\t\t} else {\n\t\t\t\tchar tmp[8] = {0};\n\t\t\t\tint i = to_eight(c.codepoint, tmp);\n\t\t\t\tfwrite(tmp, i, 1, f);\n\t\t\t}\n\t\t}\n\t\tif (env->crnl) fputc('\\r', f);\n\t\tfputc('\\n', f);\n\t}\n}\n\n/**\n * Write active buffer to file\n */\nvoid write_file(char * file) {\n\tif (!file) {\n\t\trender_error(\"Need a file to write to.\");\n\t\treturn;\n\t}\n\n\tchar * _file = file;\n\n\tif (file[0] == '~') {\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (home) {\n\t\t\t_file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */\n\t\t\tsprintf(_file, \"%s%s\", home, file+1);\n\t\t}\n\t}\n\n\n\tFILE * f = fopen(_file, \"w+\");\n\tif (file != _file) free(_file);\n\n\tif (!f) {\n\t\trender_error(\"Failed to open file for writing.\");\n\t\treturn;\n\t}\n\n\t/* Go through each line and convert it back to UTF-8 */\n\toutput_file(env, f);\n\n\tfclose(f);\n\n\t/* Mark it no longer modified */\n\tenv->modified = 0;\n\tenv->last_save_history = env->history;\n\n\t/* If there was no file name set, set one */\n\tif (!env->file_name) {\n\t\tenv->file_name = malloc(strlen(file) + 1);\n\t\tmemcpy(env->file_name, file, strlen(file) + 1);\n\t}\n\n\tif (env->checkgitstatusonwrite) {\n\t\tgit_examine(file);\n\t}\n\n\tupdate_title();\n\tredraw_all();\n}\n\n/**\n * Close the active buffer\n */\nvoid close_buffer(void) {\n\tbuffer_t * previous_env = env;\n\tbuffer_t * new_env = buffer_close(env);\n\tif (new_env == previous_env) {\n\t\t/* ?? Failed to close buffer */\n\t\trender_error(\"lolwat\");\n\t}\n\tif (left_buffer && env == left_buffer) {\n\t\tleft_buffer = NULL;\n\t\tright_buffer->left = 0;\n\t\tright_buffer->width = global_config.term_width;\n\t\tright_buffer = NULL;\n\t} else if (left_buffer && env == right_buffer) {\n\t\tright_buffer = NULL;\n\t\tleft_buffer->left = 0;\n\t\tleft_buffer->width = global_config.term_width;\n\t\tleft_buffer = NULL;\n\t}\n\t/* No more buffers, exit */\n\tif (!new_env) {\n\t\tquit(NULL);\n\t}\n\n\t/* Set the new active buffer */\n\tenv = new_env;\n\n\t/* Redraw the screen */\n\tredraw_all();\n\tupdate_title();\n}\n\n/**\n * Set the visual column the cursor should attempt to keep\n * when moving up and down based on where the cursor currently is.\n * This should happen whenever the user intentionally changes\n * the cursor's horizontal positioning, such as with left/right\n * arrow keys, word-move, search, mouse, etc.\n */\nvoid set_preferred_column(void) {\n\tint c = 0;\n\tfor (int i = 0; i < env->lines[env->line_no-1]->actual && i < env->col_no-1; ++i) {\n\t\tc += env->lines[env->line_no-1]->text[i].display_width;\n\t}\n\tenv->preferred_column = c;\n}\n\nBIM_ACTION(cursor_down, 0,\n\t\"Move the cursor one line down.\"\n,void) {\n\t/* If this isn't already the last line... */\n\tif (env->line_no < env->line_count) {\n\n\t\t/* Move the cursor down */\n\t\tenv->line_no += 1;\n\n\t\t/* Try to place the cursor horizontally at the preferred column */\n\t\tint _x = 0;\n\t\tfor (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\t\tchar_t * c = &env->lines[env->line_no-1]->text[i];\n\t\t\t_x += c->display_width;\n\t\t\tenv->col_no = i+1;\n\t\t\tif (_x > env->preferred_column) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (env->mode == MODE_INSERT && _x <= env->preferred_column) {\n\t\t\tenv->col_no = env->lines[env->line_no-1]->actual + 1;\n\t\t}\n\n\t\t/*\n\t\t * If the horizontal cursor position exceeds the width the new line,\n\t\t * then move the cursor left to the extent of the new line.\n\t\t *\n\t\t * If we're in insert mode, we can go one cell beyond the end of the line\n\t\t */\n\t\tif (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) {\n\t\t\tenv->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT);\n\t\t\tif (env->col_no == 0) env->col_no = 1;\n\t\t}\n\n\t\tif (env->loading) return;\n\n\t\t/*\n\t\t * If the screen was scrolled horizontally, unscroll it;\n\t\t * if it will be scrolled on this line as well, that will\n\t\t * be handled by place_cursor_actual\n\t\t */\n\t\tint redraw = 0;\n\t\tif (env->coffset != 0) {\n\t\t\tenv->coffset = 0;\n\t\t\tredraw = 1;\n\t\t}\n\n\t\t/* If we've scrolled past the bottom of the screen, scroll the screen */\n\t\tif (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - global_config.tabs_visible - global_config.cursor_padding) {\n\t\t\tenv->offset += 1;\n\n\t\t\t/* Tell terminal to scroll */\n\t\t\tif (global_config.can_scroll && !left_buffer) {\n\t\t\t\tif (!global_config.can_insert) {\n\t\t\t\t\tshift_up(1);\n\t\t\t\t\tredraw_tabbar();\n\t\t\t\t} else {\n\t\t\t\t\tdelete_lines_at(global_config.tabs_visible ? 2 : 1, 1);\n\t\t\t\t}\n\n\t\t\t\t/* A new line appears on screen at the bottom, draw it */\n\t\t\t\tint l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible;\n\t\t\t\tif (env->offset + l < env->line_count + 1) {\n\t\t\t\t\tredraw_line(env->offset + l-1);\n\t\t\t\t} else {\n\t\t\t\t\tdraw_excess_line(l - 1);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tredraw_text();\n\t\t\t}\n\t\t\tredraw_statusbar();\n\t\t\tredraw_commandline();\n\t\t\tplace_cursor_actual();\n\t\t\treturn;\n\t\t} else if (redraw) {\n\t\t\t/* Otherwise, if we need to redraw because of coffset change, do that */\n\t\t\tredraw_text();\n\t\t}\n\n\t\tset_history_break();\n\n\t\t/* Update the status bar */\n\t\tredraw_statusbar();\n\n\t\t/* Place the terminal cursor again */\n\t\tplace_cursor_actual();\n\t}\n}\n\nBIM_ACTION(cursor_up, 0,\n\t\"Move the cursor up one line.\"\n,void) {\n\t/* If this isn't the first line already */\n\tif (env->line_no > 1) {\n\n\t\t/* Move the cursor down */\n\t\tenv->line_no -= 1;\n\n\t\t/* Try to place the cursor horizontally at the preferred column */\n\t\tint _x = 0;\n\t\tfor (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\t\tchar_t * c = &env->lines[env->line_no-1]->text[i];\n\t\t\t_x += c->display_width;\n\t\t\tenv->col_no = i+1;\n\t\t\tif (_x > env->preferred_column) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (env->mode == MODE_INSERT && _x <= env->preferred_column) {\n\t\t\tenv->col_no = env->lines[env->line_no-1]->actual + 1;\n\t\t}\n\n\t\t/*\n\t\t * If the horizontal cursor position exceeds the width the new line,\n\t\t * then move the cursor left to the extent of the new line.\n\t\t *\n\t\t * If we're in insert mode, we can go one cell beyond the end of the line\n\t\t */\n\t\tif (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) {\n\t\t\tenv->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT);\n\t\t\tif (env->col_no == 0) env->col_no = 1;\n\t\t}\n\n\t\tif (env->loading) return;\n\n\t\t/*\n\t\t * If the screen was scrolled horizontally, unscroll it;\n\t\t * if it will be scrolled on this line as well, that will\n\t\t * be handled by place_cursor_actual\n\t\t */\n\t\tint redraw = 0;\n\t\tif (env->coffset != 0) {\n\t\t\tenv->coffset = 0;\n\t\t\tredraw = 1;\n\t\t}\n\n\t\tint e = (env->offset == 0) ? env->offset : env->offset + global_config.cursor_padding;\n\t\tif (env->line_no <= e) {\n\t\t\tenv->offset -= 1;\n\n\t\t\t/* Tell terminal to scroll */\n\t\t\tif (global_config.can_scroll && !left_buffer) {\n\t\t\t\tif (!global_config.can_insert) {\n\t\t\t\t\tshift_down(1);\n\t\t\t\t\tredraw_tabbar();\n\t\t\t\t} else {\n\t\t\t\t\tinsert_lines_at(global_config.tabs_visible ? 2 : 1, 1);\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * The line at the top of the screen should always be real\n\t\t\t\t * so we can just call redraw_line here\n\t\t\t\t */\n\t\t\t\tredraw_line(env->offset);\n\t\t\t} else {\n\t\t\t\tredraw_tabbar();\n\t\t\t\tredraw_text();\n\t\t\t}\n\t\t\tredraw_statusbar();\n\t\t\tredraw_commandline();\n\t\t\tplace_cursor_actual();\n\t\t\treturn;\n\t\t} else if (redraw) {\n\t\t\t/* Otherwise, if we need to redraw because of coffset change, do that */\n\t\t\tredraw_text();\n\t\t}\n\n\t\tset_history_break();\n\n\t\t/* Update the status bar */\n\t\tredraw_statusbar();\n\n\t\t/* Place the terminal cursor again */\n\t\tplace_cursor_actual();\n\t}\n}\n\nBIM_ACTION(cursor_left, 0,\n\t\"Move the cursor one character to the left.\"\n,void) {\n\tif (env->col_no > 1) {\n\t\tenv->col_no -= 1;\n\n\t\t/* Update the status bar */\n\t\tredraw_statusbar();\n\n\t\t/* Place the terminal cursor again */\n\t\tplace_cursor_actual();\n\t}\n\tset_history_break();\n\tset_preferred_column();\n}\n\nBIM_ACTION(cursor_right, 0,\n\t\"Move the cursor one character to the right.\"\n,void) {\n\n\t/* If this isn't already the rightmost column we can reach on this line in this mode... */\n\tif (env->col_no < env->lines[env->line_no-1]->actual + !!(env->mode == MODE_INSERT)) {\n\t\tenv->col_no += 1;\n\n\t\t/* Update the status bar */\n\t\tredraw_statusbar();\n\n\t\t/* Place the terminal cursor again */\n\t\tplace_cursor_actual();\n\t}\n\tset_history_break();\n\tset_preferred_column();\n}\n\nBIM_ACTION(cursor_home, 0,\n\t\"Move the cursor to the beginning of the line.\"\n,void) {\n\tenv->col_no = 1;\n\tset_history_break();\n\tset_preferred_column();\n\n\t/* Update the status bar */\n\tredraw_statusbar();\n\n\t/* Place the terminal cursor again */\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(cursor_end, 0,\n\t\"Move the cursor to the end of the line, or past the end in insert mode.\"\n,void) {\n\tenv->col_no = env->lines[env->line_no-1]->actual+!!(env->mode == MODE_INSERT);\n\tset_history_break();\n\tset_preferred_column();\n\n\t/* Update the status bar */\n\tredraw_statusbar();\n\n\t/* Place the terminal cursor again */\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(leave_insert, 0,\n\t\"Leave insert modes and return to normal mode.\"\n,void) {\n\tif (env->col_no > env->lines[env->line_no-1]->actual) {\n\t\tenv->col_no = env->lines[env->line_no-1]->actual;\n\t\tif (env->col_no == 0) env->col_no = 1;\n\t\tset_preferred_column();\n\t}\n\t\tset_history_break();\n\t\tenv->mode = MODE_NORMAL;\n\t\tredraw_commandline();\n}\n\nstruct MatchQualifier {\n\tint (*matchFunc)(struct MatchQualifier*,uint32_t,int);\n\tunion {\n\t\tuint32_t matchChar;\n\t\tstruct {\n\t\t\tuint32_t * start;\n\t\t\tuint32_t * end;\n\t\t} matchSquares;\n\t};\n};\n\n/**\n * Helper for handling smart case sensitivity.\n */\nint match_char(struct MatchQualifier * self, uint32_t b, int mode) {\n\tif (mode == 0) {\n\t\treturn self->matchChar == b;\n\t} else if (mode == 1) {\n\t\treturn tolower(self->matchChar) == tolower(b);\n\t}\n\treturn 0;\n}\n\nint match_squares(struct MatchQualifier * self, uint32_t c, int mode) {\n\tuint32_t * start = self->matchSquares.start;\n\tuint32_t * end = self->matchSquares.end;\n\tuint32_t * t = start;\n\tint good = 1;\n\tif (*t == '^') { t++; good = 0; }\n\twhile (t != end) {\n\t\tuint32_t test = *t++;\n\t\tif (test == '\\\\' && *t && strchr(\"\\\\]\",*t)) {\n\t\t\ttest = *t++;\n\t\t} else if (test == '\\\\' && *t == 't') {\n\t\t\ttest = '\\t'; t++;\n\t\t}\n\n\t\tif (*t == '-') {\n\t\t\tt++;\n\t\t\tif (t == end) return 0;\n\t\t\tuint32_t right = *t++;\n\t\t\tif (right == '\\\\' && *t && strchr(\"\\\\]\",*t)) {\n\t\t\t\tright = *t++;\n\t\t\t} else if (right == '\\\\' && *t == 't') {\n\t\t\t\tright = '\\t'; t++;\n\t\t\t}\n\t\t\tif (mode ? (tolower(c) >= tolower(test) && tolower(c) <= tolower(right)) : (c >= test && c <= right)) return good;\n\t\t} else {\n\t\t\tif (mode ? (tolower(c) == tolower(test)) : (c == test)) return good;\n\t\t}\n\t}\n\treturn !good;\n}\n\nint match_dot(struct MatchQualifier * self, uint32_t c, int mode) {\n\treturn 1;\n}\n\nstruct BackRef {\n\tint start;\n\tint len;\n\tuint32_t * copy;\n};\n\n#define MAX_REFS 10\nint regex_matches(line_t * line, int j, uint32_t * needle, int ignorecase, int *len, uint32_t **needleout, int refindex, struct BackRef * refs) {\n\tint k = j;\n\tuint32_t * match = needle;\n\tif (*match == '^') {\n\t\tif (j != 0) return 0;\n\t\tmatch++;\n\t}\n\twhile (k < line->actual + 1) {\n\t\tif (needleout && *match == ')') {\n\t\t\t*needleout = match + 1;\n\t\t\tif (len) *len = k - j;\n\t\t\treturn 1;\n\t\t}\n\t\tif (*match == '\\0') {\n\t\t\tif (needleout) return 0;\n\t\t\tif (len) *len = k - j;\n\t\t\treturn 1;\n\t\t}\n\t\tif (*match == '$') {\n\t\t\tif (k != line->actual) return 0;\n\t\t\tmatch++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (k == line->actual) break;\n\n\t\tstruct MatchQualifier matcher = {match_char, .matchChar=*match};\n\t\tif (*match == '.') {\n\t\t\tmatcher.matchFunc = match_dot;\n\t\t\tmatch++;\n\t\t} else if (*match == '\\\\' && strchr(\"$^/\\\\.[?]*+()\",match[1]) != NULL) {\n\t\t\tmatcher.matchChar = match[1];\n\t\t\tmatch += 2;\n\t\t} else if (*match == '\\\\' && match[1] == 't') {\n\t\t\tmatcher.matchChar = '\\t';\n\t\t\tmatch += 2;\n\t\t} else if (*match == '[') {\n\t\t\tuint32_t * s = match+1;\n\t\t\tuint32_t * e = s;\n\t\t\twhile (*e && *e != ']') {\n\t\t\t\tif (*e == '\\\\' && e[1] == ']') e++;\n\t\t\t\te++;\n\t\t\t}\n\t\t\tif (!*e) break; /* fail match on unterminated [] sequence */\n\t\t\tmatch = e + 1;\n\t\t\tmatcher.matchFunc = match_squares;\n\t\t\tmatcher.matchSquares.start = s;\n\t\t\tmatcher.matchSquares.end = e;\n\t\t} else if (*match == '(') {\n\t\t\tmatch++;\n\t\t\tint _len;\n\t\t\tuint32_t * newmatch;\n\t\t\tif (!regex_matches(line, k, match, ignorecase, &_len, &newmatch, 0, NULL)) break;\n\t\t\tmatch = newmatch;\n\t\t\tif (refindex && refindex < MAX_REFS) {\n\t\t\t\trefs[refindex].start = k;\n\t\t\t\trefs[refindex].len = _len;\n\t\t\t\trefindex++;\n\t\t\t}\n\t\t\tk += _len;\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tmatch++;\n\t\t}\n\t\tif (*match == '?') {\n\t\t\t/* Optional */\n\t\t\tmatch++;\n\t\t\tif (matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) {\n\t\t\t\tint _len;\n\t\t\t\tif (regex_matches(line,k+1,match,ignorecase,&_len, needleout, refindex, refs)) {\n\t\t\t\t\tif (len) *len = _len + k + 1 - j;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t} else if (*match == '+' || *match == '*') {\n\t\t\t/* Must match at least one */\n\t\t\tif (*match == '+') {\n\t\t\t\tif (!matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) break;\n\t\t\t\tk++;\n\t\t\t}\n\t\t\t/* Match any */\n\t\t\tmatch++;\n\t\t\tint greedy = 1;\n\t\t\tif (*match == '?') {\n\t\t\t\t/* non-greedy */\n\t\t\t\tmatch++;\n\t\t\t\tgreedy = 0;\n\t\t\t}\n\n\t\t\tint _j = k;\n\t\t\twhile (_j < line->actual + 1) {\n\t\t\t\tint _len;\n\t\t\t\tif (!greedy && regex_matches(line, _j, match, ignorecase, &_len, needleout, refindex, refs)) {\n\t\t\t\t\tif (len) *len = _len + _j - j;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tif (_j < line->actual && !matcher.matchFunc(&matcher, line->text[_j].codepoint, ignorecase)) break;\n\t\t\t\t_j++;\n\t\t\t}\n\t\t\tif (!greedy) return 0;\n\t\t\twhile (_j >= k) {\n\t\t\t\tint _len;\n\t\t\t\tif (regex_matches(line, _j, match, ignorecase, &_len, needleout, refindex, refs)) {\n\t\t\t\t\tif (len) *len = _len + _j - j;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\t_j--;\n\t\t\t}\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tif (!matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) break;\n\t\t\tk++;\n\t\t}\n\t}\n\treturn 0;\n}\n\nint subsearch_matches(line_t * line, int j, uint32_t * needle, int ignorecase, int *len) {\n\treturn regex_matches(line, j, needle, ignorecase, len, NULL, 0, NULL);\n}\n\n/**\n * Replace text on a given line with other text.\n */\nvoid perform_replacement(int line_no, uint32_t * needle, uint32_t * replacement, int col, int ignorecase, int *out_col, int *line_out) {\n\tline_t * line = env->lines[line_no-1];\n\tint j = col;\n\twhile (j < line->actual + 1) {\n\t\tint match_len;\n\t\tstruct BackRef refs[MAX_REFS] = {0};\n\t\tif (regex_matches(line,j,needle,ignorecase,&match_len,NULL,1,refs)) {\n\t\t\trefs[0].start = j;\n\t\t\trefs[0].len = match_len;\n\t\t\tfor (int i = 0; i < MAX_REFS; ++i) {\n\t\t\t\trefs[i].copy = malloc(sizeof(uint32_t) * refs[i].len);\n\t\t\t\tfor (int j = 0; j < refs[i].len; ++j) {\n\t\t\t\t\trefs[i].copy[j] = line->text[j+refs[i].start].codepoint;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* Perform replacement */\n\t\t\tfor (int i = 0; i < match_len; ++i) {\n\t\t\t\tline_delete(line, j+1, line_no-1);\n\t\t\t}\n\t\t\tint t = 0;\n\t\t\tfor (uint32_t * r = replacement; *r; ++r) {\n\t\t\t\tuint32_t rep = *r;\n\t\t\t\tchar_t _c;\n\t\t\t\t_c.flags = 0;\n\t\t\t\tline_t * nline = line;\n\t\t\t\tif (*r == '\\\\' && r[1] == 't') {\n\t\t\t\t\trep = '\\t';\n\t\t\t\t\t++r;\n\t\t\t\t} else if (*r == '\\\\' && (r[1] == '\\\\')) {\n\t\t\t\t\trep = r[1];\n\t\t\t\t\t++r;\n\t\t\t\t} else if (*r == '\\\\' && (r[1] >= '0' && r[1] <= '9')) {\n\t\t\t\t\tint i = r[1] - '0';\n\t\t\t\t\t++r;\n\t\t\t\t\tnline = line;\n\t\t\t\t\tfor (int k = 0; k < refs[i].len; ++k) {\n\t\t\t\t\t\t_c.codepoint = refs[i].copy[k];\n\t\t\t\t\t\t_c.display_width = codepoint_width(refs[i].copy[k]);\n\t\t\t\t\t\tnline = line_insert(nline, _c, j + t + k, line_no -1);\n\t\t\t\t\t}\n\t\t\t\t\tt += refs[i].len;\n\t\t\t\t\trep = 0;\n\t\t\t\t} else if (*r == '\\\\' && (r[1] == 'n')) {\n\t\t\t\t\t++r;\n\t\t\t\t\tenv->lines = split_line(env->lines, line_no - 1, j + t);\n\t\t\t\t\tline_no++;\n\t\t\t\t\tline = env->lines[line_no-1];\n\t\t\t\t\tj = 0;\n\t\t\t\t\tt = 0;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (rep) {\n\t\t\t\t\t_c.codepoint = rep;\n\t\t\t\t\t_c.display_width = codepoint_width(rep);\n\t\t\t\t\tnline = line_insert(nline, _c, j + t, line_no -1);\n\t\t\t\t\tt++;\n\t\t\t\t}\n\t\t\t\tif (line != nline) {\n\t\t\t\t\tenv->lines[line_no-1] = nline;\n\t\t\t\t\tline = nline;\n\t\t\t\t}\n\t\t\t}\n\t\t\t*out_col = j + t;\n\t\t\t*line_out = line_no;\n\t\t\tset_modified();\n\n\t\t\tfor (int i = 0; i < MAX_REFS; ++i) {\n\t\t\t\tfree(refs[i].copy);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\t\tj++;\n\t}\n\t*out_col = -1;\n}\n\n#define COMMAND_HISTORY_MAX 255\nunsigned char * command_history[COMMAND_HISTORY_MAX] = {NULL};\nunsigned char * search_history[COMMAND_HISTORY_MAX] = {NULL};\n\n/**\n * Add a command to the history. If that command was\n * already in history, it is moved to the front of the list;\n * otherwise, the whole list is shifted backwards and\n * overflow is freed up.\n */\nvoid insert_command_history(unsigned char ** which_history, char * cmd) {\n\t/* See if this is already in the history. */\n\tsize_t amount_to_shift = COMMAND_HISTORY_MAX - 1;\n\tfor (int i = 0; i < COMMAND_HISTORY_MAX && which_history[i]; ++i) {\n\t\tif (!strcmp((char*)which_history[i], cmd)) {\n\t\t\tfree(which_history[i]);\n\t\t\tamount_to_shift = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Remove last entry that will roll off the stack */\n\tif (amount_to_shift == COMMAND_HISTORY_MAX - 1) {\n\t\tif (which_history[COMMAND_HISTORY_MAX-1]) free(which_history[COMMAND_HISTORY_MAX-1]);\n\t}\n\n\t/* Roll the history */\n\tmemmove(&which_history[1], &which_history[0], sizeof(char *) * (amount_to_shift));\n\n\twhich_history[0] = (unsigned char*)strdup(cmd);\n}\n\nstatic uint32_t term_colors[] = {\n 0x000000, 0xcc0000, 0x3e9a06, 0xc4a000, 0x3465a4, 0x75507b, 0x06989a, 0xeeeeec, 0x555753, 0xef2929, 0x8ae234, 0xfce94f, 0x729fcf, 0xad7fa8, 0x34e2e2,\n 0xFFFFFF, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f,\n 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7,\n 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f,\n 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7,\n 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f,\n 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7,\n 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,\n 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7,\n 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f,\n 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7,\n 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f,\n 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7,\n 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f,\n 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7,\n 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,\n 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4,\n 0xeeeeee,\n};\n\n/**\n * Convert a color setting from terminal format\n * to a hexadecimal color code and add it to the current\n * buffer. This is used for HTML conversion, but could\n * possibly be used for other purposes.\n */\nstatic void html_convert_color(const char * color_string) {\n\tchar tmp[100];\n\tif (!strncmp(color_string,\"2;\",2)) {\n\t\t/* 24-bit color */\n\t\tint red, green, blue;\n\t\tsscanf(color_string+2,\"%d;%d;%d\",&red,&green,&blue);\n\t\tsprintf(tmp, \"#%02x%02x%02x;\", red,green,blue);\n\t} else if (!strncmp(color_string,\"5;\",2)) {\n\t\t/* 256 colors; needs lookup table */\n\t\tint index;\n\t\tsscanf(color_string+2,\"%d\",&index);\n\t\tsprintf(tmp,\"#%06x;\", (unsigned int)term_colors[(index >= 0 && index <= 255) ? index : 0]);\n\t} else {\n\t\t/* 16 colors; needs lookup table */\n\t\tint index;\n\t\tuint32_t color;\n\t\tsscanf(color_string+1,\"%d\",&index);\n\t\tif (index >= 10) {\n\t\t\tindex -= 10;\n\t\t\tcolor = term_colors[index+8];\n\t\t} else if (index == 9) {\n\t\t\tcolor = term_colors[0];\n\t\t} else {\n\t\t\tcolor = term_colors[index];\n\t\t}\n\t\tsprintf(tmp,\"#%06x;\", (unsigned int)color);\n\t}\n\tadd_string(tmp);\n\tchar * italic = strstr(color_string,\";3\");\n\tif (italic && italic[2] == '\\0') {\n\t\tadd_string(\" font-style: oblique;\");\n\t}\n\tchar * bold = strstr(color_string,\";1\");\n\tif (bold && bold[2] == '\\0') {\n\t\tadd_string(\" font-weight: bold;\");\n\t}\n\tchar * underline = strstr(color_string,\";4\");\n\tif (underline && underline[2] == '\\0') {\n\t\tadd_string(\" font-decoration: underline;\");\n\t}\n}\n\nint convert_to_html(void) {\n\tbuffer_t * old = env;\n\tenv = buffer_new();\n\tsetup_buffer(env);\n\tenv->loading = 1;\n\n\tadd_string(\"<!doctype html>\\n\");\n\tadd_string(\"<html>\\n\");\n\tadd_string(\"\t<head>\\n\");\n\tadd_string(\"\t\t<meta charset=\\\"UTF-8\\\">\\n\");\n\tif (old->file_name) {\n\t\tadd_string(\"\t\t<title>\");\n\t\tadd_string(file_basename(old->file_name));\n\t\tadd_string(\"</title>\\n\");\n\t}\n\tadd_string(\"\t\t<style>\\n\");\n\tadd_string(\"\t\t\tbody {\\n\");\n\tadd_string(\"\t\t\t\tmargin: 0;\\n\");\n\tadd_string(\"\t\t\t\t-webkit-text-size-adjust: none;\\n\");\n\tadd_string(\"\t\t\t\tcounter-reset: line-no;\\n\");\n\tadd_string(\"\t\t\t\tbackground-color: \");\n\t/* Convert color */\n\thtml_convert_color(COLOR_BG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t\t.ul { text-decoration: underline; }\\n\");\n\tfor (int i = 0; i < 15; ++i) {\n\t\t/* For each of the relevant flags... */\n\t\tchar tmp[20];\n\t\tsprintf(tmp,\"\t\t\t.s%d { color: \", i);\n\t\tadd_string(tmp);\n\t\t/* These are special */\n\t\tif (i == FLAG_NOTICE) {\n\t\t\thtml_convert_color(COLOR_SEARCH_FG);\n\t\t\tadd_string(\" background-color: \");\n\t\t\thtml_convert_color(COLOR_SEARCH_BG);\n\t\t} else if (i == FLAG_ERROR) {\n\t\t\thtml_convert_color(COLOR_ERROR_FG);\n\t\t\tadd_string(\" background-color: \");\n\t\t\thtml_convert_color(COLOR_ERROR_BG);\n\t\t} else {\n\t\t\thtml_convert_color(flag_to_color(i));\n\t\t}\n\t\tadd_string(\"}\\n\");\n\t}\n\tadd_string(\"\t\t\tpre {\\n\");\n\tadd_string(\"\t\t\t\tmargin: 0;\\n\");\n\tadd_string(\"\t\t\t\twhite-space: pre-wrap;\\n\");\n\tadd_string(\"\t\t\t\tfont-family: \\\"DejaVu Sans Mono\\\", Courier, monospace;\\n\");\n\tadd_string(\"\t\t\t\tfont-size: 10pt;\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t\tpre>span {\\n\");\n\tadd_string(\"\t\t\t\tdisplay: inline-block;\\n\");\n\tadd_string(\"\t\t\t\twidth: 100%;\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t\tpre>span>a::before {\\n\");\n\tadd_string(\"\t\t\t\tcounter-increment: line-no;\\n\");\n\tadd_string(\"\t\t\t\tcontent: counter(line-no);\\n\");\n\tadd_string(\"\t\t\t\tpadding-right: 1em;\\n\");\n\tadd_string(\"\t\t\t\twidth: 3em;\\n\");\n\tadd_string(\"\t\t\t\tdisplay: inline-block;\\n\");\n\tadd_string(\"\t\t\t\ttext-align: right;\\n\");\n\tadd_string(\"\t\t\t\tbackground-color: \");\n\thtml_convert_color(COLOR_NUMBER_BG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t\tcolor: \");\n\thtml_convert_color(COLOR_NUMBER_FG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t\tpre>span:target {\\n\");\n\tadd_string(\"\t\t\t\tbackground-color: \");\n\thtml_convert_color(COLOR_ALT_BG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t\tpre>span:target>a::before {\\n\");\n\tadd_string(\"\t\t\t\tbackground-color: \");\n\thtml_convert_color(COLOR_NUMBER_FG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t\tcolor: \");\n\thtml_convert_color(COLOR_NUMBER_BG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tfor (int i = 1; i <= env->tabstop; ++i) {\n\t\tchar tmp[20];\n\t\tsprintf(tmp, \".tab%d\", i);\n\t\tadd_string(\"\t\t\t\");\n\t\tadd_string(tmp);\n\t\tadd_string(\">span {\\n\");\n\t\tadd_string(\"\t\t\t\tdisplay: inline-block;\\n\");\n\t\tadd_string(\"\t\t\t\toverflow: hidden;\\n\");\n\t\tadd_string(\"\t\t\t\twidth: 0;\\n\");\n\t\tadd_string(\"\t\t\t\theight: 0;\\n\");\n\t\tadd_string(\"\t\t\t}\\n\");\n\t\tadd_string(\"\t\t\t\");\n\t\tadd_string(tmp);\n\t\tadd_string(\"::after {\\n\");\n\t\tadd_string(\"\t\t\t\tcontent: '»\");\n\t\tfor (int j = 1; j < i; ++j) {\n\t\t\tadd_string(\"·\");\n\t\t}\n\t\tadd_string(\"';\\n\");\n\t\tadd_string(\"\t\t\t\tbackground-color: \");\n\t\thtml_convert_color(COLOR_ALT_BG);\n\t\tadd_string(\"\\n\");\n\t\tadd_string(\"\t\t\t\tcolor: \");\n\t\thtml_convert_color(COLOR_ALT_FG);\n\t\tadd_string(\"\\n\");\n\t\tadd_string(\"\t\t\t}\\n\");\n\t}\n\tadd_string(\"\t\t\t.space {\\n\");\n\tadd_string(\"\t\t\t\tborder-left: 1px solid \");\n\thtml_convert_color(COLOR_ALT_FG);\n\tadd_string(\"\\n\");\n\tadd_string(\"\t\t\t\tmargin-left: -1px;\\n\");\n\tadd_string(\"\t\t\t}\\n\");\n\tadd_string(\"\t\t</style>\\n\");\n\tadd_string(\"\t</head>\\n\");\n\tadd_string(\"\t<body><pre>\\n\");\n\n\tfor (int i = 0; i < old->line_count; ++i) {\n\t\tchar tmp[100];\n\t\tsprintf(tmp, \"<span id=\\\"L%d\\\"><a href=\\\"#L%d\\\"></a>\", i+1, i+1);\n\t\tadd_string(tmp);\n\t\tint last_flag = -1;\n\t\tint opened = 0;\n\t\tint all_spaces = 1;\n\t\tfor (int j = 0; j < old->lines[i]->actual; ++j) {\n\t\t\tchar_t c = old->lines[i]->text[j];\n\n\t\t\tif (c.codepoint != ' ') all_spaces = 0;\n\n\t\t\tif (last_flag == -1 || last_flag != (c.flags & 0x1F)) {\n\t\t\t\tif (opened) add_string(\"</span>\");\n\t\t\t\topened = 1;\n\t\t\t\tchar tmp[100];\n\t\t\t\tsprintf(tmp, \"<span class=\\\"s%d%s\\\">\",\n\t\t\t\t\tc.flags & FLAG_MASK_COLORS,\n\t\t\t\t\t(c.flags & FLAG_UNDERLINE) ? \" ul\" : \"\");\n\t\t\t\tadd_string(tmp);\n\t\t\t\tlast_flag = (c.flags & 0x1F);\n\t\t\t}\n\n\t\t\tif (c.codepoint == '<') {\n\t\t\t\tadd_string(\"&lt;\");\n\t\t\t} else if (c.codepoint == '>') {\n\t\t\t\tadd_string(\"&gt;\");\n\t\t\t} else if (c.codepoint == '&') {\n\t\t\t\tadd_string(\"&amp;\");\n\t\t\t} else if (c.codepoint == '\\t') {\n\t\t\t\tchar tmp[100];\n\t\t\t\tsprintf(tmp, \"<span class=\\\"tab%d\\\"><span>\t</span></span>\",c.display_width);\n\t\t\t\tadd_string(tmp);\n\t\t\t} else if (j > 0 && c.codepoint == ' ' && all_spaces && !(j % old->tabstop)) {\n\t\t\t\tadd_string(\"<span class=\\\"space\\\"> </span>\");\n\t\t\t} else {\n\t\t\t\tchar tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */\n\t\t\t\tto_eight(c.codepoint, tmp);\n\t\t\t\tadd_string(tmp);\n\t\t\t}\n\t\t}\n\t\tif (opened) {\n\t\t\tadd_string(\"</span>\");\n\t\t} else {\n\t\t\tadd_string(\"<wbr>\");\n\t\t}\n\t\tadd_string(\"</span>\\n\");\n\t}\n\n\tadd_string(\"</pre></body>\\n\");\n\tadd_string(\"</html>\\n\");\n\n\tenv->loading = 0;\n\tenv->modified = 1;\n\tif (old->file_name) {\n\t\tchar * base = file_basename(old->file_name);\n\t\tchar * tmp = malloc(strlen(base) + 5);\n\t\t*tmp = '\\0';\n\t\tstrcat(tmp, base);\n\t\tstrcat(tmp, \".htm\");\n\t\tenv->file_name = tmp;\n\t}\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\trecalculate_tabs(env->lines[i]);\n\t}\n\tenv->syntax = match_syntax(\".htm\");\n\tschedule_complete_recalc();\n\n\treturn 0;\n}\n\n/**\n * Based on vim's :TOhtml\n * Convert syntax-highlighted buffer contents to HTML.\n */\nBIM_COMMAND(tohtml,\"tohtml\",\"Convert the document to an HTML representation with syntax highlighting.\") {\n\tconvert_to_html();\n\n\tredraw_all();\n\treturn 0;\n}\n\nBIM_ALIAS(\"TOhtml\",tohtml,tohtml)\n\nint _prefix_command_run_script(char * cmd) {\n\tif (env->mode == MODE_LINE_SELECTION) {\n\t\tint range_top, range_bot;\n\t\trange_top = env->start_line < env->line_no ? env->start_line : env->line_no;\n\t\trange_bot = env->start_line < env->line_no ? env->line_no : env->start_line;\n\n\t\tint in[2];\n\t\tpipe(in);\n\t\tint out[2];\n\t\tpipe(out);\n\t\tint child = fork();\n\n\t\t/* Open child process and set up pipes */\n\t\tif (child == 0) {\n\t\t\tFILE * dev_null = fopen(\"/dev/null\",\"w\"); /* for stderr */\n\t\t\tclose(out[0]);\n\t\t\tclose(in[1]);\n\t\t\tdup2(out[1], STDOUT_FILENO);\n\t\t\tdup2(in[0], STDIN_FILENO);\n\t\t\tdup2(fileno(dev_null), STDERR_FILENO);\n\t\t\tsystem(&cmd[1]); /* Yes we can just do this */\n\t\t\texit(1);\n\t\t} else if (child < 0) {\n\t\t\trender_error(\"Failed to fork\");\n\t\t\treturn 1;\n\t\t}\n\t\tclose(out[1]);\n\t\tclose(in[0]);\n\n\t\t/* Write lines to child process */\n\t\tFILE * f = fdopen(in[1],\"w\");\n\t\tfor (int i = range_top; i <= range_bot; ++i) {\n\t\t\tline_t * line = env->lines[i-1];\n\t\t\tfor (int j = 0; j < line->actual; j++) {\n\t\t\t\tchar_t c = line->text[j];\n\t\t\t\tif (c.codepoint == 0) {\n\t\t\t\t\tchar buf[1] = {0};\n\t\t\t\t\tfwrite(buf, 1, 1, f);\n\t\t\t\t} else {\n\t\t\t\t\tchar tmp[8] = {0};\n\t\t\t\t\tint i = to_eight(c.codepoint, tmp);\n\t\t\t\t\tfwrite(tmp, i, 1, f);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfputc('\\n', f);\n\t\t}\n\t\tfclose(f);\n\t\tclose(in[1]);\n\n\t\t/* Read results from child process into a new buffer */\n\t\tFILE * result = fdopen(out[0],\"r\");\n\t\tbuffer_t * old = env;\n\t\tenv = buffer_new();\n\t\tsetup_buffer(env);\n\t\tenv->loading = 1;\n\t\tuint8_t buf[BLOCK_SIZE];\n\t\tstate = 0;\n\t\twhile (!feof(result) && !ferror(result)) {\n\t\t\tsize_t r = fread(buf, 1, BLOCK_SIZE, result);\n\t\t\tadd_buffer(buf, r);\n\t\t}\n\t\tif (env->line_no && env->lines[env->line_no-1] && env->lines[env->line_no-1]->actual == 0) {\n\t\t\tenv->lines = remove_line(env->lines, env->line_no-1);\n\t\t}\n\t\tfclose(result);\n\t\twaitpid(-1,NULL,WNOHANG);\n\t\tenv->loading = 0;\n\n\t\t/* Return to the original buffer and replace the selected lines with the output */\n\t\tbuffer_t * new = env;\n\t\tenv = old;\n\t\tfor (int i = range_top; i <= range_bot; ++i) {\n\t\t\t/* Remove the existing lines */\n\t\t\tenv->lines = remove_line(env->lines, range_top-1);\n\t\t}\n\t\tfor (int i = 0; i < new->line_count; ++i) {\n\t\t\t/* Add the new lines */\n\t\t\tenv->lines = add_line(env->lines, range_top + i - 1);\n\t\t\treplace_line(env->lines, range_top + i - 1, new->lines[i]);\n\t\t\trecalculate_tabs(env->lines[range_top+i-1]);\n\t\t}\n\n\t\tenv->modified = 1;\n\n\t\t/* Close the temporary buffer */\n\t\tbuffer_close(new);\n\t} else {\n\t\t/* Reset and draw some line feeds */\n\t\treset();\n\t\tprintf(\"\\n\\n\");\n\n\t\t/* Set buffered for shell application */\n\t\tset_buffered();\n\n\t\t/* Call the shell and wait for completion */\n\t\tsystem(&cmd[1]);\n\n\t\t/* Return to the editor, wait for user to press enter. */\n\t\tset_unbuffered();\n\t\tprintf(\"\\n\\nPress ENTER to continue.\");\n\t\tint c;\n\t\twhile ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED));\n\n\t\t/* Redraw the screen */\n\t\tredraw_all();\n\t}\n\n\t/* Done processing command */\n\treturn 0;\n}\n\nint replace_text(int range_top, int range_bot, char divider, char * needle) {\n\tchar * c = needle;\n\tchar * replacement = NULL;\n\tchar * options = \"\";\n\n\twhile (*c) {\n\t\tif (*c == divider) {\n\t\t\t*c = '\\0';\n\t\t\treplacement = c + 1;\n\t\t\tbreak;\n\t\t}\n\t\tc++;\n\t}\n\n\tif (!replacement) {\n\t\trender_error(\"nothing to replace with\");\n\t\treturn 1;\n\t}\n\n\tc = replacement;\n\twhile (*c) {\n\t\tif (*c == divider) {\n\t\t\t*c = '\\0';\n\t\t\toptions = c + 1;\n\t\t\tbreak;\n\t\t}\n\t\tc++;\n\t}\n\n\tint global = 0;\n\tint case_insensitive = 0;\n\n\t/* Parse options */\n\twhile (*options) {\n\t\tswitch (*options) {\n\t\t\tcase 'g':\n\t\t\t\tglobal = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'i':\n\t\t\t\tcase_insensitive = 1;\n\t\t\t\tbreak;\n\t\t}\n\t\toptions++;\n\t}\n\n\tuint32_t * needle_c = malloc(sizeof(uint32_t) * (strlen(needle) + 1));\n\tuint32_t * replacement_c = malloc(sizeof(uint32_t) * (strlen(replacement) + 1));\n\n\t{\n\t\tint i = 0;\n\t\tuint32_t c, state = 0;\n\t\tfor (char * cin = needle; *cin; cin++) {\n\t\t\tif (!decode(&state, &c, *cin)) {\n\t\t\t\tneedle_c[i] = c;\n\t\t\t\ti++;\n\t\t\t} else if (state == UTF8_REJECT) {\n\t\t\t\tstate = 0;\n\t\t\t}\n\t\t}\n\t\tneedle_c[i] = 0;\n\t\ti = 0;\n\t\tc = 0;\n\t\tstate = 0;\n\t\tfor (char * cin = replacement; *cin; cin++) {\n\t\t\tif (!decode(&state, &c, *cin)) {\n\t\t\t\treplacement_c[i] = c;\n\t\t\t\ti++;\n\t\t\t} else if (state == UTF8_REJECT) {\n\t\t\t\tstate = 0;\n\t\t\t}\n\t\t}\n\t\treplacement_c[i] = 0;\n\t}\n\n\tint replacements = 0;\n\tfor (int line = range_top; line <= range_bot; ++line) {\n\t\tint col = 0;\n\t\twhile (col != -1) {\n\t\t\tint _line = line;\n\t\t\tperform_replacement(line, needle_c, replacement_c, col, case_insensitive, &col, &_line);\n\t\t\tif (col != -1) replacements++;\n\t\t\tif (_line > line) {\n\t\t\t\trange_bot += _line - line;\n\t\t\t\tline = _line;\n\t\t\t}\n\t\t\tif (!global) break;\n\t\t}\n\t}\n\tif (env->mode == MODE_LINE_SELECTION) {\n\t\tenv->start_line = env->start_line < env->line_no ? range_top : range_bot;\n\t\tenv->line_no    = env->start_line < env->line_no ? range_bot : range_top;\n\t}\n\tfree(needle_c);\n\tfree(replacement_c);\n\tif (replacements) {\n\t\trender_status_message(\"replaced %d instance%s of %s\", replacements, replacements == 1 ? \"\" : \"s\", needle);\n\t\tset_history_break();\n\t\tredraw_text();\n\t} else {\n\t\trender_error(\"Pattern not found: %s\", needle);\n\t}\n\n\treturn 0;\n}\n\nBIM_PREFIX_COMMAND(repsome,\"s\",\"Perform a replacement over selected lines\") {\n\tint range_top, range_bot;\n\tif (env->mode == MODE_LINE_SELECTION) {\n\t\trange_top = env->start_line < env->line_no ? env->start_line : env->line_no;\n\t\trange_bot = env->start_line < env->line_no ? env->line_no : env->start_line;\n\t} else {\n\t\trange_top = env->line_no;\n\t\trange_bot = env->line_no;\n\t}\n\treturn replace_text(range_top, range_bot, cmd[1], &cmd[2]);\n}\n\nBIM_PREFIX_COMMAND(repall,\"%s\",\"Perform a replacement over the entire file.\") {\n\treturn replace_text(1, env->line_count, cmd[2], &cmd[3]);\n}\n\nBIM_COMMAND(e,\"e\",\"Open a file\") {\n\tif (argc > 1) {\n\t\t/* This actually opens a new tab */\n\t\topen_file(argv[1]);\n\t\tupdate_title();\n\t} else {\n\t\tif (env->modified) {\n\t\t\trender_error(\"File is modified, can not reload.\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (!env->file_name) {\n\t\t\trender_error(\"No file name.\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tbuffer_t * old_env = env;\n\t\topen_file(env->file_name);\n\t\tbuffer_t * new_env = env;\n\t\tenv = old_env;\n\n#define SWAP(T,a,b) do { T x = a; a = b; b = x; } while (0)\n\t\tSWAP(line_t **, env->lines, new_env->lines);\n\t\tSWAP(int, env->line_count, new_env->line_count);\n\t\tSWAP(int, env->line_avail, new_env->line_avail);\n\t\tSWAP(history_t *, env->history, new_env->history);\n\n\t\tbuffer_close(new_env); /* Should probably also free, this needs editing. */\n\t\tschedule_complete_recalc();\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(tabnew,\"tabnew\",\"Open a new tab\") {\n\tif (argc > 1) {\n\t\topen_file(argv[1]);\n\t\tupdate_title();\n\t} else {\n\t\tenv = buffer_new();\n\t\tsetup_buffer(env);\n\t\tredraw_all();\n\t\tupdate_title();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(w,\"w\",\"Write a file\") {\n\t/* w: write file */\n\tif (argc > 1) {\n\t\twrite_file(argv[1]);\n\t} else {\n\t\twrite_file(env->file_name);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(wq,\"wq\",\"Write and close buffer\") {\n\twrite_file(env->file_name);\n\tclose_buffer();\n\treturn 0;\n}\n\nBIM_COMMAND(history,\"history\",\"Display command history\") {\n\trender_commandline_message(\"\"); /* To clear command line */\n\tfor (int i = COMMAND_HISTORY_MAX; i > 1; --i) {\n\t\tif (command_history[i-1]) render_commandline_message(\"%d:%s\\n\", i-1, command_history[i-1]);\n\t}\n\trender_commandline_message(\"\\n\");\n\tredraw_tabbar();\n\tredraw_commandline();\n\tpause_for_key();\n\treturn 0;\n}\n\nBIM_COMMAND(q,\"q\",\"Close buffer\") {\n\tif (left_buffer && left_buffer == right_buffer) {\n\t\tunsplit();\n\t\treturn 0;\n\t}\n\tif (env->modified) {\n\t\trender_error(\"No write since last change. Use :q! to force exit.\");\n\t} else {\n\t\tclose_buffer();\n\t}\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(qbang,\"q!\",\"Force close buffer\") {\n\tclose_buffer();\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(qa,\"qa\",\"Try to close all buffers\") {\n\ttry_quit();\n\treturn 0;\n}\n\nBIM_ALIAS(\"qall\",qall,qa)\n\nBIM_COMMAND(qabang,\"qa!\",\"Force exit\") {\n\t/* Forcefully exit editor */\n\twhile (buffers_len) {\n\t\tbuffer_close(buffers[0]);\n\t}\n\tquit(NULL);\n\treturn 1; /* doesn't return */\n}\n\nBIM_COMMAND(tabp,\"tabp\",\"Previous tab\") {\n\tprevious_tab();\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(tabn,\"tabn\",\"Next tab\") {\n\tnext_tab();\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(tabm,\"tabm\",\"Move the current tab to a new index\") {\n\t/* Figure out the current index */\n\tint i = 0;\n\tfor (; i < buffers_len; i++) {\n\t\tif (buffers[i] == env) break;\n\t}\n\n\tif (i == buffers_len) {\n\t\trender_status_message(\"(invalid state?)\");\n\t\treturn 1;\n\t}\n\n\tif (argc < 2) {\n\t\trender_status_message(\"tab = %d\", i);\n\t\treturn 1;\n\t}\n\n\tint newIndex = atoi(argv[1]);\n\n\tif (newIndex == i) {\n\t\treturn 0;\n\t}\n\n\t/* Okay, this is stupid, but, remove the buffer */\n\tmemmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i -1));\n\t/* Then make space at the destination */\n\tmemmove(&buffers[newIndex+1], &buffers[newIndex], sizeof(*buffers) * (buffers_len - newIndex -1));\n\n\tbuffers[newIndex] = env;\n\n\tredraw_tabbar();\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(tab,\"tab\", \"Open a specific tab\") {\n\tif (argc < 2) return bim_command_tabm(\"tabm\", argc, argv);\n\tint i = atoi(argv[1]);\n\n\tif (i < 0 || i > buffers_len) {\n\t\trender_error(\"Invalid tab index\");\n\t\treturn 1;\n\t}\n\n\tenv = buffers[i];\n\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\tredraw_all();\n\tupdate_title();\n\treturn 0;\n}\n\nBIM_COMMAND(tabindicator,\"tabindicator\",\"Set the tab indicator\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"tabindicator=%s\", global_config.tab_indicator);\n\t\treturn 0;\n\t}\n\tif (!global_config.can_unicode && strlen(argv[1]) != 1) return 0;\n\tif (display_width_of_string(argv[1]) != 1) {\n\t\trender_error(\"Can't set '%s' as indicator, must be one cell wide.\", argv[1]);\n\t\treturn 1;\n\t}\n\tif (global_config.tab_indicator) free(global_config.tab_indicator);\n\tglobal_config.tab_indicator = strdup(argv[1]);\n\treturn 0;\n}\n\nBIM_COMMAND(spaceindicator,\"spaceindicator\",\"Set the space indicator\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"spaceindicator=%s\", global_config.space_indicator);\n\t\treturn 0;\n\t}\n\tif (!global_config.can_unicode && strlen(argv[1]) != 1) return 0;\n\tif (display_width_of_string(argv[1]) != 1) {\n\t\trender_error(\"Can't set '%s' as indicator, must be one cell wide.\", argv[1]);\n\t\treturn 1;\n\t}\n\tif (global_config.space_indicator) free(global_config.space_indicator);\n\tglobal_config.space_indicator = strdup(argv[1]);\n\treturn 0;\n}\n\nBIM_COMMAND(global_git,\"global.git\",\"Show or change the default status of git integration\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"global.git=%d\", global_config.check_git);\n\t} else {\n\t\tglobal_config.check_git = !!atoi(argv[1]);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(git,\"git\",\"Show or change status of git integration\") {\n\tif (!env) {\n\t\trender_error(\"requires environment (did you mean global.git?)\");\n\t\treturn 1;\n\t}\n\tif (argc < 2) {\n\t\trender_status_message(\"git=%d\", env->checkgitstatusonwrite);\n\t} else {\n\t\tenv->checkgitstatusonwrite = !!atoi(argv[1]);\n\t\tif (env->checkgitstatusonwrite && !env->modified && env->file_name) {\n\t\t\tgit_examine(env->file_name);\n\t\t\tredraw_text();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(colorgutter,\"colorgutter\",\"Show or change status of gutter colorization for unsaved modifications\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"colorgutter=%d\", global_config.color_gutter);\n\t} else {\n\t\tglobal_config.color_gutter = !!atoi(argv[1]);\n\t\tredraw_text();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(indent,\"indent\",\"Enable smart indentation\") {\n\tenv->indent = 1;\n\tredraw_statusbar();\n\treturn 0;\n}\n\nBIM_COMMAND(noindent,\"noindent\",\"Disable smart indentation\") {\n\tenv->indent = 0;\n\tredraw_statusbar();\n\treturn 0;\n}\n\n/* TODO: global.maxcolumn */\nBIM_COMMAND(maxcolumn,\"maxcolumn\",\"Highlight past the given column to indicate maximum desired line length\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"maxcolumn=%d\",env->maxcolumn);\n\t\treturn 0;\n\t}\n\tenv->maxcolumn = atoi(argv[1]);\n\tredraw_text();\n\treturn 0;\n}\n\nBIM_COMMAND(cursorcolumn,\"cursorcolumn\",\"Show the visual column offset of the cursor.\") {\n\trender_status_message(\"cursorcolumn=%d\", env->preferred_column);\n\treturn 0;\n}\n\nBIM_COMMAND(noh,\"noh\",\"Clear search term\") {\n\tif (global_config.search) {\n\t\tfree(global_config.search);\n\t\tglobal_config.search = NULL;\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\t\tenv->lines[i]->text[j].flags &= ~(FLAG_SEARCH);\n\t\t\t}\n\t\t}\n\t\tredraw_text();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(help,\"help\",\"Show help text.\") {\n\tif (argc < 2) {\n\t\trender_commandline_message(\"\"); /* To clear command line */\n\t\trender_commandline_message(\"\\n\");\n\t\trender_commandline_message(\" \\033[1mbim - a text editor \\033[22m\\n\");\n\t\trender_commandline_message(\"\\n\");\n\t\trender_commandline_message(\" Available commands:\\n\");\n\t\trender_commandline_message(\"   Quit with \\033[3m:q\\033[23m, \\033[3m:qa\\033[23m, \\033[3m:q!\\033[23m, \\033[3m:qa!\\033[23m\\n\");\n\t\trender_commandline_message(\"   Write out with \\033[3m:w \\033[4mfile\\033[24;23m\\n\");\n\t\trender_commandline_message(\"   Set syntax with \\033[3m:syntax \\033[4mlanguage\\033[24;23m\\n\");\n\t\trender_commandline_message(\"   Open a new tab with \\033[3m:e \\033[4mpath/to/file\\033[24;23m\\n\");\n\t\trender_commandline_message(\"   \\033[3m:tabn\\033[23m and \\033[3m:tabp\\033[23m can be used to switch tabs\\n\");\n\t\trender_commandline_message(\"   Set the color scheme with \\033[3m:theme \\033[4mtheme\\033[24;23m\\n\");\n\t\trender_commandline_message(\"   Set the behavior of the tab key with \\033[3m:tabs\\033[23m or \\033[3m:spaces\\033[23m\\n\");\n\t\trender_commandline_message(\"   Set tabstop with \\033[3m:tabstop \\033[4mwidth\\033[24;23m\\n\");\n\t\trender_commandline_message(\"\\n\");\n\t\trender_commandline_message(\" Bim %s%s\\n\", BIM_VERSION, BIM_BUILD_DATE);\n\t\trender_commandline_message(\" %s\\n\", BIM_COPYRIGHT);\n\t\trender_commandline_message(\"\\n\");\n\t} else {\n\t\tint found = 0;\n\t\tfor (struct command_def * c = regular_commands; !found && regular_commands && c->name; ++c) {\n\t\t\tif (!strcmp(c->name, argv[1])) {\n\t\t\t\trender_commandline_message(\"\"); /* To clear command line */\n\t\t\t\trender_commandline_message(\"Help description for `%s`:\\n\", c->name);\n\t\t\t\trender_commandline_message(\"  %s\\n\", c->description);\n\t\t\t\tfound = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfor (struct command_def * c = prefix_commands; !found && prefix_commands && c->name; ++c) {\n\t\t\tif (!strcmp(c->name, argv[1])) {\n\t\t\t\trender_commandline_message(\"\"); /* To clear command line */\n\t\t\t\trender_commandline_message(\"Help description for `%s`:\\n\", c->name);\n\t\t\t\trender_commandline_message(\"  %s\\n\", c->description);\n\t\t\t\tfound = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!found) {\n\t\t\trender_error(\"Unknown command: %s\", argv[1]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\t/* Redrawing the tabbar makes it look like we just shifted the whole view up */\n\tredraw_tabbar();\n\tredraw_commandline();\n\t/* Wait for a character so we can redraw the screen before continuing */\n\tpause_for_key();\n\treturn 0;\n}\n\nBIM_COMMAND(version,\"version\",\"Show version information.\") {\n\trender_status_message(\"Bim %s%s\", BIM_VERSION, BIM_BUILD_DATE);\n\treturn 0;\n}\n\nBIM_COMMAND(theme,\"theme\",\"Set color theme\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"theme=%s\", current_theme);\n\t} else {\n\t\tfor (struct theme_def * d = themes; themes && d->name; ++d) {\n\t\t\tif (!strcmp(argv[1], d->name)) {\n\t\t\t\tptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack;\n\t\t\t\tkrk_push(OBJECT_VAL(d->callable));\n\t\t\t\tKrkValue result = krk_callStack(0);\n\t\t\t\tkrk_currentThread.stackTop = krk_currentThread.stack + before;\n\t\t\t\tif (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) {\n\t\t\t\t\trender_error(\"Exception occurred in theme: %s\", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars);\n\t\t\t\t\tkrk_dumpTraceback();\n\t\t\t\t\tint key = 0;\n\t\t\t\t\twhile ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\t\t\t\t}\n\t\t\t\tcurrent_theme = d->name;\n\t\t\t\tredraw_all();\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_ALIAS(\"colorscheme\",colorscheme,theme)\n\nBIM_COMMAND(splitpercent,\"splitpercent\",\"Display or change view split\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"splitpercent=%d\", global_config.split_percent);\n\t\treturn 0;\n\t} else {\n\t\tglobal_config.split_percent = atoi(argv[1]);\n\t\tif (left_buffer) {\n\t\t\tupdate_split_size();\n\t\t\tredraw_all();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(split,\"split\",\"Split the current view.\") {\n\tbuffer_t * original = env;\n\tif (argc > 1) {\n\t\tint is_not_number = 0;\n\t\tfor (char * c = argv[1]; *c; ++c) is_not_number |= !isdigit(*c);\n\t\tif (is_not_number) {\n\t\t\t/* Open a file for the new split */\n\t\t\topen_file(argv[1]);\n\t\t\tright_buffer = buffers[buffers_len-1];\n\t\t} else {\n\t\t\t/* Use an existing buffer for the new split */\n\t\t\tint other = atoi(argv[1]);\n\t\t\tif (other >= buffers_len || other < 0) {\n\t\t\t\trender_error(\"Invalid buffer number: %d\", other);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tright_buffer = buffers[other];\n\t\t}\n\t} else {\n\t\t/* Use the current buffer for the new split */\n\t\tright_buffer = original;\n\t}\n\tleft_buffer = original;\n\tupdate_split_size();\n\tredraw_all();\n\treturn 0;\n}\n\nBIM_COMMAND(unsplit,\"unsplit\",\"Show only one buffer on screen\") {\n\tunsplit();\n\treturn 0;\n}\n\nBIM_COMMAND(horizontalscrolling,\"horizontalscrolling\",\"Set the horizontal scrolling mode\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"horizontalscrolling=%d\", global_config.horizontal_shift_scrolling);\n\t\treturn 0;\n\t} else {\n\t\tglobal_config.horizontal_shift_scrolling = !!atoi(argv[1]);\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(syntax,\"syntax\",\"Show or set the active syntax highlighter\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"syntax=%s\", env->syntax ? env->syntax->name : \"none\");\n\t} else {\n\t\tset_syntax_by_name(argv[1]);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(recalc,\"recalc\",\"Recalculate syntax for the entire file.\") {\n\tschedule_complete_recalc();\n\tredraw_all();\n\treturn 0;\n}\n\nBIM_COMMAND(tabstop,\"tabstop\",\"Show or set the tabstop (width of an indentation unit)\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"tabstop=%d\", env->tabstop);\n\t} else {\n\t\tint t = atoi(argv[1]);\n\t\tif (t > 0 && t < 12) {\n\t\t\tenv->tabstop = t;\n\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\trecalculate_tabs(env->lines[i]);\n\t\t\t}\n\t\t\tredraw_all();\n\t\t} else {\n\t\t\trender_error(\"Invalid tabstop: %s\", argv[1]);\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(spaces,\"spaces\",\"Use spaces for indentation\") {\n\tenv->tabs = 0;\n\tif (argc > 1) {\n\t\tbim_command_tabstop(\"tabstop\", argc, argv);\n\t}\n\tredraw_statusbar();\n\treturn 0;\n}\n\nBIM_COMMAND(tabs,\"tabs\",\"Use tabs for indentation\") {\n\tenv->tabs = 1;\n\tif (argc > 1) {\n\t\tbim_command_tabstop(\"tabstop\", argc, argv);\n\t}\n\tredraw_statusbar();\n\treturn 0;\n}\n\nBIM_COMMAND(clearyear,\"clearyank\",\"Clear the yank buffer\") {\n\tif (global_config.yanks) {\n\t\tfor (unsigned int i = 0; i < global_config.yank_count; ++i) {\n\t\t\tfree(global_config.yanks[i]);\n\t\t}\n\t\tfree(global_config.yanks);\n\t\tglobal_config.yanks = NULL;\n\t\tglobal_config.yank_count = 0;\n\t\tredraw_statusbar();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(padding,\"padding\",\"Show or set cursor padding when scrolling vertically\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"padding=%d\", global_config.cursor_padding);\n\t} else {\n\t\tglobal_config.cursor_padding = atoi(argv[1]);\n\t\tif (env) {\n\t\t\tplace_cursor_actual();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(smartcase,\"smartcase\",\"Show or set the status of the smartcase search option\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"smartcase=%d\", global_config.smart_case);\n\t} else {\n\t\tglobal_config.smart_case = atoi(argv[1]);\n\t\tif (env) place_cursor_actual();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(hlparen,\"hlparen\",\"Show or set the configuration option to highlight matching braces\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"hlparen=%d\", global_config.highlight_parens);\n\t} else {\n\t\tglobal_config.highlight_parens = atoi(argv[1]);\n\t\tif (env) {\n\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\t\t\tenv->lines[i]->text[j].flags &= (~FLAG_SELECT);\n\t\t\t\t}\n\t\t\t}\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(hlcurrent,\"hlcurrent\",\"Show or set the configuration option to highlight the current line\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"hlcurrent=%d\", global_config.highlight_current_line);\n\t} else {\n\t\tglobal_config.highlight_current_line = atoi(argv[1]);\n\t\tif (env) {\n\t\t\tif (!global_config.highlight_current_line) {\n\t\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\t\tenv->lines[i]->is_current = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(crnl,\"crnl\",\"Show or set the line ending mode\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"crnl=%d\", env->crnl);\n\t} else {\n\t\tenv->crnl = !!atoi(argv[1]);\n\t\tredraw_statusbar();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(global_numbers,\"global.numbers\",\"Set whether numbers are displayed by default\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"global.numbers=%d\", global_config.numbers);\n\t} else {\n\t\tglobal_config.numbers = !!atoi(argv[1]);\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(global_statusbar,\"global.statusbar\",\"Show or set whether to display the statusbar\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"global.statusbar=%d\",!global_config.hide_statusbar);\n\t} else {\n\t\tglobal_config.hide_statusbar = !atoi(argv[1]);\n\t\tglobal_config.bottom_size = global_config.hide_statusbar ? 1 : 2;\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(global_scrollamount,\"global.scrollamount\",\"Show or set scroll amount when using mouse wheel\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"global.scrollamount=%d\",global_config.scroll_amount);\n\t} else {\n\t\tglobal_config.scroll_amount = atoi(argv[1]);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(global_search_wraps,\"wrapsearch\",\"Enable search wrapping around from top or bottom\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"wrapsearch=%d\",global_config.search_wraps);\n\t} else {\n\t\tglobal_config.search_wraps = !!atoi(argv[1]);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(smartcomplete,\"smartcomplete\",\"Enable autocompletion while typing\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"smartcomplete=%d\",global_config.smart_complete);\n\t} else {\n\t\tglobal_config.smart_complete = !!atoi(argv[1]);\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(global_autohide_tabs,\"global.autohidetabs\",\"Whether to show the tab bar when there is only one tab\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"global.autohidetabs=%d\", global_config.autohide_tabs);\n\t} else {\n\t\tglobal_config.autohide_tabs = !!atoi(argv[1]);\n\t\tglobal_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1);\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(numbers,\"numbers\",\"Show or set the display of line numbers\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"numbers=%d\", env->numbers);\n\t} else {\n\t\tenv->numbers = !!atoi(argv[1]);\n\t\tredraw_all();\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(relativenumbers,\"relativenumbers\",\"Show or set the display of relative line numbers\") {\n\tif (argc < 2) {\n\t\trender_status_message(\"relativenumber=%d\", global_config.relative_lines);\n\t} else {\n\t\tglobal_config.relative_lines = atoi(argv[1]);\n\t\tif (env) {\n\t\t\tif (!global_config.relative_lines) {\n\t\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\t\tenv->lines[i]->is_current = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t}\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(buffers,\"buffers\",\"Show the open buffers\") {\n\tfor (int i = 0; i < buffers_len; ++i) {\n\t\trender_commandline_message(\"%d: %s\\n\", i, buffers[i]->file_name ? buffers[i]->file_name : \"(no name)\");\n\t}\n\tredraw_tabbar();\n\tredraw_commandline();\n\tpause_for_key();\n\treturn 0;\n}\n\nBIM_COMMAND(keyname,\"keyname\",\"Press and key and get its name.\") {\n\tint c;\n\trender_commandline_message(\"(press a key)\");\n\twhile ((c = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\trender_commandline_message(\"%d = %s\", c, name_from_key(c));\n\treturn 0;\n}\n\nint isSubstitutionSymbol(int c) {\n\tif (c >= '!' && c <= '/') return 1;\n\tif (c >= ':' && c <= '@') return 1;\n\tif (c >= '[' && c <= '`') return 1;\n\tif (c >= '{' && c <= '~') return 1;\n\treturn 0;\n}\n\nint alldigits(const char * c) {\n\twhile (*c) {\n\t\tif (!isdigit(*c)) return 0;\n\t\tc++;\n\t}\n\treturn 1;\n}\n\n/**\n * Process a user command.\n */\nint process_krk_command(const char * cmd, KrkValue * outVal);\nint process_command(char * cmd) {\n\tif (cmd[0] == '-' && alldigits(&cmd[1])) {\n\t\tgoto_line(env->line_no-atoi(&cmd[1]));\n\t\treturn 0;\n\t} else if (cmd[0] == '+' && alldigits(&cmd[1])) {\n\t\tgoto_line(env->line_no+atoi(&cmd[1]));\n\t\treturn 0;\n\t} else if (alldigits(cmd)) {\n\t\tgoto_line(atoi(cmd));\n\t\treturn 0;\n\t} else if (cmd[0] == '!') {\n\t\treturn _prefix_command_run_script(cmd);\n\t} else if (cmd[0] == 's' && isSubstitutionSymbol(cmd[1])) {\n\t\treturn bim_command_repsome(cmd, 0, NULL);\n\t} else if (cmd[0] == '%' && cmd[1] == 's') {\n\t\treturn bim_command_repall(cmd, 0, NULL);\n\t}\n\n\t/* See if it's a bim command in the classic format */\n\t{\n\t\tchar * argv[3] = {NULL, NULL, NULL};\n\t\tint argc = !!(*cmd);\n\t\tchar cmd_name[512] = {0};\n\t\tfor (char * c = (char*)cmd; *c; ++c) {\n\t\t\tif (c-cmd == 511) break;\n\t\t\tif (*c == ' ') {\n\t\t\t\tcmd_name[c-cmd] = '\\0';\n\t\t\t\twhile (*c == ' ') c++;\n\t\t\t\targv[1] = c;\n\t\t\t\tif (*argv[1]) argc++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcmd_name[c-cmd] = *c;\n\t\t}\n\n\t\targv[0] = cmd_name;\n\t\targv[argc] = NULL;\n\t\tfor (struct command_def * c = regular_commands; regular_commands && c->name; ++c) {\n\t\t\tif (!strcmp(argv[0], c->name)) {\n\t\t\t\tkrk_resetStack();\n\t\t\t\treturn c->command((char*)cmd, argc, argv);\n\t\t\t}\n\t\t}\n\t}\n\n\tint retval = process_krk_command(cmd, NULL);\n\n\treturn retval;\n}\n\nstruct Candidate {\n\tchar * text;\n\tint type;\n};\n\nstatic int biased_strcmp(const char *l, const char *r) {\n\tfor (; *l == *r && *l; l++, r++);\n\t/* Treat / like \\0 */\n\tif (*l == '/') return -1;\n\tif (*r == '/') return 1;\n\treturn *(unsigned char *)l - *(unsigned char *)r;\n}\n\n/**\n * Wrap strcmp for use with qsort.\n */\nint compare_candidate(const void * a, const void * b) {\n\tconst struct Candidate *_a = a;\n\tconst struct Candidate *_b = b;\n\treturn biased_strcmp(_a->text, _b->text);\n}\n\n/**\n * List of file extensions to ignore when tab completing.\n * TODO this should be configurable\n */\nconst char * tab_complete_ignore[] = {\".o\",\".lo\",NULL};\n\n/**\n * Wrapper around krk_valueGetAttribute...\n */\nstatic KrkValue findFromProperty(KrkValue current, KrkToken next) {\n\tKrkValue member = OBJECT_VAL(krk_copyString(next.start, next.literalWidth));\n\tkrk_push(member);\n\tKrkValue value = krk_valueGetAttribute_default(current, AS_CSTRING(member), NONE_VAL());\n\tkrk_pop();\n\treturn value;\n}\n\n/**\n * Macros for use in command mode.\n */\n#define _syn_command() do { env->syntax = global_config.command_syn; } while (0)\n#define _syn_restore() do { env->syntax = global_config.command_syn_back; } while (0)\n\n/* Forward declarations because I don't want to move these. */\nextern void eat_mouse(void);\nextern void eat_mouse_sgr(void);\n\n/**\n * Clear everything before the cursor in the command in put buffer.\n * Generally part of serialization/deserialization when moving things\n * into and out of the command buffer for tab completion.\n */\nstatic void command_buffer_clear_before(void) {\n\t_syn_command();\n\twhile (global_config.command_col_no > 1) {\n\t\tline_delete(global_config.command_buffer, global_config.command_col_no - 1, -1);\n\t\tglobal_config.command_col_no--;\n\t}\n\t_syn_restore();\n}\n\n/**\n * Serialize the command buffer, up to the cursor, to a utf8 string,\n * and then delete that portion of the buffer.\n */\nstatic char * command_buffer_serialize(void) {\n\tstruct StringBuilder sb = {0};\n\n\tfor (int i = 0; i < global_config.command_col_no-1; ++i) {\n\t\tchar buf[10];\n\t\tsize_t t = to_eight(global_config.command_buffer->text[i].codepoint, buf);\n\t\tkrk_pushStringBuilderStr(&sb, buf, t);\n\t}\n\tkrk_pushStringBuilder(&sb, '\\0');\n\n\tcommand_buffer_clear_before();\n\n\treturn sb.bytes;\n}\n\n/**\n * Fill the command buffer, from the cursor, with a string.\n */\nstatic void command_buffer_deserialize(char * tmp) {\n\tuint32_t state = 0, c= 0;\n\tchar * t = tmp;\n\t_syn_command();\n\twhile (*t) {\n\t\tif (!decode(&state, &c, *t)) {\n\t\t\tchar_t _c = {codepoint_width(c), 0, c};\n\t\t\tglobal_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1);\n\t\t\tglobal_config.command_col_no++;\n\t\t}\n\t\tt++;\n\t}\n\t_syn_restore();\n\tfree(tmp);\n}\n\n/**\n * Tab completion for command mode.\n */\nchar * command_tab_complete(char * buffer) {\n\t/* Figure out which argument this is and where it starts */\n\tint arg = 0;\n\tchar * buf = strdup(buffer);\n\tchar * b = buf;\n\n\tint args_count = 0;\n\tint args_space = 4;\n\tchar ** args = malloc(sizeof(char*)*args_space);\n#define add_arg(argument) \\\n\tdo { \\\n\t\tif (args_count == args_space) { \\\n\t\t\targs_space *= 2; \\\n\t\t\targs = realloc(args, sizeof(char*) * args_space); \\\n\t\t} \\\n\t\targs[args_count++] = argument; \\\n\t} while (0)\n\n\tint candidate_count= 0;\n\tint candidate_space = 4;\n\tstruct Candidate * candidates = malloc(sizeof(struct Candidate)*candidate_space);\n\n\t/* Accept whitespace before first argument */\n\twhile (*b == ' ') b++;\n\tchar * start = b;\n\tadd_arg(start);\n\twhile (*b && *b != ' ') b++;\n\twhile (*b) {\n\t\twhile (*b == ' ') {\n\t\t\t*b = '\\0';\n\t\t\tb++;\n\t\t}\n\t\tstart = b;\n\t\targ++;\n\t\tadd_arg(start);\n\t\tbreak;\n\t}\n\n\t/**\n\t * Check a possible candidate and add it to the\n\t * candidates list, expanding as necessary,\n\t * if it matches for the current argument.\n\t */\n#define add_candidate(candidate,candtype) \\\n\tdo { \\\n\t\tchar * _arg = args[arg]; \\\n\t\tint r = strncmp(_arg, candidate, strlen(_arg)); \\\n\t\tif (!r) { \\\n\t\t\tint skip = 0; \\\n\t\t\tfor (int i = 0; i < candidate_count; ++i) { \\\n\t\t\t\tif (!strcmp(candidates[i].text,candidate)) { skip = 1; break; } \\\n\t\t\t} \\\n\t\t\tif (skip) break; \\\n\t\t\tif (candidate_count == candidate_space) { \\\n\t\t\t\tcandidate_space *= 2; \\\n\t\t\t\tcandidates = realloc(candidates,sizeof(struct Candidate) * candidate_space); \\\n\t\t\t} \\\n\t\t\tcandidates[candidate_count].text = strdup(candidate); \\\n\t\t\tcandidates[candidate_count].type = candtype; \\\n\t\t\tcandidate_count++; \\\n\t\t} \\\n\t} while (0)\n#define Candidate_Normal 0\n#define Candidate_Command 1\n#define Candidate_Builtin 2\n\n\tint _candidates_are_files = 0;\n\n\tif (arg == 0 || (arg == 1 && !strcmp(args[0], \"help\"))) {\n\t\t/* Complete command names */\n\t\tfor (struct command_def * c = regular_commands; regular_commands && c->name; ++c) {\n\t\t\tadd_candidate(c->name,Candidate_Command);\n\t\t}\n\t\tfor (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) {\n\t\t\tadd_candidate(c->name,Candidate_Command);\n\t\t}\n\n\t\tgoto _try_kuroko;\n\t}\n\n\tif (arg == 1 && !strcmp(args[0], \"syntax\")) {\n\t\t/* Complete syntax options */\n\t\tadd_candidate(\"none\", Candidate_Builtin);\n\t\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\t\tadd_candidate(s->name, Candidate_Builtin);\n\t\t}\n\t\tgoto _accept_candidate;\n\t}\n\n\tif (arg == 1 && (!strcmp(args[0], \"theme\") || !strcmp(args[0], \"colorscheme\"))) {\n\t\t/* Complete color theme names */\n\t\tfor (struct theme_def * s = themes; themes && s->name; ++s) {\n\t\t\tadd_candidate(s->name, Candidate_Builtin);\n\t\t}\n\t\tgoto _accept_candidate;\n\t}\n\n\tif (arg == 1 && (!strcmp(args[0], \"setcolor\"))) {\n\t\tfor (struct ColorName * c = color_names; c->name; ++c) {\n\t\t\tadd_candidate(c->name, Candidate_Builtin);\n\t\t}\n\t\tgoto _accept_candidate;\n\t}\n\n\tif (arg == 1 && (!strcmp(args[0], \"action\"))) {\n\t\tfor (struct action_def * a = mappable_actions; a->name; ++a) {\n\t\t\tadd_candidate(a->name, Candidate_Builtin);\n\t\t}\n\t\tgoto _accept_candidate;\n\t}\n\n\tif (arg == 1 && (!strcmp(args[0], \"mapkey\"))) {\n\t\tfor (int i = 0; args[arg][i]; ++i) {\n\t\t\tif (args[arg][i] == ' ') {\n\t\t\t\twhile (args[arg][i] == ' ') {\n\t\t\t\t\targs[arg][i] = '\\0';\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t\tstart = &args[arg][i];\n\t\t\t\targ++;\n\t\t\t\tadd_arg(start);\n\t\t\t\ti = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (arg == 1) {\n\t\t\tfor (struct mode_names * m = mode_names; m->name; ++m) {\n\t\t\t\tadd_candidate(m->name, Candidate_Builtin);\n\t\t\t}\n\t\t} else if (arg == 2) {\n\t\t\tfor (unsigned int i = 0;  i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) {\n\t\t\t\tadd_candidate(KeyNames[i].name, Candidate_Builtin);\n\t\t\t}\n\t\t} else if (arg == 3) {\n\t\t\tfor (struct action_def * a = mappable_actions; a->name; ++a) {\n\t\t\t\tadd_candidate(a->name, Candidate_Builtin);\n\t\t\t}\n\t\t\tadd_candidate(\"none\", Candidate_Builtin);\n\t\t} else if (arg == 4) {\n\t\t\tfor (char * c = \"racnwmb\"; *c; ++c) {\n\t\t\t\tchar tmp[] = {*c,'\\0'};\n\t\t\t\tadd_candidate(tmp, Candidate_Builtin);\n\t\t\t}\n\t\t}\n\t\tgoto _accept_candidate;\n\t}\n\n\tif (arg == 1 && (!strcmp(args[0], \"e\") || !strcmp(args[0], \"tabnew\") ||\n\t    !strcmp(args[0],\"split\") || !strcmp(args[0],\"w\") || !strcmp(args[0],\"runscript\") ||\n\t    !strcmp(args[0],\"rundir\") || args[0][0] == '!')) {\n\t\t/* Complete file paths */\n\n\t\t/* First, find the deepest directory match */\n\t\tchar * tmp = strdup(args[arg]);\n\t\tchar * last_slash = strrchr(tmp, '/');\n\t\tDIR * dirp;\n\t\tif (last_slash) {\n\t\t\t*last_slash = '\\0';\n\t\t\tif (last_slash == tmp) {\n\t\t\t\t/* Started with slash, and it was the only slash */\n\t\t\t\tdirp = opendir(\"/\");\n\t\t\t} else {\n\t\t\t\tchar * home;\n\t\t\t\tif (*tmp == '~' && (home = getenv(\"HOME\"))) {\n\t\t\t\t\tchar * t = malloc(strlen(tmp) + strlen(home) + 4);\n\t\t\t\t\tsprintf(t, \"%s%s\",home,tmp+1);\n\t\t\t\t\tdirp = opendir(t);\n\t\t\t\t\tfree(t);\n\t\t\t\t} else {\n\t\t\t\t\tdirp = opendir(tmp);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t/* No directory match, completing from current directory */\n\t\t\tdirp = opendir(\".\");\n\t\t\ttmp[0] = '\\0';\n\t\t}\n\n\t\tif (!dirp) {\n\t\t\t/* Directory match doesn't exist, no candidates to populate */\n\t\t\tfree(tmp);\n\t\t\tgoto done;\n\t\t}\n\n\t\t_candidates_are_files = 1;\n\n\t\tstruct dirent * ent = readdir(dirp);\n\t\twhile (ent != NULL) {\n\t\t\tif (ent->d_name[0] != '.' || (last_slash ? (last_slash[1] == '.') : (tmp[0] == '.'))) {\n\t\t\t\tstruct stat statbuf;\n\t\t\t\t/* Figure out if this file is a directory */\n\t\t\t\tif (last_slash) {\n\t\t\t\t\tchar * x;\n\t\t\t\t\tchar * home;\n\t\t\t\t\tif (tmp[0] == '~' && (home = getenv(\"HOME\"))) {\n\t\t\t\t\t\tx = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1);\n\t\t\t\t\t\tsnprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1, \"%s%s/%s\",home,tmp+1,ent->d_name);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tx = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1);\n\t\t\t\t\t\tsnprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1, \"%s/%s\",tmp,ent->d_name);\n\t\t\t\t\t}\n\t\t\t\t\tstat(x, &statbuf);\n\t\t\t\t\tfree(x);\n\t\t\t\t} else {\n\t\t\t\t\tstat(ent->d_name, &statbuf);\n\t\t\t\t}\n\n\t\t\t\t/* Build the complete argument name to tab complete */\n\t\t\t\tint type = Candidate_Normal;\n\t\t\t\tchar s[1024] = {0};\n\t\t\t\tif (last_slash == tmp) {\n\t\t\t\t\tstrcat(s,\"/\");\n\t\t\t\t} else if (*tmp) {\n\t\t\t\t\tstrcat(s,tmp);\n\t\t\t\t\tstrcat(s,\"/\");\n\t\t\t\t}\n\t\t\t\tstrcat(s,ent->d_name);\n\t\t\t\t/*\n\t\t\t\t * If it is a directory, add a / to the end so the next completion\n\t\t\t\t * attempt will complete the directory's contents.\n\t\t\t\t */\n\t\t\t\tif (S_ISDIR(statbuf.st_mode)) {\n\t\t\t\t\tstrcat(s,\"/\");\n\t\t\t\t\ttype = Candidate_Command;\n\t\t\t\t}\n\n\t\t\t\tint skip = 0;\n\t\t\t\tfor (const char ** c = tab_complete_ignore; *c; ++c) {\n\t\t\t\t\tif (str_ends_with(s, *c)) {\n\t\t\t\t\t\tskip = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!skip) {\n\t\t\t\t\tadd_candidate(s, type);\n\t\t\t\t}\n\t\t\t}\n\t\t\tent = readdir(dirp);\n\t\t}\n\t\tclosedir(dirp);\n\t\tfree(tmp);\n\t\tgoto _accept_candidate;\n\t}\n\n\t/* Hacky port of the kuroko repl completer */\n_try_kuroko:\n\t{\n\t\tKrkScanner scanner = krk_initScanner(buffer);\n\t\tKrkToken * space = malloc(sizeof(KrkToken) * (strlen(buffer) + 2));\n\t\tint count = 0;\n\t\tdo {\n\t\t\tspace[count++] = krk_scanToken(&scanner);\n\t\t} while (space[count-1].type != TOKEN_EOF && space[count-1].type != TOKEN_ERROR);\n\n\t\tif (count == 1) {\n\t\t\tgoto _cleanup;\n\t\t}\n\n\t\tint base = 2;\n\t\tint n = base;\n\t\tif (space[count-base].type == TOKEN_DOT) {\n\t\t\t/* Dots we need to look back at the previous tokens for */\n\t\t\tn--;\n\t\t\tbase--;\n\t\t} else if (space[count-base].type >= TOKEN_IDENTIFIER && space[count-base].type <= TOKEN_WITH) {\n\t\t\t/* Something alphanumeric; only for the last element */\n\t\t} else {\n\t\t\t/* Some other symbol */\n\t\t\tgoto _cleanup;\n\t\t}\n\n\t\twhile (n < count) {\n\t\t\tif (space[count-n-1].type != TOKEN_DOT) break;\n\t\t\tn++;\n\t\t\tif (n == count) break;\n\t\t\tif (space[count-n-1].type != TOKEN_IDENTIFIER) break;\n\t\t\tn++;\n\t\t}\n\n\t\tif (n <= count) {\n\t\t\t/* Now work forwards, starting from the current globals. */\n\t\t\tKrkValue root = OBJECT_VAL(krk_currentThread.module);\n\t\t\tint isGlobal = 1;\n\t\t\twhile (n > base) {\n\t\t\t\t/* And look at the potential fields for instances/classes */\n\t\t\t\tKrkValue next = findFromProperty(root, space[count-n]);\n\t\t\t\tif (IS_NONE(next)) {\n\t\t\t\t\t/* If we hit None, we found something invalid (or literally hit a None\n\t\t\t\t\t * object, but really the difference is minimal in this case: Nothing\n\t\t\t\t\t * useful to tab complete from here. */\n\t\t\t\t\tif (!isGlobal) goto _cleanup;\n\t\t\t\t\t/* Does this match a builtin? */\n\t\t\t\t\tif (!krk_tableGet_fast(&vm.builtins->fields,\n\t\t\t\t\t\tkrk_copyString(space[count-n].start,space[count-n].literalWidth), &next) || IS_NONE(next)) {\n\t\t\t\t\t\tgoto _cleanup;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tisGlobal = 0;\n\t\t\t\troot = next;\n\t\t\t\tn -= 2; /* To skip every other dot. */\n\t\t\t}\n\n\t\t\tif (isGlobal && n < count && (space[count-n-1].type == TOKEN_IMPORT || space[count-n-1].type == TOKEN_FROM)) {\n\t\t\t\tKrkInstance * modules = krk_newInstance(vm.baseClasses->objectClass);\n\t\t\t\troot = OBJECT_VAL(modules);\n\t\t\t\tkrk_push(root);\n\t\t\t\tfor (size_t i = 0; i < vm.modules.capacity; ++i) {\n\t\t\t\t\tKrkTableEntry * entry = &vm.modules.entries[i];\n\t\t\t\t\tif (IS_KWARGS(entry->key)) continue;\n\t\t\t\t\tkrk_attachNamedValue(&modules->fields, AS_CSTRING(entry->key), NONE_VAL());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* Now figure out what we're completing - did we already have a partial symbol name? */\n\t\t\tint length = (space[count-base].type == TOKEN_DOT) ? 0 : (space[count-base].length);\n\t\t\tisGlobal = isGlobal && (length != 0);\n\n\t\t\t/* Take the last symbol name from the chain and get its member list from dir() */\n\t\t\tstatic char * syn_krk_keywords[] = {\n\t\t\t\t\"and\",\"class\",\"def\",\"else\",\"for\",\"if\",\"in\",\"import\",\"del\",\n\t\t\t\t\"let\",\"not\",\"or\",\"return\",\"while\",\"try\",\"except\",\"raise\",\n\t\t\t\t\"continue\",\"break\",\"as\",\"from\",\"elif\",\"lambda\",\"with\",\"is\",\n\t\t\t\t\"pass\",\"assert\",\"yield\",\"finally\",\"async\",\"await\",\n\t\t\t\tNULL\n\t\t\t};\n\n\t\t\tKrkInstance * fakeKeywordsObject = NULL;\n\n\t\t\tfor (;;) {\n\t\t\t\tKrkValue dirList = krk_dirObject(1,(KrkValue[]){root},0);\n\t\t\t\tkrk_push(dirList);\n\t\t\t\tif (!IS_INSTANCE(dirList)) {\n\t\t\t\t\trender_error(\"Internal error while tab completing.\");\n\t\t\t\t\tgoto _cleanup;\n\t\t\t\t}\n\n\t\t\t\tfor (size_t i = 0; i < AS_LIST(dirList)->count; ++i) {\n\t\t\t\t\tKrkString * s = AS_STRING(AS_LIST(dirList)->values[i]);\n\t\t\t\t\tkrk_push(OBJECT_VAL(s));\n\t\t\t\t\tKrkToken asToken = {.start = s->chars, .literalWidth = s->length};\n\t\t\t\t\tKrkValue thisValue = findFromProperty(root, asToken);\n\t\t\t\t\tkrk_push(thisValue);\n\t\t\t\t\tif (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || IS_NATIVE(thisValue)) {\n\t\t\t\t\t\tsize_t allocSize = s->length + 2;\n\t\t\t\t\t\tchar * tmp = malloc(allocSize);\n\t\t\t\t\t\tsize_t len = snprintf(tmp, allocSize, \"%s(\", s->chars);\n\t\t\t\t\t\ts = krk_takeString(tmp, len);\n\t\t\t\t\t\tkrk_pop();\n\t\t\t\t\t\tkrk_push(OBJECT_VAL(s));\n\t\t\t\t\t}\n\n\t\t\t\t\t/* If this symbol is shorter than the current submatch, skip it. */\n\t\t\t\t\tif (length && (int)s->length < length) continue;\n\t\t\t\t\tif (!memcmp(s->chars, space[count-base].start, length)) {\n\t\t\t\t\t\tchar * tmp = malloc(strlen(args[arg]) + s->length + 1);\n\t\t\t\t\t\tsprintf(tmp,\"%s%s\", args[arg], s->chars + length);\n\t\t\t\t\t\tint type = Candidate_Normal;\n\t\t\t\t\t\tif (IS_OBJECT(root) && AS_OBJECT(root) == (KrkObj*)vm.builtins) {\n\t\t\t\t\t\t\ttype = Candidate_Builtin;\n\t\t\t\t\t\t} else if (IS_OBJECT(root) && AS_OBJECT(root) == (KrkObj*)fakeKeywordsObject) {\n\t\t\t\t\t\t\ttype = Candidate_Command;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tadd_candidate(tmp, type);\n\t\t\t\t\t\tfree(tmp);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/*\n\t\t\t\t * If the object we were scanning was the current module,\n\t\t\t\t * then we should also throw the builtins into the ring.\n\t\t\t\t */\n\t\t\t\tif (isGlobal && AS_OBJECT(root) == (KrkObj*)krk_currentThread.module) {\n\t\t\t\t\troot = OBJECT_VAL(vm.builtins);\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.builtins) {\n\t\t\t\t\tfakeKeywordsObject = krk_newInstance(vm.baseClasses->objectClass);\n\t\t\t\t\troot = OBJECT_VAL(fakeKeywordsObject);\n\t\t\t\t\tkrk_push(root);\n\t\t\t\t\tfor (char ** keyword = syn_krk_keywords; *keyword; keyword++) {\n\t\t\t\t\t\tkrk_attachNamedValue(&fakeKeywordsObject->fields, *keyword, NONE_VAL());\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t} else {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n_cleanup:\n\t\tfree(space);\n\t\tkrk_resetStack();\n\t}\n\n_accept_candidate:\n\tif (candidate_count == 0) {\n\t\tgoto done;\n\t}\n\n\tif (candidate_count == 1) {\n\t\t/* Only one completion possibility */\n\t\tstruct StringBuilder sb = {0};\n\t\tkrk_pushStringBuilderStr(&sb, buffer, start - buf);\n\t\tfor (unsigned int i = 0; i < strlen(candidates[0].text); ++i) {\n\t\t\tkrk_pushStringBuilder(&sb, candidates[0].text[i]);\n\t\t}\n\t\tkrk_pushStringBuilder(&sb, '\\0');\n\t\t/* Accept this new buffer data */\n\t\tfree(buffer);\n\t\tbuffer = sb.bytes;\n\t} else {\n\t\t/* Sort candidates */\n\t\tqsort(candidates, candidate_count, sizeof(candidates[0]), compare_candidate);\n\t\tint selection = 0;\n\n\t\tstruct StringBuilder cmd = {0};\n\n\t\t/* Try to prefill the buffer with a common substring. If this actually resulted\n\t\t * in some amount of prefill, then the selection will start in a special mode and\n\t\t * won't be filled with one of the candidates. */\n\t\tkrk_pushStringBuilderStr(&cmd, buffer, start - buf);\n\t\tfor (int i = 0; ; ++i) {\n\t\t\tif ((signed char)candidates[0].text[i] <= 0) goto _end_prefill; /* TODO continuation bytes */\n\t\t\tfor (int j = 1; j < candidate_count; ++j) {\n\t\t\t\tif (candidates[0].text[i] != candidates[j].text[i]) goto _end_prefill;\n\t\t\t}\n\t\t\tkrk_pushStringBuilder(&cmd, candidates[0].text[i]);\n\t\t}\n_end_prefill:\n\t\tif (cmd.length > strlen(buffer)) {\n\t\t\tselection = -2;\n\t\t} else {\n\t\t\t/* That didn't do anything new, so never mind. */\n\t\t\tkrk_discardStringBuilder(&cmd);\n\t\t}\n\n\t\twhile (1) {\n\t\t\t/* Print candidates in status bar */\n\t\t\tstruct StringBuilder sb = {0};\n\t\t\tint offset = 0;\n\t\t\tint selection_found = (selection < 0);\n\t\t\tfor (int i = 0; i < candidate_count; ++i) {\n\t\t\t\tchar * printed_candidate = candidates[i].text;\n\t\t\t\tif (_candidates_are_files) {\n\t\t\t\t\t/* If the candidates are files, then the candidate can be a full path\n\t\t\t\t\t * but we are only completing the last element, so we should restrict\n\t\t\t\t\t * what we show to just that final element. */\n\t\t\t\t\tfor (char * c = printed_candidate; *c; ++c) {\n\t\t\t\t\t\tif (c[0] == '/' && c[1] != '\\0') {\n\t\t\t\t\t\t\tprinted_candidate = c+1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t/* Otherwise, try to be smart about completing things within a\n\t\t\t\t\t * Kuroko expression, skipping over previous member accesses or\n\t\t\t\t\t * function calls. */\n\t\t\t\t\tfor (char * c = printed_candidate; *c; ++c) {\n\t\t\t\t\t\tif ((c[0] == '.' || c[0] == '(') && c[1] != '\\0') {\n\t\t\t\t\t\t\tprinted_candidate = c+1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (offset + 1 + (signed)display_width_of_string(printed_candidate) > global_config.term_width - 3) {\n\t\t\t\t\tif (selection_found) {\n\t\t\t\t\t\t/* We have run out of space but we have shown the current selected candidate.\n\t\t\t\t\t\t * Stop here and show that there are more candidates available off-screen. */\n\t\t\t\t\t\tkrk_pushStringBuilderFormat(&sb, \" %s>\", color_string(COLOR_STATUS_BG, COLOR_STATUS_FG));\n\t\t\t\t\t\tkrk_pushStringBuilderFormat(&sb, \"%s\", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\t/* We have run out of space but still haven't shown the current selected candidate,\n\t\t\t\t\t * so clear out the current status line and start over from here. */\n\t\t\t\t\tkrk_discardStringBuilder(&sb);\n\t\t\t\t\toffset = 0;\n\t\t\t\t}\n\n\t\t\t\t/* Did we find the selected candidate ? */\n\t\t\t\tif (i == selection) selection_found = 1;\n\n\t\t\t\t/* Put a space before any candidate that isn't the first on the line. */\n\t\t\t\tif (offset > 0) {\n\t\t\t\t\tkrk_pushStringBuilderFormat(&sb, \" \");\n\t\t\t\t\toffset++;\n\t\t\t\t}\n\n\t\t\t\t/* Color the candidate based on its type, unless it's the selected\n\t\t\t\t * candidate, in which case we use the search highlight colors. */\n\t\t\t\tconst char * color_fg =\n\t\t\t\t\tcandidates[i].type == Candidate_Normal ? COLOR_STATUS_FG :\n\t\t\t\t\t\tcandidates[i].type == Candidate_Command ? COLOR_KEYWORD :\n\t\t\t\t\t\t\tcandidates[i].type == Candidate_Builtin ? COLOR_TYPE : COLOR_STATUS_FG;\n\t\t\t\tconst char * colorString = (i == selection) ? color_string(COLOR_SEARCH_FG, COLOR_SEARCH_BG) : color_string(color_fg, COLOR_STATUS_BG);\n\n\t\t\t\tkrk_pushStringBuilderFormat(&sb, \"%s%s\", colorString, printed_candidate);\n\t\t\t\tkrk_pushStringBuilderFormat(&sb, \"%s\", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG));\n\n\t\t\t\toffset += display_width_of_string(printed_candidate);\n\t\t\t}\n\n\t\t\t/* Finally, render the message we built. */\n\t\t\tkrk_pushStringBuilder(&sb, '\\0');\n\t\t\trender_status_message(\"%s\", sb.bytes);\n\t\t\tkrk_discardStringBuilder(&sb);\n\n\t\t\t/* Reuse the previous string builder to fill the current selected\n\t\t\t * candidate into the input buffer and redraw the input buffer */\n\t\t\tif (selection >= 0) {\n\t\t\t\tkrk_pushStringBuilderStr(&cmd, buffer, start - buf);\n\t\t\t\tfor (unsigned int i = 0; i < strlen(candidates[selection].text); ++i) {\n\t\t\t\t\tkrk_pushStringBuilder(&cmd, candidates[selection].text[i]);\n\t\t\t\t}\n\t\t\t} else if (selection == -1) {\n\t\t\t\t/* With selection == -1, we want to redraw the original input. */\n\t\t\t\tkrk_pushStringBuilderStr(&cmd, buffer, strlen(buffer));\n\t\t\t}\n\t\t\t/* else selection == -2 and we already filled it before printing the candidate list */\n\t\t\tkrk_pushStringBuilder(&cmd, '\\0');\n\t\t\tcommand_buffer_deserialize(cmd.bytes);\n\t\t\trender_command_input_buffer();\n\t\t\tcmd = (struct StringBuilder){0};\n\n\t\t\tint k = 0;\n\t\t\twhile ((k = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\n\t\t\tswitch (k) {\n\t\t\t\t/* Move selection to next candidate. */\n\t\t\t\tcase KEY_RIGHT:\n\t\t\t\tcase KEY_TAB:\n\t\t\t\t\tselection = (selection < 0) ? 0 : selection + 1;\n\t\t\t\t\tif (selection == candidate_count) selection = -1;\n\t\t\t\t\tcommand_buffer_clear_before();\n\t\t\t\t\tcontinue;\n\t\t\t\t/* Move selection to previous candidate. */\n\t\t\t\tcase KEY_LEFT:\n\t\t\t\tcase KEY_SHIFT_TAB:\n\t\t\t\t\tselection = (selection >= 0 ? selection : candidate_count) - 1;\n\t\t\t\t\tcommand_buffer_clear_before();\n\t\t\t\t\tcontinue;\n\t\t\t\t/* Abort tab completion. Clear anything we put in the buffer,\n\t\t\t\t * and let the command mode figure out what to do next, which\n\t\t\t\t * is probably just exit command mode. */\n\t\t\t\tcase KEY_ESCAPE:\n\t\t\t\t\tbim_unget(27);\n\t\t\t\t\tcommand_buffer_clear_before();\n\t\t\t\t\tbreak;\n\t\t\t\t/* TODO: Consider supporting mouse input for picking a selection? */\n\t\t\t\tcase KEY_MOUSE:\n\t\t\t\t\teat_mouse();\n\t\t\t\t\tcommand_buffer_clear_before();\n\t\t\t\t\tcontinue;\n\t\t\t\tcase KEY_MOUSE_SGR:\n\t\t\t\t\teat_mouse_sgr();\n\t\t\t\t\tcommand_buffer_clear_before();\n\t\t\t\t\tcontinue;\n\t\t\t\t/* On anything else, accept the current candidate and then try\n\t\t\t\t * to let the command mode handle the entered text if it wasn't a special key. */\n\t\t\t\tdefault:\n\t\t\t\t\tif (k < 256) bim_unget(k);\n\t\t\t\t\t/* Replace the old buffer data with empty data, we have already written out\n\t\t\t\t\t * our completion to the command buffer. */\n\t\t\t\t\tfree(buffer);\n\t\t\t\t\tbuffer = strdup(\"\");\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Free candidates */\n\tfor (int i = 0; i < candidate_count; ++i) {\n\t\tfree(candidates[i].text);\n\t}\n\ndone:\n\tredraw_statusbar();\n\tfree(candidates);\n\tfree(buf);\n\treturn buffer;\n}\n\n/**\n * Draw the command buffer and any prefix.\n */\nvoid render_command_input_buffer(void) {\n\n\tif (!global_config.command_buffer) return;\n\n\t/* Place the cursor at the bottom of the screen */\n\tplace_cursor(1, global_config.term_height);\n\tpaint_line(COLOR_BG);\n\tset_colors(COLOR_ALT_FG, COLOR_BG);\n\n\t/* If there's a mode name to render, draw it first */\n\tint _left_gutter = 0;\n\tif (env->mode == MODE_LINE_SELECTION) {\n\t\t_left_gutter = printf(\"(LINE %d:%d)\",\n\t\t\t(env->start_line < env->line_no) ? env->start_line : env->line_no,\n\t\t\t(env->start_line < env->line_no) ? env->line_no : env->start_line);\n\t} else if (env->mode == MODE_COL_SELECTION) {\n\t\t_left_gutter = printf(\"(COL %d:%d %d)\",\n\t\t\t(env->start_line < env->line_no) ? env->start_line : env->line_no,\n\t\t\t(env->start_line < env->line_no) ? env->line_no : env->start_line,\n\t\t\t(env->sel_col));\n\t} else if (env->mode == MODE_CHAR_SELECTION) {\n\t\t_left_gutter = printf(\"(CHAR)\");\n\t}\n\n\t/* Figure out the cursor position and adjust the offset if necessary */\n\tint x = 2 + _left_gutter - global_config.command_offset;\n\tfor (int i = 0; i < global_config.command_col_no - 1; ++i) {\n\t\tchar_t * c = &global_config.command_buffer->text[i];\n\t\tx += c->display_width;\n\t}\n\tif (x > global_config.term_width - 1) {\n\t\tint diff = x - (global_config.term_width - 1);\n\t\tglobal_config.command_offset += diff;\n\t\tx -= diff;\n\t}\n\tif (x < 2 + _left_gutter) {\n\t\tint diff = (2 + _left_gutter) - x;\n\t\tglobal_config.command_offset -= diff;\n\t\tx += diff;\n\t}\n\n\t/* If the input buffer is horizontally shifted because it's too long, indicate that. */\n\tif (global_config.command_offset) {\n\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\tprintf(\"<\");\n\t} else {\n\t\t/* Otherwise indicate buffer mode (search / ?, or command :) */\n\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\tif (global_config.overlay_mode == OVERLAY_MODE_SEARCH) {\n\t\t\tprintf(global_config.search_direction == 0 ? \"?\" : \"/\");\n\t\t} else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) {\n\t\t\tprintf(\"_\");\n\t\t} else {\n\t\t\tprintf(\":\");\n\t\t}\n\t}\n\n\t/* Render the buffer */\n\trender_line(global_config.command_buffer, global_config.term_width-1-_left_gutter, global_config.command_offset, -1);\n\n\t/* Place and display the cursor */\n\tplace_cursor(x, global_config.term_height);\n\tshow_cursor();\n}\n\nBIM_ACTION(command_discard, 0,\n\t\"Discard the input buffer and cancel command or search.\"\n,void) {\n\tfree(global_config.command_buffer);\n\tglobal_config.command_buffer = NULL;\n\tif (global_config.overlay_mode == OVERLAY_MODE_SEARCH) {\n\t\tenv->line_no = global_config.prev_line;\n\t\tenv->col_no  = global_config.prev_col;\n\t\t/* Unhighlight search matches */\n\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\t\tenv->lines[i]->text[j].flags &= (~FLAG_SEARCH);\n\t\t\t}\n\t\t\trehighlight_search(env->lines[i]);\n\t\t}\n\t}\n\tglobal_config.overlay_mode = OVERLAY_MODE_NONE;\n\tredraw_all();\n\t/* TODO exit some other modes? */\n}\n\nBIM_ACTION(enter_command, 0,\n\t\"Enter command input mode.\"\n,void) {\n\tglobal_config.overlay_mode = OVERLAY_MODE_COMMAND;\n\n\tglobal_config.command_offset = 0;\n\tglobal_config.command_col_no = 1;\n\n\tif (global_config.command_buffer) {\n\t\tfree(global_config.command_buffer);\n\t}\n\n\tglobal_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1);\n\tglobal_config.command_buffer->available = 32;\n\n\tglobal_config.command_syn_back = env->syntax;\n\tglobal_config.command_syn = find_syntax_calculator(\"bimcmd\");\n\n\tglobal_config.history_point = -1;\n\n\trender_command_input_buffer();\n}\n\nstatic char * command_buffer_to_utf8(void) {\n\tsize_t size = 0;\n\tfor (int i = 0; i < global_config.command_buffer->actual; ++i) {\n\t\tchar tmp[8] = {0};\n\t\tsize += to_eight(global_config.command_buffer->text[i].codepoint, tmp);\n\t}\n\tchar * tmp = malloc(size + 8); /* for overflow from to_eight */\n\tchar * t = tmp;\n\tfor (int i = 0; i < global_config.command_buffer->actual; ++i) {\n\t\tt += to_eight(global_config.command_buffer->text[i].codepoint, t);\n\t}\n\t*t = '\\0';\n\treturn tmp;\n}\n\nBIM_ACTION(command_accept, 0,\n\t\"Accept the command input and run the requested command.\"\n,void) {\n\t/* Convert command buffer to UTF-8 char-array string */\n\tchar * tmp = command_buffer_to_utf8();\n\n\t/* Free the original editing buffer */\n\tfree(global_config.command_buffer);\n\tglobal_config.command_buffer = NULL;\n\n\t/* Run the converted command */\n\tglobal_config.break_from_selection = 0;\n\tinsert_command_history(command_history, tmp);\n\tglobal_config.overlay_mode = OVERLAY_MODE_NONE;\n\tprocess_command(tmp);\n\tfree(tmp);\n\n\tif (!global_config.break_from_selection && env->mode != MODE_DIRECTORY_BROWSE) {\n\t\tif (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION || env->mode == MODE_COL_SELECTION) {\n\t\t\trecalculate_selected_lines();\n\t\t}\n\t\tenv->mode = MODE_NORMAL;\n\t}\n\n\t/* Leave command mode */\n}\n\nBIM_ACTION(command_word_delete, 0,\n\t\"Delete the previous word from the input buffer.\"\n,void) {\n\t_syn_command();\n\twhile (global_config.command_col_no > 1 &&\n\t       (global_config.command_buffer->text[global_config.command_col_no-2].codepoint == ' ' ||\n\t        global_config.command_buffer->text[global_config.command_col_no-2].codepoint == '/')) {\n\t\tline_delete(global_config.command_buffer, global_config.command_col_no - 1, -1);\n\t\tglobal_config.command_col_no--;\n\t}\n\twhile (global_config.command_col_no > 1 &&\n\t       global_config.command_buffer->text[global_config.command_col_no-2].codepoint != ' ' &&\n\t       global_config.command_buffer->text[global_config.command_col_no-2].codepoint != '/') {\n\t\tline_delete(global_config.command_buffer, global_config.command_col_no - 1, -1);\n\t\tglobal_config.command_col_no--;\n\t}\n\t_syn_restore();\n}\n\nBIM_ACTION(command_tab_complete_buffer, 0,\n\t\"Complete command names and arguments in the input buffer.\"\n,void) {\n\t/* command_tab_complete should probably just be adjusted to deal with the buffer... */\n\tchar * tmp = command_buffer_serialize();\n\ttmp = command_tab_complete(tmp);\n\tcommand_buffer_deserialize(tmp);\n}\n\nBIM_ACTION(command_backspace, 0,\n\t\"Erase the character before the cursor in the input buffer.\"\n,void) {\n\tif (global_config.command_col_no <= 1) {\n\t\tif (global_config.command_buffer->actual == 0) {\n\t\t\tcommand_discard();\n\t\t}\n\t\treturn;\n\t}\n\t_syn_command();\n\tline_delete(global_config.command_buffer, global_config.command_col_no - 1, -1);\n\t_syn_restore();\n\tglobal_config.command_col_no--;\n\tglobal_config.command_offset = 0;\n}\n\nstatic void _restore_history(unsigned char **which_history, int point) {\n\tunsigned char * t = which_history[point];\n\tglobal_config.command_col_no = 1;\n\tglobal_config.command_buffer->actual = 0;\n\t_syn_command();\n\tuint32_t state = 0;\n\tuint32_t c = 0;\n\twhile (*t) {\n\t\tif (!decode(&state, &c, *t)) {\n\t\t\tchar_t _c = {codepoint_width(c), 0, c};\n\t\t\tglobal_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1);\n\t\t\tglobal_config.command_col_no++;\n\t\t} else if (state == UTF8_REJECT) state = 0;\n\t\tt++;\n\t}\n\t_syn_restore();\n}\n\nstatic void _scroll_history(int direction, unsigned char **which_history, int * which_point) {\n\tif (direction == -1) {\n\t\tif (which_history[*which_point+1]) {\n\t\t\t_restore_history(which_history, *which_point+1);\n\t\t\t(*which_point)++;\n\t\t}\n\t} else {\n\t\tif (*which_point > 0) {\n\t\t\t(*which_point)--;\n\t\t\t_restore_history(which_history, *which_point);\n\t\t} else {\n\t\t\t*which_point = -1;\n\t\t\tglobal_config.command_col_no = 1;\n\t\t\tglobal_config.command_buffer->actual = 0;\n\t\t}\n\t}\n}\n\nBIM_ACTION(command_scroll_history, ARG_IS_CUSTOM,\n\t\"Scroll through command input history.\"\n,int direction) {\n\t_scroll_history(direction, command_history, &global_config.history_point);\n}\n\nBIM_ACTION(command_scroll_search_history, ARG_IS_CUSTOM,\n\t\"Scroll through search input history.\"\n,int direction) {\n\t_scroll_history(direction, search_history, &global_config.search_point);\n}\n\nBIM_ACTION(command_word_left, 0,\n\t\"Move to the start of the previous word in the input buffer.\"\n,void) {\n\tif (global_config.command_col_no > 1) {\n\t\tdo {\n\t\t\tglobal_config.command_col_no--;\n\t\t} while (isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no > 1);\n\t\tif (global_config.command_col_no == 1) return;\n\t\tdo {\n\t\t\tglobal_config.command_col_no--;\n\t\t} while (!isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no > 1);\n\t\tif (isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no < global_config.command_buffer->actual) global_config.command_col_no++;\n\t}\n}\n\nBIM_ACTION(command_word_right, 0,\n\t\"Move to the start of the next word in the input buffer.\"\n,void) {\n\tif (global_config.command_col_no < global_config.command_buffer->actual) {\n\t\tdo {\n\t\t\tglobal_config.command_col_no++;\n\t\t\tif (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; break; }\n\t\t} while (!isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no <= global_config.command_buffer->actual);\n\t\tdo {\n\t\t\tglobal_config.command_col_no++;\n\t\t\tif (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; break; }\n\t\t} while (isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no <= global_config.command_buffer->actual);\n\t\tif (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; }\n\t}\n}\n\nBIM_ACTION(command_cursor_left, 0,\n\t\"Move the cursor one character left in the input buffer.\"\n,void) {\n\tif (global_config.command_col_no > 1) global_config.command_col_no--;\n}\n\nBIM_ACTION(command_cursor_right, 0,\n\t\"Move the cursor one character right in the input buffer.\"\n,void) {\n\tif (global_config.command_col_no < global_config.command_buffer->actual+1) global_config.command_col_no++;\n}\n\nBIM_ACTION(command_cursor_home, 0,\n\t\"Move the cursor to the start of the input buffer.\"\n,void) {\n\tglobal_config.command_col_no = 1;\n}\n\nBIM_ACTION(command_cursor_end, 0,\n\t\"Move the cursor to the end of the input buffer.\"\n,void) {\n\tglobal_config.command_col_no = global_config.command_buffer->actual + 1;\n}\n\nBIM_ACTION(eat_mouse, 0,\n\t\"(temporary) Read, but ignore mouse input.\"\n,void) {\n\tbim_getch();\n\tbim_getch();\n\tbim_getch();\n}\n\nBIM_ACTION(command_insert_char, ARG_IS_INPUT,\n\t\"Insert one character into the input buffer.\"\n,int c) {\n\tchar_t _c = {codepoint_width(c), 0, c};\n\t_syn_command();\n\tglobal_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1);\n\t_syn_restore();\n\tglobal_config.command_col_no++;\n}\n\n/**\n * Determine whether a string should be searched\n * case-sensitive or not based on whether it contains\n * any upper-case letters.\n */\nint smart_case(uint32_t * str) {\n\tif (!global_config.smart_case) return 0;\n\n\tfor (uint32_t * s = str; *s; ++s) {\n\t\tif (tolower(*s) != (int)*s) {\n\t\t\treturn 0;\n\t\t}\n\t}\n\treturn 1;\n}\n\n/**\n * Search forward from the given cursor position\n * to find a basic search match.\n *\n * This could be more complicated...\n */\nvoid find_match(int from_line, int from_col, int * out_line, int * out_col, uint32_t * str, int * matchlen) {\n\tint col = from_col;\n\n\tint ignorecase = smart_case(str);\n\n\tfor (int i = from_line; i <= env->line_count; ++i) {\n\t\tline_t * line = env->lines[i - 1];\n\n\t\tint j = col - 1;\n\t\twhile (j < line->actual + 1) {\n\t\t\tif (subsearch_matches(line, j, str, ignorecase, matchlen)) {\n\t\t\t\t*out_line = i;\n\t\t\t\t*out_col = j + 1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tj++;\n\t\t}\n\t\tcol = 0;\n\t}\n}\n\n/**\n * Search backwards for matching string.\n */\nvoid find_match_backwards(int from_line, int from_col, int * out_line, int * out_col, uint32_t * str) {\n\tint col = from_col;\n\n\tint ignorecase = smart_case(str);\n\n\tfor (int i = from_line; i >= 1; --i) {\n\t\tline_t * line = env->lines[i-1];\n\n\t\tint j = col - 1;\n\t\twhile (j > -1) {\n\t\t\tif (subsearch_matches(line, j, str, ignorecase, NULL)) {\n\t\t\t\t*out_line = i;\n\t\t\t\t*out_col = j + 1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tj--;\n\t\t}\n\t\tcol = (i > 1) ? (env->lines[i-2]->actual + 1) : -1;\n\t}\n}\n\n/**\n * Re-mark search matches while editing text.\n * This gets called after recalculate_syntax, so it works as we're typing or\n * whenever syntax calculation would redraw other lines.\n * XXX There's a bunch of code duplication between the (now three) search match functions.\n *     and search should be improved to support regex anyway, so this needs to be fixed.\n */\nvoid rehighlight_search(line_t * line) {\n\tif (!global_config.search) return;\n\tint j = 0;\n\twhile (j < line->actual) {\n\t\tline->text[j].flags &= ~(FLAG_SEARCH);\n\t\tj++;\n\t}\n\tint ignorecase = smart_case(global_config.search);\n\tj = 0;\n\twhile (j < line->actual) {\n\t\tint matchlen = 0;\n\t\tif (subsearch_matches(line, j, global_config.search, ignorecase, &matchlen)) {\n\t\t\tfor (int i = j; matchlen > 0; ++i, matchlen--) {\n\t\t\t\tline->text[i].flags |= FLAG_SEARCH;\n\t\t\t}\n\t\t}\n\t\tj++;\n\t}\n}\n\n/**\n * Draw the matched search result.\n */\nvoid draw_search_match(uint32_t * buffer, int redraw_buffer) {\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\tfor (int j = 0; j < env->lines[i]->actual; ++j) {\n\t\t\tenv->lines[i]->text[j].flags &= (~FLAG_SEARCH);\n\t\t}\n\t}\n\tint my_index = 0, match_count = 0;\n\tint line = -1, col = -1, _line = 1, _col = 1;\n\tdo {\n\t\tint matchlen;\n\t\tfind_match(_line, _col, &line, &col, buffer, &matchlen);\n\t\tif (line != -1) {\n\t\t\tline_t * l = env->lines[line-1];\n\t\t\tfor (int i = col; matchlen > 0; ++i, --matchlen) {\n\t\t\t\tl->text[i-1].flags |= FLAG_SEARCH;\n\t\t\t}\n\t\t\tmatch_count += 1;\n\t\t\tif (col == env->col_no && line == env->line_no) my_index = match_count;\n\t\t}\n\t\t_line = line;\n\t\t_col  = col+1;\n\t\tline  = -1;\n\t\tcol   = -1;\n\t} while (_line != -1);\n\tredraw_text();\n\tplace_cursor_actual();\n\tredraw_statusbar();\n\tredraw_commandline();\n\tset_fg_color(COLOR_ALT_FG);\n\tprintf(\"[%d/%d] \", my_index, match_count);\n\tset_fg_color(COLOR_KEYWORD);\n\tprintf(redraw_buffer == 1 ? \"/\" : \"?\");\n\tset_fg_color(COLOR_FG);\n\tuint32_t * c = buffer;\n\twhile (*c) {\n\t\tchar tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */\n\t\tto_eight(*c, tmp);\n\t\tprintf(\"%s\", tmp);\n\t\tc++;\n\t}\n}\n\nBIM_ACTION(start_file_search, 0, \"Search for open files and switch tabs quickly.\",void) {\n\tglobal_config.overlay_mode = OVERLAY_MODE_FILESEARCH;\n\n\tglobal_config.command_offset = 0;\n\tglobal_config.command_col_no = 1;\n\n\tif (global_config.command_buffer) {\n\t\tfree(global_config.command_buffer);\n\t}\n\n\tglobal_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1);\n\tglobal_config.command_buffer->available = 32;\n\n\tglobal_config.command_syn_back = env->syntax;\n\tglobal_config.command_syn = NULL; /* uh, dunno for now */\n\n\trender_command_input_buffer();\n}\n\nBIM_ACTION(file_search_accept, 0, \"Open the requested tab\",void) {\n\tif (!global_config.command_buffer->actual) {\n\t\tgoto _finish;\n\t}\n\n\t/* See if the command buffer matches any open buffers */\n\tbuffer_t * match = NULL;\n\tfor (int i = global_config.tab_offset; i < buffers_len; i++) {\n\t\tbuffer_t * _env = buffers[i];\n\n\t\tif (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) {\n\t\t\tif (global_config.command_buffer->actual) {\n\t\t\t\tchar * f = _env->file_name ? file_basename(_env->file_name) : \"\";\n\t\t\t\t/* TODO: Support unicode input here; needs conversion */\n\t\t\t\tint i = 0;\n\t\t\t\tfor (; i < global_config.command_buffer->actual &&\n\t\t\t\t      f[i] == global_config.command_buffer->text[i].codepoint; ++i);\n\t\t\t\tif (global_config.command_buffer->actual == i) {\n\t\t\t\t\tmatch = _env;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (match) {\n\t\tenv = match;\n\t\tif (left_buffer && (left_buffer != env && right_buffer != env)) unsplit();\n\t}\n\n_finish:\n\t/* Free the original editing buffer */\n\tfree(global_config.command_buffer);\n\tglobal_config.command_buffer = NULL;\n\n\t/* Leave command mode */\n\tglobal_config.overlay_mode = OVERLAY_MODE_NONE;\n\n\tredraw_all();\n}\n\nBIM_ACTION(enter_search, ARG_IS_CUSTOM,\n\t\"Enter search mode.\"\n,int direction) {\n\tglobal_config.overlay_mode = OVERLAY_MODE_SEARCH;\n\n\tglobal_config.command_offset = 0;\n\tglobal_config.command_col_no = 1;\n\n\tglobal_config.prev_line = env->line_no;\n\tglobal_config.prev_col  = env->col_no;\n\tglobal_config.prev_coffset = env->coffset;\n\tglobal_config.prev_offset = env->offset;\n\tglobal_config.search_direction = direction;\n\n\tif (global_config.command_buffer) {\n\t\tfree(global_config.command_buffer);\n\t}\n\n\tglobal_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1);\n\tglobal_config.command_buffer->available = 32;\n\n\tglobal_config.command_syn_back = env->syntax;\n\tglobal_config.command_syn = NULL; /* Disable syntax highlighting in search; maybe use buffer's mode instead? */\n\n\trender_command_input_buffer();\n}\n\nBIM_ACTION(search_accept, 0,\n\t\"Accept the search term and return to the previous mode.\"\n,void) {\n\t/* Store the accepted search */\n\tif (!global_config.command_buffer->actual) {\n\t\tif (global_config.search) {\n\t\t\tsearch_next();\n\t\t}\n\t\tgoto _finish;\n\t}\n\tif (global_config.search) {\n\t\tfree(global_config.search);\n\t}\n\n\tglobal_config.search = malloc((global_config.command_buffer->actual + 1) * sizeof(uint32_t));\n\tfor (int i = 0; i < global_config.command_buffer->actual; ++i) {\n\t\tglobal_config.search[i] = global_config.command_buffer->text[i].codepoint;\n\t}\n\tglobal_config.search[global_config.command_buffer->actual] = 0;\n\n\tchar * tmp = command_buffer_to_utf8();\n\tinsert_command_history(search_history, tmp);\n\tfree(tmp);\n\n_finish:\n\t/* Free the original editing buffer */\n\tfree(global_config.command_buffer);\n\tglobal_config.command_buffer = NULL;\n\n\t/* Leave command mode */\n\tglobal_config.overlay_mode = OVERLAY_MODE_NONE;\n\n\tif (global_config.search) draw_search_match(global_config.search, global_config.search_direction);\n}\n\n/**\n * Find the next search result, or loop back around if at the end.\n */\nBIM_ACTION(search_next, 0,\n\t\"Jump to the next search match.\"\n,void) {\n\tif (!global_config.search) return;\n\tif (env->coffset) env->coffset = 0;\n\tint line = -1, col = -1, wrapped = 0;\n\tfind_match(env->line_no, env->col_no+1, &line, &col, global_config.search, NULL);\n\n\tif (line == -1) {\n\t\tif (!global_config.search_wraps) return;\n\t\tfind_match(1,1, &line, &col, global_config.search, NULL);\n\t\tif (line == -1) return;\n\t\twrapped = 1;\n\t}\n\n\tenv->col_no = col;\n\tenv->line_no = line;\n\tset_preferred_column();\n\tdraw_search_match(global_config.search, 1);\n\tif (wrapped) {\n\t\tset_fg_color(COLOR_ALT_FG);\n\t\tprintf(\" (search wrapped to top)\");\n\t}\n}\n\n/**\n * Find the previous search result, or loop to the end of the file.\n */\nBIM_ACTION(search_prev, 0,\n\t\"Jump to the preceding search match.\"\n,void) {\n\tif (!global_config.search) return;\n\tif (env->coffset) env->coffset = 0;\n\tint line = -1, col = -1, wrapped = 0;\n\tfind_match_backwards(env->line_no, env->col_no-1, &line, &col, global_config.search);\n\n\tif (line == -1) {\n\t\tif (!global_config.search_wraps) return;\n\t\tfind_match_backwards(env->line_count, env->lines[env->line_count-1]->actual, &line, &col, global_config.search);\n\t\tif (line == -1) return;\n\t\twrapped = 1;\n\t}\n\n\tenv->col_no = col;\n\tenv->line_no = line;\n\tset_preferred_column();\n\tdraw_search_match(global_config.search, 0);\n\tif (wrapped) {\n\t\tset_fg_color(COLOR_ALT_FG);\n\t\tprintf(\" (search wrapped to bottom)\");\n\t}\n}\n\n/**\n * Find the matching paren for this one.\n *\n * This approach skips having to do its own syntax parsing\n * to deal with, eg., erroneous parens in comments. It does\n * this by finding the matching paren with the same flag\n * value, thus parens in strings will match, parens outside\n * of strings will match, but parens in strings won't\n * match parens outside of strings and so on.\n */\nvoid find_matching_paren(int * out_line, int * out_col, int in_col) {\n\tif (env->col_no - in_col + 1 > env->lines[env->line_no-1]->actual) {\n\t\treturn; /* Invalid cursor position */\n\t}\n\n\tint paren_match = 0;\n\tint direction = 0;\n\tint start = env->lines[env->line_no-1]->text[env->col_no-in_col].codepoint;\n\tint flags = env->lines[env->line_no-1]->text[env->col_no-in_col].flags & 0x1F;\n\tint count = 0;\n\n\tfor (int i = 0; global_config.paren_pairs[i]; ++i) {\n\t\tif ((uint32_t)start == global_config.paren_pairs[i]) {\n\t\t\tdirection = (i % 2 == 0) ? 1 : -1;\n\t\t\tparen_match = global_config.paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!paren_match) return;\n\n\t/* Scan for match */\n\tint line = env->line_no;\n\tint col  = env->col_no - in_col + 1;\n\n\tdo {\n\t\twhile (col > 0 && col < env->lines[line-1]->actual + 1) {\n\t\t\t/* Only match on same syntax */\n\t\t\tif ((env->lines[line-1]->text[col-1].flags & 0x1F) == flags) {\n\t\t\t\t/* Count up on same direction */\n\t\t\t\tif (env->lines[line-1]->text[col-1].codepoint == start) count++;\n\t\t\t\t/* Count down on opposite direction */\n\t\t\t\tif (env->lines[line-1]->text[col-1].codepoint == paren_match) {\n\t\t\t\t\tcount--;\n\t\t\t\t\t/* When count == 0 we have a match */\n\t\t\t\t\tif (count == 0) goto _match_found;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcol += direction;\n\t\t}\n\n\t\tline += direction;\n\n\t\t/* Reached first/last line with no match */\n\t\tif (line == 0 || line == env->line_count + 1) {\n\t\t\treturn;\n\t\t}\n\n\t\t/* Reset column to start/end of line, depending on direction */\n\t\tif (direction > 0) {\n\t\t\tcol = 1;\n\t\t} else {\n\t\t\tcol = env->lines[line-1]->actual;\n\t\t}\n\t} while (1);\n\n_match_found:\n\t*out_line = line;\n\t*out_col  = col;\n}\n\n/**\n * Switch to the left split view\n * (Primarily to handle cases where the left and right are the same buffer)\n */\nBIM_ACTION(use_left_buffer, 0,\n\t\"Switch to the left split view.\"\n,void) {\n\tif (left_buffer == right_buffer && env->left != 0) {\n\t\tview_right_offset = env->offset;\n\t\tenv->width = env->left;\n\t\tenv->left = 0;\n\t\tenv->offset = view_left_offset;\n\t}\n\tenv = left_buffer;\n\tupdate_title();\n}\n\n/**\n * Switch to the right split view\n * (Primarily to handle cases where the left and right are the same buffer)\n */\nBIM_ACTION(use_right_buffer, 0,\n\t\"Switch to the right split view.\"\n,void) {\n\tif (left_buffer == right_buffer && env->left == 0) {\n\t\tview_left_offset = env->offset;\n\t\tenv->left = env->width;\n\t\tenv->width = global_config.term_width - env->width;\n\t\tenv->offset = view_right_offset;\n\t}\n\tenv = right_buffer;\n\tupdate_title();\n}\n\nvoid handle_common_mouse(int buttons, int x, int y) {\n\tif (buttons == 64) {\n\t\t/* Scroll up */\n\t\tif (global_config.shift_scrolling) {\n\t\t\tenv->loading = 1;\n\t\t\tint shifted = 0;\n\t\t\tfor (int i = 0; i < global_config.scroll_amount; ++i) {\n\t\t\t\tif (env->offset > 0) {\n\t\t\t\t\tenv->offset--;\n\t\t\t\t\tif (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - global_config.tabs_visible - global_config.cursor_padding) {\n\t\t\t\t\t\tcursor_up();\n\t\t\t\t\t}\n\t\t\t\t\tshifted++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tenv->loading = 0;\n\t\t\tif (!shifted) return;\n\t\t\tif (global_config.can_scroll && !left_buffer) {\n\t\t\t\tif (!global_config.can_insert) {\n\t\t\t\t\tshift_down(shifted);\n\t\t\t\t\tredraw_tabbar();\n\t\t\t\t} else {\n\t\t\t\t\tinsert_lines_at(global_config.tabs_visible ? 2 : 1, shifted);\n\t\t\t\t}\n\t\t\t\tfor (int i = 0; i < shifted; ++i) {\n\t\t\t\t\tredraw_line(env->offset+i);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tredraw_tabbar();\n\t\t\t\tredraw_text();\n\t\t\t}\n\t\t\tredraw_statusbar();\n\t\t\tredraw_commandline();\n\t\t\tplace_cursor_actual();\n\t\t} else {\n\t\t\tfor (int i = 0; i < global_config.scroll_amount; ++i) {\n\t\t\t\tcursor_up();\n\t\t\t}\n\t\t}\n\t\treturn;\n\t} else if (buttons == 65) {\n\t\t/* Scroll down */\n\t\tif (global_config.shift_scrolling) {\n\t\t\tenv->loading = 1;\n\t\t\tint shifted = 0;\n\t\t\tfor (int i = 0; i < global_config.scroll_amount; ++i) {\n\t\t\t\tif (env->offset < env->line_count-1) {\n\t\t\t\t\tenv->offset++;\n\t\t\t\t\tint e = (env->offset == 0) ? env->offset : env->offset + global_config.cursor_padding;\n\t\t\t\t\tif (env->line_no <= e) {\n\t\t\t\t\t\tcursor_down();\n\t\t\t\t\t}\n\t\t\t\t\tshifted++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tenv->loading = 0;\n\t\t\tif (!shifted) return;\n\t\t\tif (global_config.can_scroll && !left_buffer) {\n\t\t\t\tif (!global_config.can_insert) {\n\t\t\t\t\tshift_up(shifted);\n\t\t\t\t\tredraw_tabbar();\n\t\t\t\t} else {\n\t\t\t\t\tdelete_lines_at(global_config.tabs_visible ? 2 : 1, shifted);\n\t\t\t\t}\n\t\t\t\tint l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible;\n\t\t\t\tfor (int i = 0; i < shifted; ++i) {\n\t\t\t\t\tif (env->offset + l - i < env->line_count + 1) {\n\t\t\t\t\t\tredraw_line(env->offset + l-1-i);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdraw_excess_line(l - 1 - i);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tredraw_tabbar();\n\t\t\t\tredraw_text();\n\t\t\t}\n\t\t\tredraw_statusbar();\n\t\t\tredraw_commandline();\n\t\t\tplace_cursor_actual();\n\t\t} else {\n\t\t\tfor (int i = 0; i < global_config.scroll_amount; ++i) {\n\t\t\t\tcursor_down();\n\t\t\t}\n\t\t}\n\t\treturn;\n\t} else if (buttons == 3) {\n\t\t/* Move cursor to position */\n\n\t\tif (x < 0) return;\n\t\tif (y < 0) return;\n\n\t\tif (y == 1 && global_config.tabs_visible) {\n\t\t\t/* Pick from tabs */\n\t\t\tif (env->mode != MODE_NORMAL && env->mode != MODE_INSERT) return; /* Don't let the tab be switched in other modes for now */\n\t\t\tint _x = 0;\n\t\t\tif (global_config.tab_offset) _x = 1;\n\t\t\tif (global_config.tab_offset && _x >= x) {\n\t\t\t\tglobal_config.tab_offset--;\n\t\t\t\tredraw_tabbar();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (int i = global_config.tab_offset; i < buffers_len; i++) {\n\t\t\t\tbuffer_t * _env = buffers[i];\n\t\t\t\tchar tmp[64];\n\t\t\t\tint size = 0;\n\t\t\t\tint filled = draw_tab_name(_env, tmp, global_config.term_width - _x, &size);\n\t\t\t\t_x += size;\n\t\t\t\tif (_x >= x) {\n\t\t\t\t\tif (left_buffer && buffers[i] != left_buffer && buffers[i] != right_buffer) unsplit();\n\t\t\t\t\tenv = buffers[i];\n\t\t\t\t\tredraw_all();\n\t\t\t\t\tupdate_title();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (filled) break;\n\t\t\t}\n\t\t\tif (x > _x && global_config.tab_offset < buffers_len - 1) {\n\t\t\t\tglobal_config.tab_offset++;\n\t\t\t\tredraw_tabbar();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (env->mode == MODE_NORMAL || env->mode == MODE_INSERT) {\n\t\t\tint current_mode = env->mode;\n\t\t\tif (x < env->left && env == right_buffer) {\n\t\t\t\tuse_left_buffer();\n\t\t\t} else if (x > env->width && env == left_buffer) {\n\t\t\t\tuse_right_buffer();\n\t\t\t}\n\t\t\tenv->mode = current_mode;\n\t\t\tredraw_all();\n\t\t}\n\n\t\tif (env->left) {\n\t\t\tx -= env->left;\n\t\t}\n\n\t\t/* Figure out y coordinate */\n\t\tint line_no = y + env->offset - global_config.tabs_visible;\n\t\tint col_no = -1;\n\n\t\tif (line_no > env->line_count) {\n\t\t\tline_no = env->line_count;\n\t\t}\n\n\t\tif (line_no != env->line_no) {\n\t\t\tenv->coffset = 0;\n\t\t}\n\n\t\t/* Account for the left hand gutter */\n\t\tint num_size = num_width() + gutter_width();\n\t\tint _x = num_size - (line_no == env->line_no ? env->coffset : 0);\n\n\t\t/* Determine where the cursor is physically */\n\t\tfor (int i = 0; i < env->lines[line_no-1]->actual; ++i) {\n\t\t\tchar_t * c = &env->lines[line_no-1]->text[i];\n\t\t\t_x += c->display_width;\n\t\t\tif (_x > x-1) {\n\t\t\t\tcol_no = i+1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (col_no == -1 || col_no > env->lines[line_no-1]->actual) {\n\t\t\tcol_no = env->lines[line_no-1]->actual;\n\t\t}\n\n\t\tenv->line_no = line_no;\n\t\tenv->col_no = col_no;\n\t\tset_history_break();\n\t\tset_preferred_column();\n\t\tredraw_statusbar();\n\t\tplace_cursor_actual();\n\t}\n\treturn;\n}\n\n/**\n * Handle mouse event\n */\nBIM_ACTION(handle_mouse, 0,\n\t\"Process mouse actions.\"\n,void) {\n\tint buttons = bim_getch() - 32;\n\tint x = bim_getch() - 32;\n\tint y = bim_getch() - 32;\n\n\thandle_common_mouse(buttons, x, y);\n}\n\nBIM_ACTION(eat_mouse_sgr, 0,\n\t\"Receive, but do not process, mouse actions.\"\n,void) {\n\tdo {\n\t\tint _c = bim_getch();\n\t\tif (_c == -1 || _c == 'm' || _c == 'M') break;\n\t} while (1);\n}\n\nBIM_ACTION(handle_mouse_sgr, 0,\n\t\"Process SGR-style mouse actions.\"\n,void) {\n\tint values[3] = {0};\n\tchar tmp[512] = {0};\n\tchar * c = tmp;\n\tint buttons = 0;\n\tdo {\n\t\tint _c = bim_getch();\n\t\tif (_c == -1) {\n\t\t\tbreak;\n\t\t}\n\t\tif (_c == 'm') {\n\t\t\tbuttons = 3;\n\t\t\tbreak;\n\t\t} else if (_c == 'M') {\n\t\t\tbuttons = 0;\n\t\t\tbreak;\n\t\t}\n\t\t*c = _c;\n\t\t++c;\n\t} while (1);\n\tchar * j = tmp;\n\tchar * last = tmp;\n\tint i = 0;\n\twhile (*j) {\n\t\tif (*j == ';') {\n\t\t\t*j = '\\0';\n\t\t\tvalues[i] = atoi(last);\n\t\t\tlast = j+1;\n\t\t\ti++;\n\t\t\tif (i == 3) break;\n\t\t}\n\t\tj++;\n\t}\n\tif (last && i < 3) values[i] = atoi(last);\n\tif (buttons != 3) {\n\t\tbuttons = values[0];\n\t}\n\tint x = values[1];\n\tint y = values[2];\n\n\thandle_common_mouse(buttons, x, y);\n}\n\n\nBIM_ACTION(insert_char, ARG_IS_INPUT | ACTION_IS_RW,\n\t\"Insert one character.\"\n,unsigned int c) {\n\tif (!c) {\n\t\trender_error(\"Inserted nil byte?\");\n\t\treturn;\n\t}\n\tchar_t _c;\n\t_c.codepoint = c;\n\t_c.flags = 0;\n\t_c.display_width = codepoint_width(c);\n\tline_t * line  = env->lines[env->line_no - 1];\n\tline_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no - 1);\n\tif (line != nline) {\n\t\tenv->lines[env->line_no - 1] = nline;\n\t}\n\tenv->col_no += 1;\n\tset_preferred_column();\n\tset_modified();\n}\n\nBIM_ACTION(replace_char, ARG_IS_PROMPT | ACTION_IS_RW,\n\t\"Replace a single character.\"\n,unsigned int c) {\n\tif (env->col_no < 1 || env->col_no > env->lines[env->line_no-1]->actual) return;\n\n\tif (c >= KEY_ESCAPE) {\n\t\trender_error(\"Invalid key for replacement\");\n\t\treturn;\n\t}\n\n\tchar_t _c;\n\t_c.codepoint = c;\n\t_c.flags = 0;\n\t_c.display_width = codepoint_width(c);\n\n\tline_replace(env->lines[env->line_no-1], _c, env->col_no-1, env->line_no-1);\n\n\tredraw_line(env->line_no-1);\n\tset_modified();\n}\n\nBIM_ACTION(undo_history, ACTION_IS_RW,\n\t\"Undo history until the last breakpoint.\"\n,void) {\n\tif (!global_config.history_enabled) return;\n\n\tenv->loading = 1;\n\thistory_t * e = env->history;\n\n\tif (e->type == HISTORY_SENTINEL) {\n\t\tenv->loading = 0;\n\t\trender_commandline_message(\"Already at oldest change\");\n\t\treturn;\n\t}\n\n\tint count_chars = 0;\n\tint count_lines = 0;\n\n\tdo {\n\t\tif (e->type == HISTORY_SENTINEL) break;\n\n\t\tswitch (e->type) {\n\t\t\tcase HISTORY_INSERT:\n\t\t\t\t/* Delete */\n\t\t\t\tline_delete(\n\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\te->contents.insert_delete_replace.offset+1,\n\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t);\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_DELETE:\n\t\t\t\t{\n\t\t\t\t\tchar_t _c = {codepoint_width(e->contents.insert_delete_replace.old_codepoint),0,e->contents.insert_delete_replace.old_codepoint};\n\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno] = line_insert(\n\t\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\t\t_c,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.offset-1,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REPLACE:\n\t\t\t\t{\n\t\t\t\t\tchar_t _o = {codepoint_width(e->contents.insert_delete_replace.old_codepoint),0,e->contents.insert_delete_replace.old_codepoint};\n\t\t\t\t\tline_replace(\n\t\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\t\t_o,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.offset,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REMOVE_LINE:\n\t\t\t\tenv->lines = add_line(env->lines, e->contents.remove_replace_line.lineno);\n\t\t\t\treplace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.old_contents);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_ADD_LINE:\n\t\t\t\tenv->lines = remove_line(env->lines, e->contents.add_merge_split_lines.lineno);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REPLACE_LINE:\n\t\t\t\treplace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.old_contents);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_SPLIT_LINE:\n\t\t\t\tenv->lines = merge_lines(env->lines, e->contents.add_merge_split_lines.lineno+1);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_MERGE_LINES:\n\t\t\t\tenv->lines = split_line(env->lines, e->contents.add_merge_split_lines.lineno-1, e->contents.add_merge_split_lines.split);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_BREAK:\n\t\t\t\t/* Ignore break */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\trender_error(\"Unknown type %d!\\n\", e->type);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tenv->line_no = env->history->line;\n\t\tenv->col_no = env->history->col;\n\n\t\tenv->history = e->previous;\n\t\te = env->history;\n\t} while (e->type != HISTORY_BREAK);\n\n\tif (env->line_no > env->line_count) env->line_no = env->line_count;\n\tif (env->line_no < 1) env->line_no = 1;\n\tif (env->col_no > env->lines[env->line_no-1]->actual) env->col_no = env->lines[env->line_no-1]->actual;\n\tif (env->col_no < 1) env->col_no = 1;\n\n\tenv->modified = (env->history != env->last_save_history);\n\n\tenv->loading = 0;\n\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\tenv->lines[i]->istate = 0;\n\t\trecalculate_tabs(env->lines[i]);\n\t}\n\tschedule_complete_recalc();\n\tplace_cursor_actual();\n\tupdate_title();\n\tredraw_all();\n\trender_commandline_message(\"%d character%s, %d line%s changed\",\n\t\t\tcount_chars, (count_chars == 1) ? \"\" : \"s\",\n\t\t\tcount_lines, (count_lines == 1) ? \"\" : \"s\");\n}\n\nBIM_ACTION(redo_history, ACTION_IS_RW,\n\t\"Redo history until the next breakpoint.\"\n,void) {\n\tif (!global_config.history_enabled) return;\n\n\tenv->loading = 1;\n\thistory_t * e = env->history->next;\n\n\tif (!e) {\n\t\tenv->loading = 0;\n\t\trender_commandline_message(\"Already at newest change\");\n\t\treturn;\n\t}\n\n\tint count_chars = 0;\n\tint count_lines = 0;\n\n\twhile (e) {\n\t\tif (e->type == HISTORY_BREAK) {\n\t\t\tenv->history = e;\n\t\t\tbreak;\n\t\t}\n\n\t\tswitch (e->type) {\n\t\t\tcase HISTORY_INSERT:\n\t\t\t\t{\n\t\t\t\t\tchar_t _c = {codepoint_width(e->contents.insert_delete_replace.codepoint),0,e->contents.insert_delete_replace.codepoint};\n\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno] = line_insert(\n\t\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\t\t_c,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.offset,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_DELETE:\n\t\t\t\t/* Delete */\n\t\t\t\tline_delete(\n\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\te->contents.insert_delete_replace.offset,\n\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t);\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REPLACE:\n\t\t\t\t{\n\t\t\t\t\tchar_t _o = {codepoint_width(e->contents.insert_delete_replace.codepoint),0,e->contents.insert_delete_replace.codepoint};\n\t\t\t\t\tline_replace(\n\t\t\t\t\t\t\tenv->lines[e->contents.insert_delete_replace.lineno],\n\t\t\t\t\t\t\t_o,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.offset,\n\t\t\t\t\t\t\te->contents.insert_delete_replace.lineno\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tcount_chars++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_ADD_LINE:\n\t\t\t\tenv->lines = add_line(env->lines, e->contents.remove_replace_line.lineno);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REMOVE_LINE:\n\t\t\t\tenv->lines = remove_line(env->lines, e->contents.remove_replace_line.lineno);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_REPLACE_LINE:\n\t\t\t\treplace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.contents);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_MERGE_LINES:\n\t\t\t\tenv->lines = merge_lines(env->lines, e->contents.add_merge_split_lines.lineno);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_SPLIT_LINE:\n\t\t\t\tenv->lines = split_line(env->lines, e->contents.add_merge_split_lines.lineno, e->contents.add_merge_split_lines.split);\n\t\t\t\tcount_lines++;\n\t\t\t\tbreak;\n\t\t\tcase HISTORY_BREAK:\n\t\t\t\t/* Ignore break */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\trender_error(\"Unknown type %d!\\n\", e->type);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tenv->history = e;\n\t\te = e->next;\n\t}\n\n\tenv->line_no = env->history->line;\n\tenv->col_no = env->history->col;\n\n\tif (env->line_no > env->line_count) env->line_no = env->line_count;\n\tif (env->line_no < 1) env->line_no = 1;\n\tif (env->col_no > env->lines[env->line_no-1]->actual) env->col_no = env->lines[env->line_no-1]->actual;\n\tif (env->col_no < 1) env->col_no = 1;\n\n\tenv->modified = (env->history != env->last_save_history);\n\n\tenv->loading = 0;\n\n\tfor (int i = 0; i < env->line_count; ++i) {\n\t\tenv->lines[i]->istate = 0;\n\t\trecalculate_tabs(env->lines[i]);\n\t}\n\tschedule_complete_recalc();\n\tplace_cursor_actual();\n\tupdate_title();\n\tredraw_all();\n\trender_commandline_message(\"%d character%s, %d line%s changed\",\n\t\t\tcount_chars, (count_chars == 1) ? \"\" : \"s\",\n\t\t\tcount_lines, (count_lines == 1) ? \"\" : \"s\");\n}\n\nint is_whitespace(int codepoint) {\n\treturn codepoint == ' ' || codepoint == '\\t';\n}\n\nint is_normal(int codepoint) {\n\treturn isalnum(codepoint) || codepoint == '_';\n}\n\nint is_special(int codepoint) {\n\treturn !is_normal(codepoint) && !is_whitespace(codepoint);\n}\n\nBIM_ACTION(word_left, 0,\n\t\"Move the cursor left to the previous word.\"\n,void) {\n\tif (!env->lines[env->line_no-1]) return;\n\n\twhile (env->col_no > 1 && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) {\n\t\tenv->col_no -= 1;\n\t}\n\n\tif (env->col_no == 1) {\n\t\tif (env->line_no == 1) goto _place;\n\t\tenv->line_no--;\n\t\tenv->col_no = env->lines[env->line_no-1]->actual;\n\t\tgoto _place;\n\t}\n\n\tint (*inverse_comparator)(int) = is_special;\n\tif (env->col_no > 1 && is_special(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) {\n\t\tinverse_comparator = is_normal;\n\t}\n\n\tdo {\n\t\tif (env->col_no > 1) {\n\t\t\tenv->col_no -= 1;\n\t\t}\n\t} while (env->col_no > 1 && !is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint) && !inverse_comparator(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint));\n\n_place:\n\tset_preferred_column();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(big_word_left, 0,\n\t\"Move the cursor left to the previous WORD.\"\n,void) {\n\tint line_no = env->line_no;\n\tint col_no = env->col_no;\n\n\tdo {\n\t\tcol_no--;\n\t\twhile (col_no == 0) {\n\t\t\tline_no--;\n\t\t\tif (line_no == 0) {\n\t\t\t\tgoto_line(1);\n\t\t\t\tset_preferred_column();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcol_no = env->lines[line_no-1]->actual;\n\t\t}\n\t} while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint));\n\n\tdo {\n\t\tcol_no--;\n\t\tif (col_no == 0) {\n\t\t\tenv->col_no = 1;\n\t\t\tenv->line_no = line_no;\n\t\t\tset_preferred_column();\n\t\t\tredraw_statusbar();\n\t\t\tplace_cursor_actual();\n\t\t\treturn;\n\t\t}\n\t} while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint));\n\n\tenv->col_no = col_no;\n\tenv->line_no = line_no;\n\tset_preferred_column();\n\tcursor_right();\n}\n\nBIM_ACTION(word_right, 0,\n\t\"Move the cursor right to the start of the next word.\"\n,void) {\n\tif (!env->lines[env->line_no-1]) return;\n\n\tif (env->col_no >= env->lines[env->line_no-1]->actual) {\n\t\t/* next line */\n\t\tif (env->line_no == env->line_count) return;\n\t\tenv->line_no++;\n\t\tenv->col_no = 0;\n\t\tif (env->col_no >= env->lines[env->line_no-1]->actual) {\n\t\t\tgoto _place;\n\t\t}\n\t}\n\n\tif (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no-1]->text[env->col_no - 1].codepoint)) {\n\t\twhile (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no-1]->text[env->col_no - 1].codepoint)) {\n\t\t\tenv->col_no++;\n\t\t}\n\t\tgoto _place;\n\t}\n\n\tint (*inverse_comparator)(int) = is_special;\n\tif (is_special(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint)) {\n\t\tinverse_comparator = is_normal;\n\t}\n\n\twhile (env->col_no < env->lines[env->line_no-1]->actual && !is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint) && !inverse_comparator(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint)) {\n\t\tenv->col_no++;\n\t}\n\n\twhile (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint)) {\n\t\tenv->col_no++;\n\t}\n\n_place:\n\tset_preferred_column();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(big_word_right, 0,\n\t\"Move the cursor right to the start of the next WORD.\"\n,void) {\n\tint line_no = env->line_no;\n\tint col_no = env->col_no;\n\n\tdo {\n\t\tcol_no++;\n\t\tif (col_no > env->lines[line_no-1]->actual) {\n\t\t\tline_no++;\n\t\t\tif (line_no > env->line_count) {\n\t\t\t\tenv->line_no = env->line_count;\n\t\t\t\tenv->col_no  = env->lines[env->line_no-1]->actual;\n\t\t\t\tset_preferred_column();\n\t\t\t\tredraw_statusbar();\n\t\t\t\tplace_cursor_actual();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcol_no = 0;\n\t\t\tbreak;\n\t\t}\n\t} while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint));\n\n\tdo {\n\t\tcol_no++;\n\t\twhile (col_no > env->lines[line_no-1]->actual) {\n\t\t\tline_no++;\n\t\t\tif (line_no >= env->line_count) {\n\t\t\t\tenv->col_no = env->lines[env->line_count-1]->actual;\n\t\t\t\tenv->line_no = env->line_count;\n\t\t\t\tset_preferred_column();\n\t\t\t\tredraw_statusbar();\n\t\t\t\tplace_cursor_actual();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcol_no = 1;\n\t\t}\n\t} while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint));\n\n\tenv->col_no = col_no;\n\tenv->line_no = line_no;\n\tset_preferred_column();\n\tredraw_statusbar();\n\tplace_cursor_actual();\n\treturn;\n}\n\nBIM_ACTION(delete_at_cursor, ACTION_IS_RW,\n\t\"Delete the character at the cursor, or merge with previous line.\"\n,void) {\n\tif (env->col_no > 1) {\n\t\tline_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1);\n\t\tenv->col_no -= 1;\n\t\tif (env->coffset > 0) env->coffset--;\n\t\tredraw_line(env->line_no-1);\n\t\tset_modified();\n\t\tredraw_statusbar();\n\t\tplace_cursor_actual();\n\t} else if (env->line_no > 1) {\n\t\tint tmp = env->lines[env->line_no - 2]->actual;\n\t\tmerge_lines(env->lines, env->line_no - 1);\n\t\tenv->line_no -= 1;\n\t\tenv->col_no = tmp+1;\n\t\tset_preferred_column();\n\t\tredraw_text();\n\t\tset_modified();\n\t\tredraw_statusbar();\n\t\tplace_cursor_actual();\n\t}\n}\n\nBIM_ACTION(delete_word, ACTION_IS_RW,\n\t\"Delete the previous word.\"\n,void) {\n\tif (!env->lines[env->line_no-1]) return;\n\tif (env->col_no > 1) {\n\n\t\t/* Start by deleting whitespace */\n\t\twhile (env->col_no > 1 && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) {\n\t\t\tline_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1);\n\t\t\tenv->col_no -= 1;\n\t\t\tif (env->coffset > 0) env->coffset--;\n\t\t}\n\n\t\tint (*inverse_comparator)(int) = is_special;\n\t\tif (env->col_no > 1 && is_special(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) {\n\t\t\tinverse_comparator = is_normal;\n\t\t}\n\n\t\tdo {\n\t\t\tif (env->col_no > 1) {\n\t\t\t\tline_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1);\n\t\t\t\tenv->col_no -= 1;\n\t\t\t\tif (env->coffset > 0) env->coffset--;\n\t\t\t}\n\t\t} while (env->col_no > 1 && !is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint) && !inverse_comparator(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint));\n\n\t\tset_preferred_column();\n\t\tredraw_text();\n\t\tset_modified();\n\t\tredraw_statusbar();\n\t\tplace_cursor_actual();\n\t}\n}\n\nBIM_ACTION(insert_line_feed, ACTION_IS_RW,\n\t\"Insert a line break, splitting the current line into two.\"\n,void) {\n\tif (env->indent) {\n\t\tif ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0x1F) == FLAG_COMMENT &&\n\t\t\t(env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') &&\n\t\t\t(env->col_no > 3) &&\n\t\t\t(env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) {\n\t\t\tdelete_at_cursor();\n\t\t}\n\t}\n\tif (env->col_no == env->lines[env->line_no - 1]->actual + 1) {\n\t\tenv->lines = add_line(env->lines, env->line_no);\n\t} else {\n\t\tenv->lines = split_line(env->lines, env->line_no-1, env->col_no - 1);\n\t}\n\tunhighlight_matching_paren();\n\tenv->coffset = 0;\n\tenv->col_no = 1;\n\tenv->line_no += 1;\n\tset_preferred_column();\n\tadd_indent(env->line_no-1,env->line_no-2,0);\n\tif (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) {\n\t\tenv->offset += 1;\n\t}\n\tif (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++;\n\tset_modified();\n}\n\nBIM_ACTION(yank_lines, 0,\n\t\"Copy lines into the paste buffer.\"\n,void) {\n\tint start = env->start_line;\n\tint end = env->line_no;\n\tif (global_config.yanks) {\n\t\tfor (unsigned int i = 0; i < global_config.yank_count; ++i) {\n\t\t\tfree(global_config.yanks[i]);\n\t\t}\n\t\tfree(global_config.yanks);\n\t}\n\tint lines_to_yank;\n\tint start_point;\n\tif (start <= end) {\n\t\tlines_to_yank = end - start + 1;\n\t\tstart_point = start - 1;\n\t} else {\n\t\tlines_to_yank = start - end + 1;\n\t\tstart_point = end - 1;\n\t}\n\tglobal_config.yanks = malloc(sizeof(line_t *) * lines_to_yank);\n\tglobal_config.yank_count = lines_to_yank;\n\tglobal_config.yank_is_full_lines = 1;\n\tfor (int i = 0; i < lines_to_yank; ++i) {\n\t\tglobal_config.yanks[i] = malloc(sizeof(line_t) + sizeof(char_t) * (env->lines[start_point+i]->available));\n\t\tglobal_config.yanks[i]->available = env->lines[start_point+i]->available;\n\t\tglobal_config.yanks[i]->actual = env->lines[start_point+i]->actual;\n\t\tglobal_config.yanks[i]->istate = 0;\n\t\tmemcpy(&global_config.yanks[i]->text, &env->lines[start_point+i]->text, sizeof(char_t) * (env->lines[start_point+i]->actual));\n\n\t\tfor (int j = 0; j < global_config.yanks[i]->actual; ++j) {\n\t\t\tglobal_config.yanks[i]->text[j].flags = 0;\n\t\t}\n\t}\n}\n\n/**\n * Helper to yank part of a line into a new yank line.\n */\nvoid yank_partial_line(int yank_no, int line_no, int start_off, int count) {\n\tif (start_off + count > env->lines[line_no]->actual) {\n\t\tif (start_off >= env->lines[line_no]->actual) {\n\t\t\tstart_off = env->lines[line_no]->actual;\n\t\t\tcount = 0;\n\t\t} else {\n\t\t\tcount = env->lines[line_no]->actual - start_off;\n\t\t}\n\t}\n\tglobal_config.yanks[yank_no] = malloc(sizeof(line_t) + sizeof(char_t) * (count + 1));\n\tglobal_config.yanks[yank_no]->available = count + 1; /* ensure extra space */\n\tglobal_config.yanks[yank_no]->actual = count;\n\tglobal_config.yanks[yank_no]->istate = 0;\n\tmemcpy(&global_config.yanks[yank_no]->text, &env->lines[line_no]->text[start_off], sizeof(char_t) * count);\n\tfor (int i = 0; i < count; ++i) {\n\t\tglobal_config.yanks[yank_no]->text[i].flags = 0;\n\t}\n}\n\n/**\n * Yank text...\n */\nvoid yank_text(int start_line, int start_col, int end_line, int end_col) {\n\tif (global_config.yanks) {\n\t\tfor (unsigned int i = 0; i < global_config.yank_count; ++i) {\n\t\t\tfree(global_config.yanks[i]);\n\t\t}\n\t\tfree(global_config.yanks);\n\t}\n\tint lines_to_yank = end_line - start_line + 1;\n\tint start_point = start_line - 1;\n\tglobal_config.yanks = malloc(sizeof(line_t *) * lines_to_yank);\n\tglobal_config.yank_count = lines_to_yank;\n\tglobal_config.yank_is_full_lines = 0;\n\tif (lines_to_yank == 1) {\n\t\tyank_partial_line(0, start_point, start_col - 1, (end_col - start_col + 1));\n\t} else {\n\t\tyank_partial_line(0, start_point, start_col - 1, (env->lines[start_point]->actual - start_col + 1));\n\t\t/* Yank middle lines */\n\t\tfor (int i = 1; i < lines_to_yank - 1; ++i) {\n\t\t\tglobal_config.yanks[i] = malloc(sizeof(line_t) + sizeof(char_t) * (env->lines[start_point+i]->available));\n\t\t\tglobal_config.yanks[i]->available = env->lines[start_point+i]->available;\n\t\t\tglobal_config.yanks[i]->actual = env->lines[start_point+i]->actual;\n\t\t\tglobal_config.yanks[i]->istate = 0;\n\t\t\tmemcpy(&global_config.yanks[i]->text, &env->lines[start_point+i]->text, sizeof(char_t) * (env->lines[start_point+i]->actual));\n\n\t\t\tfor (int j = 0; j < global_config.yanks[i]->actual; ++j) {\n\t\t\t\tglobal_config.yanks[i]->text[j].flags = 0;\n\t\t\t}\n\t\t}\n\t\t/* Yank end line */\n\t\tyank_partial_line(lines_to_yank-1, end_line - 1, 0, end_col);\n\t}\n}\n\nBIM_ACTION(delete_at_column, ARG_IS_CUSTOM | ACTION_IS_RW,\n\t\"Delete from the current column backwards (`<backspace>`) or forwards (`<del>`).\"\n,int direction) {\n\tif (direction == -1 && env->sel_col <= 0) return;\n\n\tint prev_width = 0;\n\tint s = (env->line_no < env->start_line) ? env->line_no : env->start_line;\n\tint e = (env->line_no < env->start_line) ? env->start_line : env->line_no;\n\tfor (int i = s; i <= e; i++) {\n\t\tline_t * line = env->lines[i - 1];\n\n\t\tint _x = 0;\n\t\tint col = 1;\n\n\t\tint j = 0;\n\t\tfor (; j < line->actual; ++j) {\n\t\t\tchar_t * c = &line->text[j];\n\t\t\t_x += c->display_width;\n\t\t\tcol = j+1;\n\t\t\tif (_x > env->sel_col) break;\n\t\t\tprev_width = c->display_width;\n\t\t}\n\n\t\tif ((direction == -1) && (_x == env->sel_col && j == line->actual)) {\n\t\t\tline_delete(line, line->actual, i - 1);\n\t\t\tset_modified();\n\t\t} else if (_x > env->sel_col) {\n\t\t\tline_delete(line, col - (direction == -1 ? 1 : 0), i - 1);\n\t\t\tset_modified();\n\t\t}\n\t}\n\n\tif (direction == -1) {\n\t\tenv->sel_col -= prev_width;\n\t\tenv->col_no--;\n\t}\n\tredraw_text();\n}\n\nvoid realign_column_cursor(void) {\n\tline_t * line = env->lines[env->line_no - 1];\n\tint _x = 0, col = 1, j = 0;\n\tfor (; j < line->actual; ++j) {\n\t\tchar_t * c = &line->text[j];\n\t\t_x += c->display_width;\n\t\tcol = j + 1;\n\t\tif (_x > env->sel_col) break;\n\t}\n\tenv->col_no = col;\n}\n\nBIM_ACTION(column_left, 0, \"Move the column cursor left.\",void) {\n\tif (env->sel_col > 0) {\n\t\tenv->sel_col -= 1;\n\t\tenv->preferred_column = env->sel_col;\n\t\t/* Figure out where the cursor should be */\n\t\trealign_column_cursor();\n\t\tredraw_all();\n\t}\n}\n\nBIM_ACTION(column_right, 0, \"Move the column cursor right.\",void) {\n\tenv->sel_col += 1;\n\tenv->preferred_column = env->sel_col;\n\t/* Figure out where the cursor should be */\n\trealign_column_cursor();\n\tredraw_all();\n}\n\nBIM_ACTION(column_up, 0, \"Move the column cursor up.\",void) {\n\tif (env->line_no > 1 && env->start_line > 1) {\n\t\tenv->line_no--;\n\t\tenv->start_line--;\n\t\t/* Figure out where the cursor should be */\n\t\trealign_column_cursor();\n\t\tplace_cursor_actual();\n\t\tredraw_all();\n\t}\n}\n\nBIM_ACTION(column_down, 0, \"Move the column cursor down.\",void) {\n\tif (env->line_no < env->line_count && env->start_line < env->line_count) {\n\t\tenv->line_no++;\n\t\tenv->start_line++;\n\t\t/* Figure out where the cursor should be */\n\t\trealign_column_cursor();\n\t\tplace_cursor_actual();\n\t\tredraw_all();\n\t}\n}\n\nuint32_t * get_word_under_cursor(void) {\n\t/* Figure out size */\n\tint c_before = 0;\n\tint c_after = 0;\n\tint i = env->col_no;\n\twhile (i > 0) {\n\t\tif (!simple_keyword_qualifier(env->lines[env->line_no-1]->text[i-1].codepoint)) break;\n\t\tc_before++;\n\t\ti--;\n\t}\n\ti = env->col_no+1;\n\twhile (i < env->lines[env->line_no-1]->actual+1) {\n\t\tif (!simple_keyword_qualifier(env->lines[env->line_no-1]->text[i-1].codepoint)) break;\n\t\tc_after++;\n\t\ti++;\n\t}\n\tif (!c_before && !c_after) return NULL;\n\n\t/* Populate with characters */\n\tuint32_t * out = malloc(sizeof(uint32_t) * (c_before+c_after+1));\n\tint j = 0;\n\twhile (c_before) {\n\t\tout[j] = env->lines[env->line_no-1]->text[env->col_no-c_before].codepoint;\n\t\tc_before--;\n\t\tj++;\n\t}\n\tint x = 0;\n\twhile (c_after) {\n\t\tout[j] = env->lines[env->line_no-1]->text[env->col_no+x].codepoint;\n\t\tj++;\n\t\tx++;\n\t\tc_after--;\n\t}\n\tout[j] = 0;\n\n\treturn out;\n}\n\nBIM_ACTION(search_under_cursor, 0,\n\t\"Search for the word currently under the cursor.\"\n,void) {\n\tif (global_config.search) free(global_config.search);\n\tglobal_config.search = get_word_under_cursor();\n\n\t/* Find it */\n\tsearch_next();\n}\n\nBIM_ACTION(find_character_forward, ARG_IS_PROMPT | ARG_IS_INPUT,\n\t\"Find a character forward on the current line and place the cursor on (`f`) or before (`t`) it.\"\n,int type, int c) {\n\tfor (int i = env->col_no+1; i <= env->lines[env->line_no-1]->actual; ++i) {\n\t\tif (env->lines[env->line_no-1]->text[i-1].codepoint == c) {\n\t\t\tenv->col_no = i - !!(type == 't');\n\t\t\tplace_cursor_actual();\n\t\t\tset_preferred_column();\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nBIM_ACTION(find_character_backward, ARG_IS_PROMPT | ARG_IS_INPUT,\n\t\"Find a character backward on the current line and place the cursor on (`F`) or after (`T`) it.\"\n,int type, int c) {\n\tfor (int i = env->col_no-1; i >= 1; --i) {\n\t\tif (env->lines[env->line_no-1]->text[i-1].codepoint == c) {\n\t\t\tenv->col_no = i + !!(type == 'T');\n\t\t\tplace_cursor_actual();\n\t\t\tset_preferred_column();\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/**\n * Clear the navigation number buffer\n */\nvoid reset_nav_buffer(int c) {\n\tif (c == KEY_TIMEOUT) return;\n\tif (nav_buffer && (c < '0' || c > '9')) {\n\t\tnav_buffer = 0;\n\t\tredraw_commandline();\n\t}\n}\n\n/**\n * Determine if a column + line number are within range of the\n * current character selection specified by start_line, etc.\n *\n * Used to determine how syntax flags should be set when redrawing\n * selected text in CHAR SELECTION mode.\n */\nint point_in_range(int start_line, int end_line, int start_col, int end_col, int line, int col) {\n\tif (start_line == end_line) {\n\t\tif ( end_col < start_col) {\n\t\t\tint tmp = end_col;\n\t\t\tend_col = start_col;\n\t\t\tstart_col = tmp;\n\t\t}\n\t\treturn (col >= start_col && col <= end_col);\n\t}\n\n\tif (start_line > end_line) {\n\t\tint tmp = end_line;\n\t\tend_line = start_line;\n\t\tstart_line = tmp;\n\n\t\ttmp = end_col;\n\t\tend_col = start_col;\n\t\tstart_col = tmp;\n\t}\n\n\tif (line < start_line || line > end_line) return 0;\n\n\tif (line == start_line) {\n\t\treturn col >= start_col;\n\t}\n\n\tif (line == end_line) {\n\t\treturn col <= end_col;\n\t}\n\n\treturn 1;\n}\n\n/**\n * Macro for redrawing selected lines with appropriate highlighting.\n */\n#define _redraw_line(line, force_start_line) \\\n\tdo { \\\n\t\tif (!(force_start_line) && (line) == env->start_line) break; \\\n\t\tif ((line) > env->line_count + 1) { \\\n\t\t\tif ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \\\n\t\t\t\tdraw_excess_line((line) - env->offset - 1); \\\n\t\t\t} \\\n\t\t\tbreak; \\\n\t\t} \\\n\t\tif ((env->line_no < env->start_line  && ((line) < env->line_no || (line) > env->start_line)) || \\\n\t\t\t(env->line_no > env->start_line  && ((line) > env->line_no || (line) < env->start_line)) || \\\n\t\t\t(env->line_no == env->start_line && (line) != env->start_line)) { \\\n\t\t\tfor (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \\\n\t\t\t\tenv->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \\\n\t\t\t} \\\n\t\t} else { \\\n\t\t\tfor (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \\\n\t\t\t\tenv->lines[(line)-1]->text[j].flags |= FLAG_SELECT; \\\n\t\t\t} \\\n\t\t} \\\n\t\tredraw_line((line)-1); \\\n\t} while (0)\n\n#define _redraw_line_char(line, force_start_line) \\\n\tdo { \\\n\t\tif (!(force_start_line) && (line) == env->start_line) break; \\\n\t\tif ((line) > env->line_count + 1) { \\\n\t\t\tif ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \\\n\t\t\t\tdraw_excess_line((line) - env->offset - 1); \\\n\t\t\t} \\\n\t\t\tbreak; \\\n\t\t} \\\n\t\tif ((env->line_no < env->start_line  && ((line) < env->line_no || (line) > env->start_line)) || \\\n\t\t\t(env->line_no > env->start_line  && ((line) > env->line_no || (line) < env->start_line)) || \\\n\t\t\t(env->line_no == env->start_line && (line) != env->start_line)) { \\\n\t\t\t/* Line is completely outside selection */ \\\n\t\t\tfor (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \\\n\t\t\t\tenv->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \\\n\t\t\t} \\\n\t\t} else { \\\n\t\t\tif ((line) == env->start_line || (line) == env->line_no) { \\\n\t\t\t\tfor (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \\\n\t\t\t\t\tenv->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t\tfor (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \\\n\t\t\t\tif (point_in_range(env->start_line, env->line_no,env->start_col, env->col_no, (line), j+1)) { \\\n\t\t\t\t\tenv->lines[(line)-1]->text[j].flags |= FLAG_SELECT; \\\n\t\t\t\t} \\\n\t\t\t} \\\n\t\t} \\\n\t\tredraw_line((line)-1); \\\n\t} while (0)\n\n#define _redraw_line_col(line, force_start_line) \\\n\tdo {\\\n\t\tif (!(force_start_line) && (line) == env->start_line) break; \\\n\t\tif ((line) > env->line_count + 1) { \\\n\t\t\tif ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \\\n\t\t\t\tdraw_excess_line((line) - env->offset - 1); \\\n\t\t\t} \\\n\t\t\tbreak; \\\n\t\t} \\\n\t\tredraw_line((line)-1); \\\n\t} while (0)\n\nBIM_ACTION(adjust_indent, ARG_IS_CUSTOM | ACTION_IS_RW,\n\t\"Adjust the indentation on the selected lines (`<tab>` for deeper, `<shift-tab>` for shallower).\"\n,int direction) {\n\tint lines_to_cover = 0;\n\tint start_point = 0;\n\tif (env->start_line <= env->line_no) {\n\t\tstart_point = env->start_line - 1;\n\t\tlines_to_cover = env->line_no - env->start_line + 1;\n\t} else {\n\t\tstart_point = env->line_no - 1;\n\t\tlines_to_cover = env->start_line - env->line_no + 1;\n\t}\n\tfor (int i = 0; i < lines_to_cover; ++i) {\n\t\tif (env->lines[start_point + i]->actual < 1) continue;\n\t\tif (direction == -1) {\n\t\t\tif (env->tabs) {\n\t\t\t\tif (env->lines[start_point + i]->text[0].codepoint == '\\t') {\n\t\t\t\t\tline_delete(env->lines[start_point + i],1,start_point+i);\n\t\t\t\t\t_redraw_line(start_point+i+1,1);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (env->lines[start_point + i]->text[0].codepoint == '\\t') {\n\t\t\t\t\tline_delete(env->lines[start_point + i],1,start_point+i);\n\t\t\t\t\t_redraw_line(start_point+i+1,1);\n\t\t\t\t} else {\n\t\t\t\t\tfor (int j = 0; j < env->tabstop; ++j) {\n\t\t\t\t\t\tif (env->lines[start_point + i]->text[0].codepoint == ' ') {\n\t\t\t\t\t\t\tline_delete(env->lines[start_point + i],1,start_point+i);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t_redraw_line(start_point+i+1,1);\n\t\t\t}\n\t\t} else if (direction == 1) {\n\t\t\tif (env->tabs) {\n\t\t\t\tchar_t c;\n\t\t\t\tc.codepoint = '\\t';\n\t\t\t\tc.display_width = env->tabstop;\n\t\t\t\tc.flags = FLAG_SELECT;\n\t\t\t\tenv->lines[start_point + i] = line_insert(env->lines[start_point + i], c, 0, start_point + i);\n\t\t\t} else {\n\t\t\t\tfor (int j = 0; j < env->tabstop; ++j) {\n\t\t\t\t\tchar_t c;\n\t\t\t\t\tc.codepoint = ' ';\n\t\t\t\t\tc.display_width = 1;\n\t\t\t\t\tc.flags = FLAG_SELECT;\n\t\t\t\t\tenv->lines[start_point + i] = line_insert(env->lines[start_point + i], c, 0, start_point + i);\n\t\t\t\t}\n\t\t\t}\n\t\t\t_redraw_line(start_point+i+1,1);\n\t\t}\n\t}\n\tif (env->col_no > env->lines[env->line_no-1]->actual) {\n\t\tenv->col_no = env->lines[env->line_no-1]->actual;\n\t}\n\tset_preferred_column();\n\tset_modified();\n}\n\nvoid recalculate_selected_lines(void) {\n\tint start = env->line_no < env->start_line ? env->line_no : env->start_line;\n\tint end = env->line_no > env->start_line ? env->line_no : env->start_line;\n\tif (start < 1) start = 1;\n\tif (start > env->line_count) start = env->line_count;\n\tif (end < 1) end = 1;\n\tif (end > env->line_count) end = env->line_count;\n\tfor (int i = (start > 1) ? (start-1) : (start); i <= end; ++i) {\n\t\tfor (int j = 0; j < env->lines[i-1]->actual; j++) {\n\t\t\tenv->lines[i-1]->text[j].flags &= ~(FLAG_SELECT);\n\t\t}\n\t}\n\tredraw_all();\n}\n\nBIM_ACTION(enter_line_selection, 0,\n\t\"Enter line selection mode.\"\n,void) {\n\t/* Set mode */\n\tenv->mode = MODE_LINE_SELECTION;\n\t/* Store start position */\n\tenv->start_line = env->line_no;\n\tenv->prev_line  = env->start_line;\n\tenv->start_col  = env->col_no;\n\t/* Redraw commandline to get -- LINE SELECTION -- text */\n\tredraw_commandline();\n\tunhighlight_matching_paren();\n\n\t/* Set this line as selected for syntax highlighting */\n\tfor (int j = 0; j < env->lines[env->line_no-1]->actual; ++j) {\n\t\tenv->lines[env->line_no-1]->text[j].flags |= FLAG_SELECT;\n\t}\n\n\t/* And redraw it */\n\tredraw_line(env->line_no-1);\n}\n\nBIM_ACTION(switch_selection_mode, ARG_IS_CUSTOM,\n\t\"Swap between LINE and CHAR selection modes.\"\n,int mode) {\n\tenv->mode = mode;\n\tif (mode == MODE_LINE_SELECTION) {\n\t\tint start = env->line_no < env->start_line ? env->line_no : env->start_line;\n\t\tint end = env->line_no > env->start_line ? env->line_no : env->start_line;\n\t\tfor (int i = start; i <= end; ++i) {\n\t\t\t_redraw_line(i, 1);\n\t\t}\n\t} else if (mode == MODE_CHAR_SELECTION) {\n\t\tint start = env->line_no < env->start_line ? env->line_no : env->start_line;\n\t\tint end = env->line_no > env->start_line ? env->line_no : env->start_line;\n\t\tfor (int i = start; i <= end; ++i) {\n\t\t\t_redraw_line_char(i, 1);\n\t\t}\n\t}\n}\n\nBIM_ACTION(delete_and_yank_lines, 0,\n\t\"Delete and yank the selected lines.\"\n,void) {\n\tyank_lines();\n\tif (env->start_line <= env->line_no) {\n\t\tint lines_to_delete = env->line_no - env->start_line + 1;\n\t\tfor (int i = 0; i < lines_to_delete; ++i) {\n\t\t\tenv->lines = remove_line(env->lines, env->start_line-1);\n\t\t}\n\t\tenv->line_no = env->start_line;\n\t} else {\n\t\tint lines_to_delete = env->start_line - env->line_no + 1;\n\t\tfor (int i = 0; i < lines_to_delete; ++i) {\n\t\t\tenv->lines = remove_line(env->lines, env->line_no-1);\n\t\t}\n\t}\n\tif (env->line_no > env->line_count) {\n\t\tenv->line_no = env->line_count;\n\t}\n\tif (env->col_no > env->lines[env->line_no-1]->actual) {\n\t\tenv->col_no = env->lines[env->line_no-1]->actual;\n\t}\n\tset_preferred_column();\n\tset_modified();\n}\n\nBIM_ACTION(enter_insert, ACTION_IS_RW,\n\t\"Enter insert mode.\"\n,void) {\n\tenv->mode = MODE_INSERT;\n\tset_history_break();\n}\n\nBIM_ACTION(delete_lines_and_enter_insert, ACTION_IS_RW,\n\t\"Delete and yank the selected lines and then enter insert mode.\"\n,void) {\n\tdelete_and_yank_lines();\n\tenv->lines = add_line(env->lines, env->line_no-1);\n\tredraw_text();\n\tenv->mode = MODE_INSERT;\n}\n\nBIM_ACTION(replace_chars_in_line, ARG_IS_PROMPT | ACTION_IS_RW,\n\t\"Replace characters in the selected lines.\"\n,int c) {\n\tif (c >= KEY_ESCAPE) {\n\t\trender_error(\"Invalid key for replacement\");\n\t\treturn;\n\t}\n\tchar_t _c = {codepoint_width(c), 0, c};\n\tint start_point = env->start_line < env->line_no ? env->start_line : env->line_no;\n\tint end_point = env->start_line < env->line_no ? env->line_no : env->start_line;\n\tfor (int line = start_point; line <= end_point; ++line) {\n\t\tfor (int i = 0; i < env->lines[line-1]->actual; ++i) {\n\t\t\tline_replace(env->lines[line-1], _c, i, line-1);\n\t\t}\n\t}\n}\n\nBIM_ACTION(leave_selection, 0,\n\t\"Leave selection modes and return to normal mode.\"\n,void) {\n\tset_history_break();\n\tenv->mode = MODE_NORMAL;\n\trecalculate_selected_lines();\n}\n\nBIM_ACTION(insert_char_at_column, ARG_IS_INPUT | ACTION_IS_RW,\n\t\"Insert a character on all lines at the current column.\"\n,int c) {\n\tchar_t _c;\n\t_c.codepoint = c;\n\t_c.flags = 0;\n\t_c.display_width = codepoint_width(c);\n\n\tint inserted_width = 0;\n\n\t/* For each line */\n\tint s = (env->line_no < env->start_line) ? env->line_no : env->start_line;\n\tint e = (env->line_no < env->start_line) ? env->start_line : env->line_no;\n\tfor (int i = s; i <= e; i++) {\n\t\tline_t * line = env->lines[i - 1];\n\n\t\tint _x = 0;\n\t\tint col = 1;\n\n\t\tint j = 0;\n\t\tfor (; j < line->actual; ++j) {\n\t\t\tchar_t * c = &line->text[j];\n\t\t\t_x += c->display_width;\n\t\t\tcol = j+1;\n\t\t\tif (_x > env->sel_col) break;\n\t\t}\n\n\t\tif ((_x == env->sel_col && j == line->actual)) {\n\t\t\t_x = env->sel_col + 1;\n\t\t\tcol = line->actual + 1;\n\t\t}\n\n\t\tif (_x > env->sel_col) {\n\t\t\tline_t * nline = line_insert(line, _c, col - 1, i - 1);\n\t\t\tif (line != nline) {\n\t\t\t\tenv->lines[i - 1] = nline;\n\t\t\t\tline = nline;\n\t\t\t}\n\t\t\tset_modified();\n\t\t}\n\t\trecalculate_tabs(line);\n\t\tinserted_width = line->text[col-1].display_width;\n\t\tif (i == env->line_no) env->col_no = col + 1;\n\t}\n\n\tenv->sel_col += inserted_width;\n\tenv->preferred_column = env->sel_col;\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(insert_tab_at_column, ACTION_IS_RW,\n\t\"Insert an indentation character on multiple lines.\"\n,void) {\n\tif (env->tabs) {\n\t\tinsert_char_at_column('\\t');\n\t} else {\n\t\tfor (int i = 0; i < env->tabstop; ++i) {\n\t\t\tinsert_char_at_column(' ');\n\t\t}\n\t}\n}\n\nBIM_ACTION(enter_col_insert, ACTION_IS_RW,\n\t\"Enter column insert mode.\"\n,void) {\n\tenv->mode = MODE_COL_INSERT;\n}\n\nBIM_ACTION(enter_col_insert_after, ACTION_IS_RW,\n\t\"Enter column insert mode after the selected column.\"\n,void) {\n\tenv->sel_col += 1;\n\tenter_col_insert();\n}\n\nBIM_ACTION(enter_col_selection, 0,\n\t\"Enter column selection mode.\"\n,void) {\n\t/* Set mode */\n\tenv->mode = MODE_COL_SELECTION;\n\t/* Store cursor */\n\tenv->start_line = env->line_no;\n\tenv->sel_col = env->preferred_column;\n\tenv->prev_line = env->start_line;\n\t/* Redraw commandline */\n\tredraw_commandline();\n\t/* Nothing else to do here; rely on cursor */\n}\n\nBIM_ACTION(yank_characters, 0,\n\t\"Yank the selected characters to the paste buffer.\"\n,void) {\n\tint end_line = env->line_no;\n\tint end_col  = env->col_no;\n\tif (env->start_line == end_line) {\n\t\tif (env->start_col > end_col) {\n\t\t\tint tmp = env->start_col;\n\t\t\tenv->start_col = end_col;\n\t\t\tend_col = tmp;\n\t\t}\n\t} else if (env->start_line > end_line) {\n\t\tint tmp = env->start_line;\n\t\tenv->start_line = end_line;\n\t\tend_line = tmp;\n\t\ttmp = env->start_col;\n\t\tenv->start_col = end_col;\n\t\tend_col = tmp;\n\t}\n\tyank_text(env->start_line, env->start_col, end_line, end_col);\n}\n\nBIM_ACTION(delete_and_yank_chars, ACTION_IS_RW,\n\t\"Delete and yank the selected characters.\"\n,void) {\n\tint end_line = env->line_no;\n\tint end_col  = env->col_no;\n\tif (env->start_line == end_line) {\n\t\tif (env->start_col > end_col) {\n\t\t\tint tmp = env->start_col;\n\t\t\tenv->start_col = end_col;\n\t\t\tend_col = tmp;\n\t\t}\n\t\tyank_text(env->start_line, env->start_col, end_line, end_col);\n\t\tfor (int i = env->start_col; i <= end_col; ++i) {\n\t\t\tline_delete(env->lines[env->start_line-1], env->start_col, env->start_line - 1);\n\t\t}\n\t\tenv->col_no = env->start_col;\n\t} else {\n\t\tif (env->start_line > end_line) {\n\t\t\tint tmp = env->start_line;\n\t\t\tenv->start_line = end_line;\n\t\t\tend_line = tmp;\n\t\t\ttmp = env->start_col;\n\t\t\tenv->start_col = end_col;\n\t\t\tend_col = tmp;\n\t\t}\n\t\t/* Copy lines */\n\t\tyank_text(env->start_line, env->start_col, end_line, end_col);\n\t\t/* Delete lines */\n\t\tfor (int i = env->start_line+1; i < end_line; ++i) {\n\t\t\tenv->lines = remove_line(env->lines, env->start_line);\n\t\t} /* end_line is no longer valid; should be start_line+1*/\n\t\t/* Delete from env->start_col forward */\n\t\tint tmp = env->lines[env->start_line-1]->actual;\n\t\tfor (int i = env->start_col; i <= tmp; ++i) {\n\t\t\tline_delete(env->lines[env->start_line-1], env->start_col, env->start_line - 1);\n\t\t}\n\t\tfor (int i = 1; i <= end_col; ++i) {\n\t\t\tline_delete(env->lines[env->start_line], 1, env->start_line);\n\t\t}\n\t\t/* Merge start and end lines */\n\t\tmerge_lines(env->lines, env->start_line);\n\t\tenv->line_no = env->start_line;\n\t\tenv->col_no = env->start_col;\n\t}\n\tif (env->line_no > env->line_count) {\n\t\tenv->line_no = env->line_count;\n\t}\n\tset_preferred_column();\n\tset_modified();\n}\n\nBIM_ACTION(delete_chars_and_enter_insert, ACTION_IS_RW,\n\t\"Delete and yank the selected characters and then enter insert mode.\"\n,void) {\n\tdelete_and_yank_chars();\n\tredraw_text();\n\tenter_insert();\n}\n\nBIM_ACTION(replace_chars, ARG_IS_PROMPT | ACTION_IS_RW,\n\t\"Replace the selected characters.\"\n,int c) {\n\tif (c >= KEY_ESCAPE) {\n\t\trender_error(\"Invalid key for replacement\");\n\t\treturn;\n\t}\n\n\tchar_t _c = {codepoint_width(c), 0, c};\n\t/* This should probably be a function line \"do_over_range\" or something */\n\tif (env->start_line == env->line_no) {\n\t\tint s = (env->start_col < env->col_no) ? env->start_col : env->col_no;\n\t\tint e = (env->start_col < env->col_no) ? env->col_no : env->start_col;\n\t\tfor (int i = s; i <= e; ++i) {\n\t\t\tline_replace(env->lines[env->start_line-1], _c, i-1, env->start_line-1);\n\t\t}\n\t\tredraw_text();\n\t} else {\n\t\tif (env->start_line < env->line_no) {\n\t\t\tfor (int s = env->start_col-1; s < env->lines[env->start_line-1]->actual; ++s) {\n\t\t\t\tline_replace(env->lines[env->start_line-1], _c, s, env->start_line-1);\n\t\t\t}\n\t\t\tfor (int line = env->start_line + 1; line < env->line_no; ++line) {\n\t\t\t\tfor (int i = 0; i < env->lines[line-1]->actual; ++i) {\n\t\t\t\t\tline_replace(env->lines[line-1], _c, i, line-1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int s = 0; s < env->col_no; ++s) {\n\t\t\t\tline_replace(env->lines[env->line_no-1], _c, s, env->line_no-1);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (int s = env->col_no-1; s < env->lines[env->line_no-1]->actual; ++s) {\n\t\t\t\tline_replace(env->lines[env->line_no-1], _c, s, env->line_no-1);\n\t\t\t}\n\t\t\tfor (int line = env->line_no + 1; line < env->start_line; ++line) {\n\t\t\t\tfor (int i = 0; i < env->lines[line-1]->actual; ++i) {\n\t\t\t\t\tline_replace(env->lines[line-1], _c, i, line-1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int s = 0; s < env->start_col; ++s) {\n\t\t\t\tline_replace(env->lines[env->start_line-1], _c, s, env->start_line-1);\n\t\t\t}\n\t\t}\n\t}\n}\n\nBIM_ACTION(enter_char_selection, 0,\n\t\"Enter character selection mode.\"\n,void) {\n\t/* Set mode */\n\tenv->mode = MODE_CHAR_SELECTION;\n\t/* Set cursor positions */\n\tenv->start_line = env->line_no;\n\tenv->start_col  = env->col_no;\n\tenv->prev_line  = env->start_line;\n\t/* Redraw commandline for -- CHAR SELECTION -- */\n\tredraw_commandline();\n\tunhighlight_matching_paren();\n\n\t/* Select single character */\n\tenv->lines[env->line_no-1]->text[env->col_no-1].flags |= FLAG_SELECT;\n\tredraw_line(env->line_no-1);\n}\n\nBIM_ACTION(insert_at_end_of_selection, ACTION_IS_RW,\n\t\"Move the cursor to the end of the selection and enter insert mode.\"\n,void) {\n\trecalculate_selected_lines();\n\tif (env->line_no == env->start_line) {\n\t\tenv->col_no = env->col_no > env->start_col ? env->col_no + 1 : env->start_col + 1;\n\t} else if (env->line_no < env->start_line) {\n\t\tenv->col_no = env->start_col + 1;\n\t\tenv->line_no = env->start_line;\n\t} else {\n\t\tenv->col_no += 1;\n\t}\n\tenv->mode = MODE_INSERT;\n}\n\nvoid free_completion_match(struct completion_match * match) {\n\tif (match->string) free(match->string);\n\tif (match->file) free(match->file);\n\tif (match->search) free(match->search);\n}\n\n/**\n * Read ctags file to find matches for a symbol\n */\nint read_tags(uint32_t * comp, struct completion_match **matches, int * matches_count, int complete_match) {\n\tint _matches_len = 4;\n\tint *matches_len = &_matches_len;\n\t*matches_count = 0;\n\t*matches = malloc(sizeof(struct completion_match) * (*matches_len));\n\n\tFILE * tags = fopen(\"tags\",\"r\");\n\tif (tags) {\n\t\tchar tmp[4096]; /* max line */\n\t\twhile (!feof(tags) && fgets(tmp, 4096, tags)) {\n\t\t\tif (tmp[0] == '!') continue;\n\t\t\tint i = 0;\n\t\t\twhile (comp[i] && comp[i] == (unsigned int)tmp[i]) i++;\n\t\t\tif (comp[i] == '\\0') {\n\t\t\t\tif (complete_match && tmp[i] != '\\t') continue;\n\t\t\t\tint j = i;\n\t\t\t\twhile (tmp[j] != '\\t' && tmp[j] != '\\n' && tmp[j] != '\\0') j++;\n\t\t\t\ttmp[j] = '\\0'; j++;\n\t\t\t\tchar * file = &tmp[j];\n\t\t\t\twhile (tmp[j] != '\\t' && tmp[j] != '\\n' && tmp[j] != '\\0') j++;\n\t\t\t\ttmp[j] = '\\0'; j++;\n\t\t\t\tchar * search = &tmp[j];\n\t\t\t\twhile (!(tmp[j] == '/' && tmp[j+1] == ';' && tmp[j+2] == '\"' && tmp[j+3] == '\\t') /* /normal searches/ */\n\t\t\t\t       && !(tmp[j] == ';' && tmp[j+1] == '\"' && tmp[j+2] == '\\t') /* Old ctags line number searches */\n\t\t\t\t       && (tmp[j] != '\\n' && tmp[j] != '\\0')) j++;\n\t\t\t\ttmp[j] = '\\0'; j++;\n\n\t\t\t\tadd_match(tmp,file,search);\n\t\t\t}\n\t\t}\n\t\tfclose(tags);\n\t}\n\n\tif (env->syntax && env->syntax->completion_matcher) {\n\t\tenv->syntax->completion_matcher(comp,matches,matches_count,complete_match,matches_len, env);\n\t}\n\n\treturn 0;\n}\n\n/**\n * Draw an autocomplete popover with matches.\n */\nvoid draw_completion_matches(uint32_t * tmp, struct completion_match *matches, int matches_count, int index) {\n\tint original_length = 0;\n\twhile (tmp[original_length]) original_length++;\n\tint max_width = 0;\n\tfor (int i = 0; i < matches_count; ++i) {\n\t\t/* TODO unicode width */\n\t\tunsigned int my_width = strlen(matches[i].string) + (matches[i].file ? strlen(matches[i].file) + 1 : 0);\n\t\tif (my_width > (unsigned int)max_width) {\n\t\t\tmax_width = my_width;\n\t\t}\n\t}\n\n\t/* Figure out how much space we have to display the window */\n\tint cursor_y = env->line_no - env->offset + global_config.tabs_visible;\n\tint max_y = global_config.term_height - global_config.bottom_size - cursor_y;\n\n\t/* Find a good place to put the box horizontally */\n\tint num_size = num_width() + gutter_width();\n\tint x = num_size + 1 - env->coffset;\n\n\t/* Determine where the cursor is physically */\n\tfor (int i = 0; i < env->col_no - 1 - original_length; ++i) {\n\t\tchar_t * c = &env->lines[env->line_no-1]->text[i];\n\t\tx += c->display_width;\n\t}\n\n\tint box_width = max_width;\n\tint box_x = x;\n\tint box_y = cursor_y + 1;\n\tif (max_width > env->width - num_width() - gutter_width()) {\n\t\tbox_width = env->width - num_width() - gutter_width();\n\t\tbox_x = num_width() + gutter_width() + 1;\n\t} else if (env->width - x < max_width) {\n\t\tbox_width = max_width;\n\t\tbox_x = env->width - max_width;\n\t}\n\n\tint max_count = (max_y < matches_count) ? max_y - 1 : matches_count;\n\n\tfor (int x = index; x < max_count+index; ++x) {\n\t\tint i = x % matches_count;\n\t\tplace_cursor(box_x + env->left, box_y+x-index);\n\t\tset_colors(COLOR_KEYWORD, COLOR_STATUS_BG);\n\t\t/* TODO wide characters */\n\t\tint match_width = strlen(matches[i].string);\n\t\tint file_width = matches[i].file ? strlen(matches[i].file) : 0;\n\t\tfor (int j = 0; j < box_width; ++j) {\n\t\t\tif (j == original_length) set_colors(i == index ? COLOR_NUMERAL : COLOR_STATUS_FG, COLOR_STATUS_BG);\n\t\t\tif (j == match_width) set_colors(COLOR_TYPE, COLOR_STATUS_BG);\n\t\t\tif (j < match_width) printf(\"%c\", matches[i].string[j]);\n\t\t\telse if (j > match_width && j - match_width - 1 < file_width) printf(\"%c\", matches[i].file[j-match_width-1]);\n\t\t\telse printf(\" \");\n\t\t}\n\t}\n\tif (max_count == 0) {\n\t\tplace_cursor(box_x + env->left, box_y);\n\t\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\t\tprintf(\" (no matches) \");\n\t} else if (max_count != matches_count) {\n\t\tplace_cursor(box_x + env->left, box_y+max_count);\n\t\tset_colors(COLOR_STATUS_FG, COLOR_STATUS_BG);\n\t\tprintf(\" (%d more) \", matches_count-max_count);\n\t}\n}\n\n/**\n * Autocomplete words (function/variable names, etc.) in input mode.\n */\nint omni_complete(int quit_quietly_on_none) {\n\tint retval = 0;\n\tint index = 0;\n\n\tint c;\n\n\tint (*qualifier)(int c) = simple_keyword_qualifier;\n\tif (env->syntax && env->syntax->completion_qualifier) {\n\t\tqualifier = env->syntax->completion_qualifier;\n\t}\n\n\t/* Pull the word from before the cursor */\n\tint c_before = 0;\n\tint i = env->col_no-1;\n\twhile (i > 0) {\n\t\tint c = env->lines[env->line_no-1]->text[i-1].codepoint;\n\t\tif (!qualifier(c)) break;\n\t\tc_before++;\n\t\ti--;\n\t}\n\n\tif (!c_before && quit_quietly_on_none) return 0;\n\n\t/* Populate with characters */\n\tuint32_t * tmp = malloc(sizeof(uint32_t) * (c_before+1));\n\tint j = 0;\n\twhile (c_before) {\n\t\ttmp[j] = env->lines[env->line_no-1]->text[env->col_no-c_before-1].codepoint;\n\t\tc_before--;\n\t\tj++;\n\t}\n\ttmp[j] = 0;\n\n\t/*\n\t * TODO matches should probably be a struct with more data than just\n\t * the matching string; maybe offset where the needle was found,\n\t * class information, source file information - anything we can extract\n\t * from ctags, but also other information for other sources of completion.\n\t */\n\tstruct completion_match *matches;\n\tint matches_count;\n\n\t/* TODO just reading ctags is rather mediocre; can we do something cool here? */\n\tif (read_tags(tmp, &matches, &matches_count, 0)) {\n\t\tgoto _completion_done;\n\t}\n\n\t/* Draw box with matches at cursor-width(tmp) */\n\tif (quit_quietly_on_none && matches_count == 0) {\n\t\tfree(tmp);\n\t\tfree(matches);\n\t\treturn 0;\n\t}\n\n\tdraw_completion_matches(tmp, matches, matches_count, 0);\n\n_completion_done:\n\tplace_cursor_actual();\n\twhile (1) {\n\t\tc = bim_getch();\n\t\tif (c == -1) continue;\n\t\tif (matches_count < 1) {\n\t\t\tredraw_all();\n\t\t\tbreak;\n\t\t}\n\t\tif (c == 15) {\n\t\t\tindex = (index + 1) % matches_count;\n\t\t\tdraw_completion_matches(tmp, matches, matches_count, index);\n\t\t\tplace_cursor_actual();\n\t\t\tcontinue;\n\t\t} else if (c == '\\t') {\n\t\t\tfor (unsigned int i = j; i < strlen(matches[index].string); ++i) {\n\t\t\t\tinsert_char(matches[index].string[i]);\n\t\t\t}\n\t\t\tset_preferred_column();\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t\tgoto _finish_completion;\n\t\t} else if (isgraph(c) && c != '}') {\n\t\t\t/* insert and continue matching */\n\t\t\tinsert_char(c);\n\t\t\tset_preferred_column();\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t\tretval = 1;\n\t\t\tgoto _finish_completion;\n\t\t} else if (c == DELETE_KEY || c == BACKSPACE_KEY) {\n\t\t\tdelete_at_cursor();\n\t\t\tset_preferred_column();\n\t\t\tredraw_text();\n\t\t\tplace_cursor_actual();\n\t\t\tretval = 1;\n\t\t\tgoto _finish_completion;\n\t\t}\n\t\t/* TODO: Keyboard navigation of the matches list would be nice */\n\t\tredraw_all();\n\t\tbreak;\n\t}\n\tbim_unget(c);\n_finish_completion:\n\tfor (int i = 0; i < matches_count; ++i) {\n\t\tfree_completion_match(&matches[i]);\n\t}\n\tfree(matches);\n\tfree(tmp);\n\treturn retval;\n}\n\n/**\n * Set the search string from a UTF-8 sequence.\n * Since the search string is normally a series of codepoints, this saves\n * some effort when trying to search for things we pulled from the outside world.\n * (eg., ctags search terms)\n */\nstatic void set_search_from_bytes(char * bytes) {\n\tif (global_config.search) free(global_config.search);\n\tglobal_config.search = malloc(sizeof(uint32_t) * (strlen(bytes) * 2 + 1));\n\tuint32_t * s = global_config.search;\n\tchar * tmp = bytes;\n\tuint32_t c, istate = 0;\n\twhile (*tmp) {\n\t\tif (strchr(\"\\\\.()[]+*?\", *tmp)) {\n\t\t\t*s++ = '\\\\';\n\t\t\t*s++ = *tmp;\n\t\t\t*s = 0;\n\t\t\ttmp++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!decode(&istate, &c, *tmp)) {\n\t\t\t*s = c;\n\t\t\ts++;\n\t\t\t*s = 0;\n\t\t} else if (istate == UTF8_REJECT) {\n\t\t\tistate = 0;\n\t\t}\n\t\ttmp++;\n\t}\n}\n\nstatic void _perform_correct_search(struct completion_match * matches, int i) {\n\tif (matches[i].search[0] == '/') {\n\t\tset_search_from_bytes(&matches[i].search[1]);\n\t\tsearch_next();\n\t} else {\n\t\tgoto_line(atoi(matches[i].search));\n\t}\n}\n\nBIM_ACTION(goto_definition, 0,\n\t\"Jump to the definition of the word under under cursor.\"\n,void) {\n\tuint32_t * word = get_word_under_cursor();\n\tif (!word) {\n\t\trender_error(\"No match\");\n\t\treturn;\n\t}\n\n\tstruct completion_match *matches;\n\tint matches_count;\n\n\tif (read_tags(word, &matches, &matches_count, 1)) {\n\t\trender_error(\"No tags file\");\n\t\tgoto _done;\n\t}\n\n\tif (!matches_count) {\n\t\trender_error(\"No match\");\n\t\tgoto _done;\n\t}\n\n\tif (env->file_name && !strcmp(matches[0].file, env->file_name)) {\n\t\t_perform_correct_search(matches, 0);\n\t} else {\n\t\t/* Check if there were other matches that are in this file */\n\t\tfor (int i =1; env->file_name && i < matches_count; ++i) {\n\t\t\tif (!strcmp(matches[i].file, env->file_name)) {\n\t\t\t\t_perform_correct_search(matches, i);\n\t\t\t\tgoto _done;\n\t\t\t}\n\t\t}\n\t\t/* Check buffers */\n\t\tfor (int i = 0; i < buffers_len; ++i) {\n\t\t\tif (buffers[i]->file_name && !strcmp(matches[0].file,buffers[i]->file_name)) {\n\t\t\t\tif (left_buffer && buffers[i] != left_buffer && buffers[i] != right_buffer) unsplit();\n\t\t\t\tenv = buffers[i];\n\t\t\t\tredraw_tabbar();\n\t\t\t\t_perform_correct_search(matches, i);\n\t\t\t\tgoto _done;\n\t\t\t}\n\t\t}\n\t\t/* Okay, let's try opening */\n\t\tbuffer_t * old_buf = env;\n\t\topen_file(matches[0].file);\n\t\tif (env != old_buf) {\n\t\t\t_perform_correct_search(matches, 0);\n\t\t} else {\n\t\t\trender_error(\"Could not locate file containing definition\");\n\t\t}\n\t}\n\n_done:\n\tfor (int i = 0; i < matches_count; ++i) {\n\t\tfree_completion_match(&matches[i]);\n\t}\n\tfree(matches);\n\tfree(word);\n}\n\n/**\n * Read one codepoint, with verbatim support.\n */\nint read_one_character(char * message) {\n\t/* Read one character and replace */\n\tif (!global_config.overlay_mode) {\n\t\trender_commandline_message(message);\n\t\tplace_cursor_actual();\n\t}\n\tint c;\n\twhile ((c = bim_getkey(DEFAULT_KEY_WAIT))) {\n\t\tif (c == KEY_TIMEOUT) continue;\n\t\tif (c == KEY_CTRL_V) {\n\t\t\tif (!global_config.overlay_mode) {\n\t\t\t\trender_commandline_message(message);\n\t\t\t\tprintf(\" ^V\");\n\t\t\t\tplace_cursor_actual();\n\t\t\t}\n\t\t\twhile ((c = bim_getch()) == -1);\n\t\t\tbreak;\n\t\t}\n\t\tbreak;\n\t}\n\n\tredraw_commandline();\n\treturn c;\n}\n\nint read_one_byte(char * message) {\n\tif (!global_config.overlay_mode) {\n\t\trender_commandline_message(message);\n\t\tplace_cursor_actual();\n\t}\n\tint c;\n\twhile ((c = bim_getch())) {\n\t\tif (c == -1) continue;\n\t\tbreak;\n\t}\n\tredraw_commandline();\n\treturn c;\n}\n\nBIM_ACTION(cursor_left_with_wrap, 0,\n\t\"Move the cursor one position left, wrapping to the previous line.\"\n,void) {\n\tif (env->line_no > 1 && env->col_no == 1) {\n\t\tenv->line_no--;\n\t\tenv->col_no = env->lines[env->line_no-1]->actual;\n\t\tset_preferred_column();\n\t\tplace_cursor_actual();\n\t} else {\n\t\tcursor_left();\n\t}\n}\n\nBIM_ACTION(prepend_and_insert, ACTION_IS_RW,\n\t\"Insert a new line before the current line and enter insert mode.\"\n,void) {\n\tset_history_break();\n\tenv->lines = add_line(env->lines, env->line_no-1);\n\tenv->col_no = 1;\n\tadd_indent(env->line_no-1,env->line_no,0);\n\tif (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++;\n\tredraw_text();\n\tset_preferred_column();\n\tset_modified();\n\tplace_cursor_actual();\n\tenv->mode = MODE_INSERT;\n}\n\nBIM_ACTION(append_and_insert, ACTION_IS_RW,\n\t\"Insert a new line after the current line and enter insert mode.\"\n,void) {\n\tset_history_break();\n\tunhighlight_matching_paren();\n\tenv->lines = add_line(env->lines, env->line_no);\n\tenv->col_no = 1;\n\tenv->line_no += 1;\n\tadd_indent(env->line_no-1,env->line_no-2,0);\n\tset_preferred_column();\n\tif (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) {\n\t\tenv->offset += 1;\n\t}\n\tif (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++;\n\tredraw_text();\n\tset_modified();\n\tplace_cursor_actual();\n\tenv->mode = MODE_INSERT;\n}\n\nBIM_ACTION(insert_after_cursor, ACTION_IS_RW,\n\t\"Place the cursor after the selected character and enter insert mode.\"\n,void) {\n\tif (env->col_no < env->lines[env->line_no-1]->actual + 1) {\n\t\tenv->col_no += 1;\n\t}\n\tenter_insert();\n}\n\nBIM_ACTION(delete_forward, ACTION_IS_RW,\n\t\"Delete the character under the cursor.\"\n,void) {\n\tif (env->col_no <= env->lines[env->line_no-1]->actual) {\n\t\tline_delete(env->lines[env->line_no-1], env->col_no, env->line_no-1);\n\t\tredraw_text();\n\t} else if (env->col_no == env->lines[env->line_no-1]->actual + 1 && env->line_count > env->line_no) {\n\t\tmerge_lines(env->lines, env->line_no);\n\t\tredraw_text();\n\t}\n\tset_modified();\n\tredraw_statusbar();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(delete_forward_and_insert, ACTION_IS_RW,\n\t\"Delete the character under the cursor and enter insert mode.\"\n,void) {\n\tset_history_break();\n\tdelete_forward();\n\tenv->mode = MODE_INSERT;\n}\n\nBIM_ACTION(paste, ARG_IS_CUSTOM | ACTION_IS_RW,\n\t\"Paste yanked text before (`P`) or after (`p`) the cursor.\"\n,int direction) {\n\tif (global_config.yanks) {\n\t\tenv->slowop = 1;\n\t\tif (!global_config.yank_is_full_lines) {\n\t\t\t/* Handle P for paste before, p for past after */\n\t\t\tint target_column = (direction == -1 ? (env->col_no) : (env->col_no+1));\n\t\t\tif (target_column > env->lines[env->line_no-1]->actual + 1) {\n\t\t\t\ttarget_column = env->lines[env->line_no-1]->actual + 1;\n\t\t\t}\n\t\t\tif (global_config.yank_count > 1) {\n\t\t\t\t/* Spit the current line at the current position */\n\t\t\t\tenv->lines = split_line(env->lines, env->line_no - 1, target_column - 1); /* Split after */\n\t\t\t}\n\t\t\t/* Insert first line at current position */\n\t\t\tfor (int i = 0; i < global_config.yanks[0]->actual; ++i) {\n\t\t\t\tenv->lines[env->line_no - 1] = line_insert(env->lines[env->line_no - 1], global_config.yanks[0]->text[i], target_column + i - 1, env->line_no - 1); \n\t\t\t}\n\t\t\tif (global_config.yank_count > 1) {\n\t\t\t\t/* Insert full lines */\n\t\t\t\tfor (unsigned int i = 1; i < global_config.yank_count - 1; ++i) {\n\t\t\t\t\tenv->lines = add_line(env->lines, env->line_no);\n\t\t\t\t}\n\t\t\t\tfor (unsigned int i = 1; i < global_config.yank_count - 1; ++i) {\n\t\t\t\t\treplace_line(env->lines, env->line_no + i - 1, global_config.yanks[i]);\n\t\t\t\t}\n\t\t\t\t/* Insert characters from last line into (what was) the next line */\n\t\t\t\tfor (int i = 0; i < global_config.yanks[global_config.yank_count-1]->actual; ++i) {\n\t\t\t\t\tenv->lines[env->line_no + global_config.yank_count - 2] = line_insert(env->lines[env->line_no + global_config.yank_count - 2], global_config.yanks[global_config.yank_count-1]->text[i], i, env->line_no + global_config.yank_count - 2);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t/* Insert full lines */\n\t\t\tfor (unsigned int i = 0; i < global_config.yank_count; ++i) {\n\t\t\t\tenv->lines = add_line(env->lines, env->line_no - (direction == -1 ? 1 : 0));\n\t\t\t}\n\t\t\tfor (unsigned int i = 0; i < global_config.yank_count; ++i) {\n\t\t\t\treplace_line(env->lines, env->line_no - (direction == -1 ? 1 : 0) + i, global_config.yanks[i]);\n\t\t\t}\n\t\t}\n\t\tenv->slowop = 0;\n\t\tschedule_complete_recalc();\n\t\t/* Recalculate whole document syntax */\n\t\tif (direction == 1) {\n\t\t\tif (global_config.yank_is_full_lines) {\n\t\t\t\tenv->line_no += 1;\n\t\t\t} else {\n\t\t\t\tif (global_config.yank_count == 1) {\n\t\t\t\t\tenv->col_no = env->col_no + global_config.yanks[0]->actual;\n\t\t\t\t} else {\n\t\t\t\t\tenv->line_no = env->line_no + global_config.yank_count - 1;\n\t\t\t\t\tenv->col_no = global_config.yanks[global_config.yank_count-1]->actual;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (global_config.yank_is_full_lines) {\n\t\t\tenv->col_no = 1;\n\t\t\tfor (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\t\t\tif (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) {\n\t\t\t\t\tenv->col_no = i + 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tset_history_break();\n\t\tset_modified();\n\t\tredraw_all();\n\t}\n}\n\nBIM_ACTION(insert_at_end, ACTION_IS_RW,\n\t\"Move the cursor to the end of the current line and enter insert mode.\"\n,void) {\n\tenv->col_no = env->lines[env->line_no-1]->actual+1;\n\tenv->mode = MODE_INSERT;\n\tset_history_break();\n}\n\nBIM_ACTION(enter_replace, ACTION_IS_RW,\n\t\"Enter replace mode.\"\n,void) {\n\tenv->mode = MODE_REPLACE;\n\tset_history_break();\n}\n\nBIM_ACTION(toggle_numbers, 0,\n\t\"Toggle the display of line numbers.\"\n,void) {\n\tenv->numbers = !env->numbers;\n\tredraw_all();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(toggle_gutter, 0,\n\t\"Toggle the display of the revision status gutter.\"\n,void) {\n\tenv->gutter = !env->gutter;\n\tredraw_all();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(toggle_indent, 0,\n\t\"Toggle smart indentation.\"\n,void) {\n\tenv->indent = !env->indent;\n\tredraw_statusbar();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(toggle_smartcomplete, 0,\n\t\"Toggle smart completion.\"\n,void) {\n\tglobal_config.smart_complete = !global_config.smart_complete;\n\tredraw_statusbar();\n\tplace_cursor_actual();\n}\n\nBIM_ACTION(expand_split_right, 0,\n\t\"Move the view split divider to the right.\"\n,void) {\n\tglobal_config.split_percent += 1;\n\tupdate_split_size();\n\tredraw_all();\n}\n\nBIM_ACTION(expand_split_left, 0,\n\t\"Move the view split divider to the left.\"\n,void) {\n\tglobal_config.split_percent -= 1;\n\tupdate_split_size();\n\tredraw_all();\n}\n\nBIM_ACTION(go_page_up, 0,\n\t\"Jump up a screenfull.\"\n,void) {\n\tint destination = env->line_no - (global_config.term_height - 6);\n\tif (destination < 1) destination = 1;\n\tgoto_line(destination);\n}\n\nBIM_ACTION(go_page_down, 0,\n\t\"Jump down a screenfull.\"\n,void) {\n\tgoto_line(env->line_no + (global_config.term_height - 6));\n}\n\nBIM_ACTION(jump_to_matching_bracket, 0,\n\t\"Find and jump to the matching bracket for the character under the cursor.\"\n,void) {\n\trecalculate_selected_lines();\n\n\t/* Search forward first */\n\tfor (int i = env->col_no; i <= env->lines[env->line_no-1]->actual; ++i) {\n\t\tif (is_paren(env->lines[env->line_no-1]->text[i-1].codepoint)) {\n\t\t\tenv->col_no = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Then find match */\n\tint paren_line = -1, paren_col = -1;\n\tfind_matching_paren(&paren_line, &paren_col, 1);\n\tif (paren_line != -1) {\n\t\tenv->line_no = paren_line;\n\t\tenv->col_no = paren_col;\n\t\tset_preferred_column();\n\t\tplace_cursor_actual();\n\t\tredraw_statusbar();\n\t}\n}\n\nBIM_ACTION(jump_to_previous_blank, 0,\n\t\"Jump to the preceding blank line before the cursor.\"\n,void) {\n\tenv->col_no = 1;\n\tif (env->line_no == 1) return;\n\tdo {\n\t\tenv->line_no--;\n\t\tif (env->lines[env->line_no-1]->actual == 0) break;\n\t} while (env->line_no > 1);\n\tset_preferred_column();\n\tredraw_statusbar();\n}\n\nBIM_ACTION(jump_to_next_blank, 0,\n\t\"Jump to the next blank line after the cursor.\"\n,void) {\n\tenv->col_no = 1;\n\tif (env->line_no == env->line_count) return;\n\tdo {\n\t\tenv->line_no++;\n\t\tif (env->lines[env->line_no-1]->actual == 0) break;\n\t} while (env->line_no < env->line_count);\n\tset_preferred_column();\n\tredraw_statusbar();\n}\n\nBIM_ACTION(first_non_whitespace, 0,\n\t\"Jump to the first non-whitespace character in the current line.\"\n,void) {\n\tfor (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\tif (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) {\n\t\t\tenv->col_no = i + 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\tset_preferred_column();\n\tredraw_statusbar();\n}\n\nBIM_ACTION(next_line_non_whitespace, 0,\n\t\"Jump to the first non-whitespace character in the next next line.\"\n,void) {\n\tif (env->line_no < env->line_count) {\n\t\tenv->line_no++;\n\t\tenv->col_no = 1;\n\t} else {\n\t\treturn;\n\t}\n\tfirst_non_whitespace();\n}\n\nBIM_ACTION(smart_backspace, ACTION_IS_RW,\n\t\"Delete the preceding character, with special handling for indentation.\"\n,void) {\n\tif (!env->tabs && env->col_no > 1) {\n\t\tint i;\n\t\tfor (i = 0; i < env->col_no-1; ++i) {\n\t\t\tif (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) break;\n\t\t}\n\t\tif (i == env->col_no-1) {\n\t\t\t/* Backspace until aligned */\n\t\t\tdelete_at_cursor();\n\t\t\twhile (env->col_no > 1 && (env->col_no-1) % env->tabstop) {\n\t\t\t\tdelete_at_cursor();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n\tdelete_at_cursor();\n}\n\nBIM_ACTION(perform_omni_completion, ACTION_IS_RW,\n\t\"(temporary) Perform smart symbol completion from ctags.\"\n,void) {\n\t/* This should probably be a submode */\n\twhile (omni_complete(0) == 1);\n}\n\nBIM_ACTION(smart_tab, ACTION_IS_RW,\n\t\"Insert a tab or spaces depending on indent mode. (Use ^V <tab> to guarantee a literal tab)\"\n,void) {\n\tif (env->tabs) {\n\t\tinsert_char('\\t');\n\t} else {\n\t\tfor (int i = 0; i < env->tabstop; ++i) {\n\t\t\tinsert_char(' ');\n\t\t}\n\t}\n}\n\nBIM_ACTION(smart_comment_end, ARG_IS_INPUT | ACTION_IS_RW,\n\t\"Insert a `/` ending a C-style comment.\"\n,int c) {\n\t/* smart *end* of comment anyway */\n\tif (env->indent) {\n\t\tif ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0x1F) == FLAG_COMMENT &&\n\t\t\t(env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') &&\n\t\t\t(env->col_no > 3) &&\n\t\t\t(env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) {\n\t\t\tenv->col_no--;\n\t\t\treplace_char('/');\n\t\t\tenv->col_no++;\n\t\t\tplace_cursor_actual();\n\t\t\treturn;\n\t\t}\n\t}\n\tinsert_char(c);\n}\n\nBIM_ACTION(smart_brace_end, ARG_IS_INPUT | ACTION_IS_RW,\n\t\"Insert a closing brace and smartly position it if it is the first character on a line.\"\n,int c) {\n\tif (env->indent) {\n\t\tint was_whitespace = 1;\n\t\tfor (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) {\n\t\t\tif (env->lines[env->line_no-1]->text[i].codepoint != ' ' &&\n\t\t\t\tenv->lines[env->line_no-1]->text[i].codepoint != '\\t') {\n\t\t\t\twas_whitespace = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tinsert_char(c);\n\t\tif (was_whitespace) {\n\t\t\tint line = -1, col = -1;\n\t\t\tenv->col_no--;\n\t\t\tfind_matching_paren(&line,&col, 1);\n\t\t\tif (line != -1) {\n\t\t\t\tline = find_brace_line_start(line, col);\n\t\t\t\twhile (env->lines[env->line_no-1]->actual) {\n\t\t\t\t\tline_delete(env->lines[env->line_no-1], env->lines[env->line_no-1]->actual, env->line_no-1);\n\t\t\t\t}\n\t\t\t\tadd_indent(env->line_no-1,line-1,1);\n\t\t\t\tenv->col_no = env->lines[env->line_no-1]->actual + 1;\n\t\t\t\tinsert_char(c);\n\t\t\t}\n\t\t}\n\t\tset_preferred_column();\n\t\treturn;\n\t}\n\tinsert_char(c);\n}\n\nBIM_ACTION(enter_line_selection_and_cursor_up, 0,\n\t\"Enter line selection and move the cursor up one line.\"\n,void) {\n\tenter_line_selection();\n\tcursor_up();\n}\n\nBIM_ACTION(enter_line_selection_and_cursor_down, 0,\n\t\"Enter line selection and move the cursor down one line.\"\n,void) {\n\tenter_line_selection();\n\tcursor_down();\n}\n\nBIM_ACTION(shift_horizontally, ARG_IS_CUSTOM,\n\t\"Shift the current line or screen view horizontally, depending on settings.\"\n,int amount) {\n\tenv->coffset += amount;\n\tif (env->coffset < 0) env->coffset = 0;\n\tredraw_text();\n}\n\nstatic int state_before_paste = 0;\nstatic int line_before_paste = 0;\nBIM_ACTION(paste_begin, 0, \"Begin bracketed paste; disable indentation, completion, etc.\",void) {\n\tif (global_config.smart_complete) state_before_paste |= 0x01;\n\tif (env->indent) state_before_paste |= 0x02;\n\n\tglobal_config.smart_complete = 0;\n\tenv->indent = 0;\n\tenv->slowop = 1;\n\tline_before_paste = env->line_no;\n}\n\nBIM_ACTION(paste_end, 0, \"End bracketed paste; restore indentation, completion, etc.\",void) {\n\tif (state_before_paste & 0x01) global_config.smart_complete = 1;\n\tif (state_before_paste & 0x02) env->indent = 1;\n\tenv->slowop = 0;\n\tint line_to_recalculate = (line_before_paste > 1 ? line_before_paste - 1 : 0);\n\trecalculate_syntax(env->lines[line_to_recalculate], line_to_recalculate);\n\tredraw_all();\n}\n\n#define MAP_ACTION(key, func, opts, arg) {key, opts, {{(uintptr_t)func, arg}}}\n\nstruct action_map _NORMAL_MAP[] = {\n\tMAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, opt_rep, 0),\n\tMAP_ACTION('V',           enter_line_selection, 0, 0),\n\tMAP_ACTION('v',           enter_char_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_V,    enter_col_selection, 0, 0),\n\tMAP_ACTION('O',           prepend_and_insert, opt_rw, 0),\n\tMAP_ACTION('o',           append_and_insert, opt_rw, 0),\n\tMAP_ACTION('a',           insert_after_cursor, opt_rw, 0),\n\tMAP_ACTION('s',           delete_forward_and_insert, opt_rw, 0),\n\tMAP_ACTION('x',           delete_forward, opt_rep | opt_rw, 0),\n\tMAP_ACTION('P',           paste, opt_arg | opt_rw, -1),\n\tMAP_ACTION('p',           paste, opt_arg | opt_rw, 1),\n\tMAP_ACTION('r',           replace_char, opt_char | opt_rw, 0),\n\tMAP_ACTION('A',           insert_at_end, opt_rw, 0),\n\tMAP_ACTION('u',           undo_history, opt_rw, 0),\n\tMAP_ACTION(KEY_CTRL_R,    redo_history, opt_rw, 0),\n\tMAP_ACTION(KEY_CTRL_L,    redraw_all, 0, 0),\n\tMAP_ACTION(KEY_CTRL_G,    goto_definition, 0, 0),\n\tMAP_ACTION('i',           enter_insert, opt_rw, 0),\n\tMAP_ACTION('R',           enter_replace, opt_rw, 0),\n\tMAP_ACTION(KEY_SHIFT_UP,   enter_line_selection_and_cursor_up, 0, 0),\n\tMAP_ACTION(KEY_SHIFT_DOWN, enter_line_selection_and_cursor_down, 0, 0),\n\tMAP_ACTION(KEY_ALT_UP,    previous_tab, 0, 0),\n\tMAP_ACTION(KEY_ALT_DOWN,  next_tab, 0, 0),\n\tMAP_ACTION(KEY_CTRL_UNDERSCORE, start_file_search, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _INSERT_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_insert, 0, 0),\n\tMAP_ACTION(KEY_DELETE,    delete_forward, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    leave_insert, 0, 0),\n\tMAP_ACTION(KEY_BACKSPACE, smart_backspace, 0, 0),\n\tMAP_ACTION(KEY_ENTER,     insert_line_feed, 0, 0),\n\tMAP_ACTION(KEY_CTRL_O,    perform_omni_completion, 0, 0),\n\tMAP_ACTION(KEY_CTRL_V,    insert_char, opt_byte, 0),\n\tMAP_ACTION(KEY_CTRL_W,    delete_word, 0, 0),\n\tMAP_ACTION('\\t',          smart_tab, 0, 0),\n\tMAP_ACTION('/',           smart_comment_end, opt_arg, '/'),\n\tMAP_ACTION('}',           smart_brace_end, opt_arg, '}'),\n\tMAP_ACTION(KEY_PASTE_BEGIN, paste_begin, 0, 0),\n\tMAP_ACTION(KEY_PASTE_END, paste_end, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _REPLACE_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_insert, 0, 0),\n\tMAP_ACTION(KEY_DELETE,    delete_forward, 0, 0),\n\tMAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0),\n\tMAP_ACTION(KEY_ENTER,     insert_line_feed, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _LINE_SELECTION_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    leave_selection, 0, 0),\n\tMAP_ACTION('V',           leave_selection, 0, 0),\n\tMAP_ACTION('v',           switch_selection_mode, opt_arg, MODE_CHAR_SELECTION),\n\tMAP_ACTION('y',           yank_lines, opt_norm, 0),\n\tMAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0),\n\tMAP_ACTION('\\t',          adjust_indent, opt_arg | opt_rw, 1),\n\tMAP_ACTION(KEY_SHIFT_TAB, adjust_indent, opt_arg | opt_rw, -1),\n\tMAP_ACTION('D',           delete_and_yank_lines, opt_rw | opt_norm, 0),\n\tMAP_ACTION('d',           delete_and_yank_lines, opt_rw | opt_norm, 0),\n\tMAP_ACTION('x',           delete_and_yank_lines, opt_rw | opt_norm, 0),\n\tMAP_ACTION('s',           delete_lines_and_enter_insert, opt_rw, 0),\n\tMAP_ACTION('r',           replace_chars_in_line, opt_char | opt_rw, 0),\n\n\tMAP_ACTION(KEY_SHIFT_UP,   cursor_up, 0, 0),\n\tMAP_ACTION(KEY_SHIFT_DOWN, cursor_down, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _CHAR_SELECTION_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    leave_selection, 0, 0),\n\tMAP_ACTION('v',           leave_selection, 0, 0),\n\tMAP_ACTION('V',           switch_selection_mode, opt_arg, MODE_LINE_SELECTION),\n\tMAP_ACTION('y',           yank_characters, opt_norm, 0),\n\tMAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0),\n\tMAP_ACTION('D',           delete_and_yank_chars, opt_rw | opt_norm, 0),\n\tMAP_ACTION('d',           delete_and_yank_chars, opt_rw | opt_norm, 0),\n\tMAP_ACTION('x',           delete_and_yank_chars, opt_rw | opt_norm, 0),\n\tMAP_ACTION('s',           delete_chars_and_enter_insert, opt_rw, 0),\n\tMAP_ACTION('r',           replace_chars, opt_char | opt_rw, 0),\n\tMAP_ACTION('A',           insert_at_end_of_selection, opt_rw, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _COL_SELECTION_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_V,    leave_selection, 0, 0),\n\tMAP_ACTION('I',           enter_col_insert, opt_rw, 0),\n\tMAP_ACTION('a',           enter_col_insert_after, opt_rw, 0),\n\tMAP_ACTION('d',           delete_at_column, opt_arg | opt_rw, 1),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _COL_INSERT_MAP[] = {\n\tMAP_ACTION(KEY_ESCAPE,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    leave_selection, 0, 0),\n\tMAP_ACTION(KEY_BACKSPACE, delete_at_column, opt_arg, -1),\n\tMAP_ACTION(KEY_DELETE,    delete_at_column, opt_arg, 1),\n\tMAP_ACTION(KEY_ENTER,     NULL, 0, 0),\n\tMAP_ACTION(KEY_CTRL_W,    NULL, 0, 0),\n\tMAP_ACTION(KEY_CTRL_V,    insert_char_at_column, opt_char, 0),\n\tMAP_ACTION('\\t',          insert_tab_at_column, 0, 0),\n\tMAP_ACTION(KEY_LEFT,      column_left, 0, 0),\n\tMAP_ACTION(KEY_RIGHT,     column_right, 0, 0),\n\tMAP_ACTION(KEY_UP,        column_up, 0, 0),\n\tMAP_ACTION(KEY_DOWN,      column_down, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _NAVIGATION_MAP[] = {\n\t/* Common navigation */\n\tMAP_ACTION(KEY_CTRL_B,    go_page_up, opt_rep, 0),\n\tMAP_ACTION(KEY_CTRL_F,    go_page_down, opt_rep, 0),\n\tMAP_ACTION(':',           enter_command, 0, 0),\n\tMAP_ACTION('/',           enter_search, opt_arg, 1),\n\tMAP_ACTION('?',           enter_search, opt_arg, 0),\n\tMAP_ACTION('n',           search_next, opt_rep, 0),\n\tMAP_ACTION('N',           search_prev, opt_rep, 0),\n\tMAP_ACTION('j',           cursor_down, opt_rep, 0),\n\tMAP_ACTION('k',           cursor_up, opt_rep, 0),\n\tMAP_ACTION('h',           cursor_left, opt_rep, 0),\n\tMAP_ACTION('l',           cursor_right, opt_rep, 0),\n\tMAP_ACTION('b',           word_left, opt_rep, 0),\n\tMAP_ACTION('w',           word_right, opt_rep, 0),\n\tMAP_ACTION('B',           big_word_left, opt_rep, 0),\n\tMAP_ACTION('W',           big_word_right, opt_rep, 0),\n\n\tMAP_ACTION('<',           shift_horizontally, opt_arg, -1),\n\tMAP_ACTION('>',           shift_horizontally, opt_arg, 1),\n\n\tMAP_ACTION('f',           find_character_forward, opt_rep | opt_arg | opt_char, 'f'),\n\tMAP_ACTION('F',           find_character_backward, opt_rep | opt_arg | opt_char, 'F'),\n\tMAP_ACTION('t',           find_character_forward, opt_rep | opt_arg | opt_char, 't'),\n\tMAP_ACTION('T',           find_character_backward, opt_rep | opt_arg | opt_char, 'T'),\n\n\tMAP_ACTION('G',           goto_line, opt_nav, 0),\n\tMAP_ACTION('*',           search_under_cursor, 0, 0),\n\tMAP_ACTION(' ',           go_page_down, opt_rep, 0),\n\tMAP_ACTION('%',           jump_to_matching_bracket, 0, 0),\n\tMAP_ACTION('{',           jump_to_previous_blank, opt_rep, 0),\n\tMAP_ACTION('}',           jump_to_next_blank, opt_rep, 0),\n\tMAP_ACTION('$',           cursor_end, 0, 0),\n\tMAP_ACTION('|',           cursor_home, 0, 0),\n\tMAP_ACTION(KEY_ENTER,     next_line_non_whitespace, opt_rep, 0),\n\tMAP_ACTION('^',           first_non_whitespace, 0, 0),\n\tMAP_ACTION('0',           cursor_home, 0, 0),\n\n\tMAP_ACTION(-1, NULL, 0, 0),\n};\n\nstruct action_map _ESCAPE_MAP[] = {\n\tMAP_ACTION(KEY_F1,        toggle_numbers, 0, 0),\n\tMAP_ACTION(KEY_F2,        toggle_indent, 0, 0),\n\tMAP_ACTION(KEY_F3,        toggle_gutter, 0, 0),\n\tMAP_ACTION(KEY_F4,        toggle_smartcomplete, 0, 0),\n\tMAP_ACTION(KEY_MOUSE,     handle_mouse, 0, 0),\n\tMAP_ACTION(KEY_MOUSE_SGR, handle_mouse_sgr, 0, 0),\n\n\tMAP_ACTION(KEY_UP,        cursor_up, opt_rep, 0),\n\tMAP_ACTION(KEY_DOWN,      cursor_down, opt_rep, 0),\n\n\tMAP_ACTION(KEY_RIGHT,     cursor_right, opt_rep, 0),\n\tMAP_ACTION(KEY_CTRL_RIGHT, big_word_right, opt_rep, 0),\n\tMAP_ACTION(KEY_SHIFT_RIGHT, word_right, opt_rep, 0),\n\tMAP_ACTION(KEY_ALT_RIGHT, expand_split_right, opt_rep, 0),\n\tMAP_ACTION(KEY_ALT_SHIFT_RIGHT, use_right_buffer, opt_rep, 0),\n\n\tMAP_ACTION(KEY_LEFT,      cursor_left, opt_rep, 0),\n\tMAP_ACTION(KEY_CTRL_LEFT, big_word_left, opt_rep, 0),\n\tMAP_ACTION(KEY_SHIFT_LEFT, word_left, opt_rep, 0),\n\tMAP_ACTION(KEY_ALT_LEFT, expand_split_left, opt_rep, 0),\n\tMAP_ACTION(KEY_ALT_SHIFT_LEFT, use_left_buffer, opt_rep, 0),\n\n\tMAP_ACTION(KEY_HOME, cursor_home, 0, 0),\n\tMAP_ACTION(KEY_END, cursor_end, 0, 0),\n\tMAP_ACTION(KEY_PAGE_UP, go_page_up, opt_rep, 0),\n\tMAP_ACTION(KEY_PAGE_DOWN, go_page_down, opt_rep, 0),\n\n\tMAP_ACTION(KEY_CTRL_Z,   suspend, 0, 0),\n\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\nstruct action_map _COMMAND_MAP[] = {\n\tMAP_ACTION(KEY_ENTER,     command_accept, 0, 0),\n\tMAP_ACTION('\\t',          command_tab_complete_buffer, 0, 0),\n\tMAP_ACTION(KEY_UP,        command_scroll_history, opt_arg, -1), /* back */\n\tMAP_ACTION(KEY_DOWN,      command_scroll_history, opt_arg, 1), /* forward */\n\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\nstruct action_map _FILESEARCH_MAP[] = {\n\tMAP_ACTION(KEY_ENTER,    file_search_accept, 0, 0),\n\n\tMAP_ACTION(KEY_UP,       NULL, 0, 0),\n\tMAP_ACTION(KEY_DOWN,     NULL, 0, 0),\n\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\nstruct action_map _SEARCH_MAP[] = {\n\tMAP_ACTION(KEY_ENTER,    search_accept, 0, 0),\n\n\tMAP_ACTION(KEY_UP,        command_scroll_search_history, opt_arg, -1), /* back */\n\tMAP_ACTION(KEY_DOWN,      command_scroll_search_history, opt_arg, 1), /* forward */\n\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\nstruct action_map _INPUT_BUFFER_MAP[] = {\n\t/* These are generic and shared with search */\n\tMAP_ACTION(KEY_ESCAPE,    command_discard, 0, 0),\n\tMAP_ACTION(KEY_CTRL_C,    command_discard, 0, 0),\n\tMAP_ACTION(KEY_BACKSPACE, command_backspace, 0, 0),\n\tMAP_ACTION(KEY_CTRL_W,    command_word_delete, 0, 0),\n\tMAP_ACTION(KEY_MOUSE,     eat_mouse, 0, 0),\n\tMAP_ACTION(KEY_MOUSE_SGR, eat_mouse_sgr, 0, 0),\n\tMAP_ACTION(KEY_LEFT,      command_cursor_left, 0, 0),\n\tMAP_ACTION(KEY_CTRL_LEFT, command_word_left, 0, 0),\n\tMAP_ACTION(KEY_RIGHT,     command_cursor_right, 0, 0),\n\tMAP_ACTION(KEY_CTRL_RIGHT,command_word_right, 0, 0),\n\tMAP_ACTION(KEY_HOME,      command_cursor_home, 0, 0),\n\tMAP_ACTION(KEY_END,       command_cursor_end, 0, 0),\n\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\n/* DIRECTORY_BROWSE_MAP is only to override KEY_ENTER and should not be remapped,\n * so unlike the others it is not going to be redefined as a pointer. */\nstruct action_map DIRECTORY_BROWSE_MAP[] = {\n\tMAP_ACTION(KEY_ENTER,     open_file_from_line, 0, 0),\n\tMAP_ACTION(-1, NULL, 0, 0)\n};\n\nstruct action_map * NORMAL_MAP = NULL;\nstruct action_map * INSERT_MAP = NULL;\nstruct action_map * REPLACE_MAP = NULL;\nstruct action_map * LINE_SELECTION_MAP = NULL;\nstruct action_map * CHAR_SELECTION_MAP = NULL;\nstruct action_map * COL_SELECTION_MAP = NULL;\nstruct action_map * COL_INSERT_MAP = NULL;\nstruct action_map * NAVIGATION_MAP = NULL;\nstruct action_map * ESCAPE_MAP = NULL;\nstruct action_map * COMMAND_MAP = NULL;\nstruct action_map * SEARCH_MAP = NULL;\nstruct action_map * FILESEARCH_MAP = NULL;\nstruct action_map * INPUT_BUFFER_MAP = NULL;\n\nstruct mode_names mode_names[] = {\n\t{\"Normal\",\"norm\",&NORMAL_MAP},\n\t{\"Insert\",\"insert\",&INSERT_MAP},\n\t{\"Replace\",\"replace\",&REPLACE_MAP},\n\t{\"Line Selection\",\"line\",&LINE_SELECTION_MAP},\n\t{\"Char Selection\",\"char\",&CHAR_SELECTION_MAP},\n\t{\"Col Selection\",\"col\",&COL_SELECTION_MAP},\n\t{\"Col Insert\",\"colinsert\",&COL_INSERT_MAP},\n\t{\"Navigation (Select)\",\"nav\",&NAVIGATION_MAP},\n\t{\"Escape (Select, Insert)\",\"esc\",&ESCAPE_MAP},\n\t{\"Command\",\"command\",&COMMAND_MAP},\n\t{\"Search\",\"search\",&SEARCH_MAP},\n\t{\"Input (Command, Search)\",\"input\",&INPUT_BUFFER_MAP},\n\t{NULL,NULL,NULL},\n};\n\ntypedef void (*action_no_arg)(void);\ntypedef void (*action_one_arg)(int);\ntypedef void (*action_two_arg)(int,int);\ntypedef void (*action_three_arg)(int,int,int);\n\nint handle_action(struct action_map * basemap, int key) {\n\tfor (struct action_map * map = basemap; map->key != -1; map++) {\n\t\tif (map->key == key) {\n\t\t\tif (!map->method) return 1;\n\t\t\tif ((map->options & opt_rw) && (env->readonly)) {\n\t\t\t\trender_error(\"Buffer is read-only\");\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\t/* Determine how to format this request */\n\t\t\tint reps = (map->options & opt_rep) ? ((nav_buffer) ? atoi(nav_buf) : 1) : 1;\n\t\t\tint c = 0;\n\t\t\tif (map->options & opt_char) {\n\t\t\t\tc = read_one_character(name_from_key(key));\n\t\t\t}\n\t\t\tif (map->options & opt_byte) {\n\t\t\t\tc = read_one_byte(name_from_key(key));\n\t\t\t}\n\t\t\tfor (int i = 0; i < reps; ++i) {\n\t\t\t\tif (((map->options & opt_char) || (map->options & opt_byte)) && (map->options & opt_arg)) {\n\t\t\t\t\t((action_two_arg)map->method)(map->arg, c);\n\t\t\t\t} else if ((map->options & opt_char) || (map->options & opt_byte)) {\n\t\t\t\t\t((action_one_arg)map->method)(c);\n\t\t\t\t} else if (map->options & opt_arg) {\n\t\t\t\t\t((action_one_arg)map->method)(map->arg);\n\t\t\t\t} else if (map->options & opt_nav) {\n\t\t\t\t\tif (nav_buffer) {\n\t\t\t\t\t\t((action_one_arg)map->method)(atoi(nav_buf));\n\t\t\t\t\t\treset_nav_buffer(0);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t((action_one_arg)map->method)(-1);\n\t\t\t\t\t}\n\t\t\t\t} else if (map->options & opt_krk) {\n\t\t\t\t\tptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack;\n\t\t\t\t\tkrk_push(map->callable);\n\t\t\t\t\tkrk_push(INTEGER_VAL(key));\n\t\t\t\t\tkrk_push(map->callable);\n\t\t\t\t\tKrkValue result = krk_callStack(2);\n\t\t\t\t\tkrk_currentThread.stackTop = krk_currentThread.stack + before;\n\t\t\t\t\tif (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) {\n\t\t\t\t\t\trender_error(\"Exception during action: %s\", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars);\n\t\t\t\t\t\tkrk_dumpTraceback();\n\t\t\t\t\t\tint key = 0;\n\t\t\t\t\t\twhile ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t((action_no_arg)map->method)();\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (map->options & opt_norm) {\n\t\t\t\tif (env->mode == MODE_INSERT || env->mode == MODE_REPLACE) leave_insert();\n\t\t\t\telse if (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION || env->mode == MODE_COL_SELECTION) leave_selection();\n\t\t\t\telse {\n\t\t\t\t\tenv->mode = MODE_NORMAL;\n\t\t\t\t\tredraw_all();\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint handle_nav_buffer(int key) {\n\tif ((key >= '1' && key <= '9') || (key == '0' && nav_buffer)) {\n\t\tif (nav_buffer < NAV_BUFFER_MAX) {\n\t\t\t/* Up to NAV_BUFFER_MAX=10 characters; that should be enough for most tasks */\n\t\t\tnav_buf[nav_buffer] = key;\n\t\t\tnav_buf[nav_buffer+1] = 0;\n\t\t\tnav_buffer++;\n\t\t\t/* Print the number buffer */\n\t\t\tredraw_commandline();\n\t\t}\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\n/**\n * NORMAL mode\n *\n * Default editor mode - just cursor navigation and keybinds\n * to enter the other modes.\n */\nvoid normal_mode(void) {\n\n\tint last_mode = MODE_NORMAL;\n\tint refresh = 0;\n\n\twhile (1) {\n\n\t\tif (global_config.overlay_mode) {\n\t\t\tif (global_config.overlay_mode == OVERLAY_MODE_COMMAND) {\n\t\t\t\tif (refresh) {\n\t\t\t\t\trender_command_input_buffer();\n\t\t\t\t\trefresh = 0;\n\t\t\t\t}\n\t\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\t\tif (key != KEY_TIMEOUT) {\n\t\t\t\t\trefresh = 1;\n\t\t\t\t\tif (!handle_action(COMMAND_MAP, key))\n\t\t\t\t\t\tif (!handle_action(INPUT_BUFFER_MAP, key))\n\t\t\t\t\t\t\tif (key < KEY_ESCAPE) command_insert_char(key);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) {\n\t\t\t\tif (refresh) {\n\t\t\t\t\tredraw_tabbar();\n\t\t\t\t\trender_command_input_buffer();\n\t\t\t\t\trefresh = 0;\n\t\t\t\t}\n\t\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\t\tif (key != KEY_TIMEOUT) {\n\t\t\t\t\trefresh = 1;\n\t\t\t\t\tif (!handle_action(FILESEARCH_MAP, key))\n\t\t\t\t\t\tif (!handle_action(INPUT_BUFFER_MAP, key))\n\t\t\t\t\t\t\tif (key < KEY_ESCAPE) command_insert_char(key);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) {\n\t\t\t\tif (refresh) {\n\t\t\t\t\trender_command_input_buffer();\n\t\t\t\t\trefresh = 0;\n\t\t\t\t}\n\t\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\t\tif (key != KEY_TIMEOUT) {\n\t\t\t\t\trefresh = 1;\n\t\t\t\t\tif (!handle_action(SEARCH_MAP, key)) {\n\t\t\t\t\t\tif (!handle_action(INPUT_BUFFER_MAP, key)) {\n\t\t\t\t\t\t\tif (key < KEY_ESCAPE) command_insert_char(key);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (global_config.overlay_mode == OVERLAY_MODE_SEARCH) {\n\t\t\t\t\t\t/* Find the next search match */\n\t\t\t\t\t\tuint32_t * buffer = malloc(sizeof(uint32_t) * (global_config.command_buffer->actual+1));\n\t\t\t\t\t\tfor (int i = 0; i < global_config.command_buffer->actual; ++i) {\n\t\t\t\t\t\t\tbuffer[i] = global_config.command_buffer->text[i].codepoint;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbuffer[global_config.command_buffer->actual] = 0;\n\t\t\t\t\t\tint line = -1, col = -1;\n\t\t\t\t\t\tif (global_config.search_direction == 1) {\n\t\t\t\t\t\t\tfind_match(global_config.prev_line, global_config.prev_col, &line, &col, buffer, NULL);\n\t\t\t\t\t\t\tif (line == -1 && global_config.search_wraps) {\n\t\t\t\t\t\t\t\tfind_match(1, 1, &line, &col, buffer, NULL);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfind_match_backwards(global_config.prev_line, global_config.prev_col, &line, &col, buffer);\n\t\t\t\t\t\t\tif (line == -1 && global_config.search_wraps) {\n\t\t\t\t\t\t\t\tfind_match_backwards(env->line_count, env->lines[env->line_count-1]->actual, &line, &col, buffer);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (line != -1) {\n\t\t\t\t\t\t\tenv->col_no = col;\n\t\t\t\t\t\t\tenv->line_no = line;\n\t\t\t\t\t\t\tset_preferred_column();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tenv->coffset = global_config.prev_coffset;\n\t\t\t\t\t\t\tenv->offset = global_config.prev_offset;\n\t\t\t\t\t\t\tenv->col_no = global_config.prev_col;\n\t\t\t\t\t\t\tset_preferred_column();\n\t\t\t\t\t\t\tenv->line_no = global_config.prev_line;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdraw_search_match(buffer, 0);\n\n\t\t\t\t\t\tfree(buffer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (env->mode != last_mode) {\n\t\t\tredraw_statusbar();\n\t\t\tredraw_commandline();\n\t\t\tlast_mode = env->mode;\n\t\t}\n\n\t\tif (env->mode == MODE_NORMAL) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = 0;\n\t\t\tdo {\n\t\t\t\tkey = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\t} while (key == KEY_TIMEOUT);\n\t\t\tif (handle_nav_buffer(key)) {\n\t\t\t\tif (!handle_action(NORMAL_MAP, key))\n\t\t\t\t\tif (!handle_action(NAVIGATION_MAP, key))\n\t\t\t\t\t\thandle_action(ESCAPE_MAP, key);\n\t\t\t}\n\t\t\treset_nav_buffer(key);\n\t\t} else if (env->mode == MODE_INSERT) {\n\t\t\tif (!refresh) place_cursor_actual();\n\t\t\tint key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT);\n\t\t\tif (key == KEY_TIMEOUT) {\n\t\t\t\tplace_cursor_actual();\n\t\t\t\tif (refresh > 1) {\n\t\t\t\t\tredraw_text();\n\t\t\t\t} else if (refresh) {\n\t\t\t\t\tredraw_line(env->line_no-1);\n\t\t\t\t}\n\t\t\t\trefresh = 0;\n\t\t\t} else if (handle_action(INSERT_MAP, key)) {\n\t\t\t\trefresh = 2;\n\t\t\t} else if (handle_action(ESCAPE_MAP, key)) {\n\t\t\t\t/* Do nothing */\n\t\t\t} else if (key < KEY_ESCAPE) {\n\t\t\t\tinsert_char(key);\n\t\t\t\tif (global_config.smart_complete) {\n\t\t\t\t\tredraw_line(env->line_no-1);\n\t\t\t\t\twhile (omni_complete(1) == 1);\n\t\t\t\t}\n\t\t\t\trefresh |= 1;\n\t\t\t}\n\t\t} else if (env->mode == MODE_REPLACE) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\tif (key != KEY_TIMEOUT) {\n\t\t\t\tif (handle_action(REPLACE_MAP, key)) {\n\t\t\t\t\tredraw_text();\n\t\t\t\t} else if (!handle_action(ESCAPE_MAP, key)) {\n\t\t\t\t\t/* Perform replacement */\n\t\t\t\t\tif (key < KEY_ESCAPE) {\n\t\t\t\t\t\tif (env->col_no <= env->lines[env->line_no - 1]->actual) {\n\t\t\t\t\t\t\treplace_char(key);\n\t\t\t\t\t\t\tenv->col_no += 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tinsert_char(key);\n\t\t\t\t\t\t\tredraw_line(env->line_no-1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tset_preferred_column();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (env->mode == MODE_LINE_SELECTION) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\tif (key == KEY_TIMEOUT) continue;\n\n\t\t\tif (handle_nav_buffer(key)) {\n\t\t\t\tif (!handle_action(LINE_SELECTION_MAP, key))\n\t\t\t\t\tif (!handle_action(NAVIGATION_MAP, key))\n\t\t\t\t\t\thandle_action(ESCAPE_MAP, key);\n\t\t\t}\n\n\t\t\treset_nav_buffer(key);\n\n\t\t\tif (env->mode == MODE_LINE_SELECTION) {\n\t\t\t\t/* Mark current line */\n\t\t\t\t_redraw_line(env->line_no,0);\n\t\t\t\t_redraw_line(env->start_line,1);\n\n\t\t\t\t/* Properly mark everything in the span we just moved through */\n\t\t\t\tif (env->prev_line < env->line_no) {\n\t\t\t\t\tfor (int i = env->prev_line; i < env->line_no; ++i) {\n\t\t\t\t\t\t_redraw_line(i,0);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t} else if (env->prev_line > env->line_no) {\n\t\t\t\t\tfor (int i = env->line_no + 1; i <= env->prev_line; ++i) {\n\t\t\t\t\t\t_redraw_line(i,0);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t}\n\t\t\t\tredraw_commandline();\n\t\t\t}\n\t\t} else if (env->mode == MODE_CHAR_SELECTION) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\tif (key == KEY_TIMEOUT) continue;\n\n\t\t\tif (handle_nav_buffer(key)) {\n\t\t\t\tif (!handle_action(CHAR_SELECTION_MAP, key))\n\t\t\t\t\tif (!handle_action(NAVIGATION_MAP, key))\n\t\t\t\t\t\thandle_action(ESCAPE_MAP, key);\n\t\t\t}\n\n\t\t\treset_nav_buffer(key);\n\n\t\t\tif (env->mode == MODE_CHAR_SELECTION) {\n\t\t\t\t/* Mark current line */\n\t\t\t\t_redraw_line_char(env->line_no,1);\n\n\t\t\t\t/* Properly mark everything in the span we just moved through */\n\t\t\t\tif (env->prev_line < env->line_no) {\n\t\t\t\t\tfor (int i = env->prev_line; i < env->line_no; ++i) {\n\t\t\t\t\t\t_redraw_line_char(i,1);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t} else if (env->prev_line > env->line_no) {\n\t\t\t\t\tfor (int i = env->line_no + 1; i <= env->prev_line; ++i) {\n\t\t\t\t\t\t_redraw_line_char(i,1);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (env->mode == MODE_COL_SELECTION) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\tif (key == KEY_TIMEOUT) continue;\n\n\t\t\tif (handle_nav_buffer(key)) {\n\t\t\t\tif (!handle_action(COL_SELECTION_MAP, key))\n\t\t\t\t\tif (!handle_action(NAVIGATION_MAP, key))\n\t\t\t\t\t\thandle_action(ESCAPE_MAP, key);\n\t\t\t}\n\n\t\t\treset_nav_buffer(key);\n\n\t\t\tif (env->mode == MODE_COL_SELECTION) {\n\t\t\t\t_redraw_line_col(env->line_no, 0);\n\t\t\t\t/* Properly mark everything in the span we just moved through */\n\t\t\t\tif (env->prev_line < env->line_no) {\n\t\t\t\t\tfor (int i = env->prev_line; i < env->line_no; ++i) {\n\t\t\t\t\t\t_redraw_line_col(i,0);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t} else if (env->prev_line > env->line_no) {\n\t\t\t\t\tfor (int i = env->line_no + 1; i <= env->prev_line; ++i) {\n\t\t\t\t\t\t_redraw_line_col(i,0);\n\t\t\t\t\t}\n\t\t\t\t\tenv->prev_line = env->line_no;\n\t\t\t\t}\n\n\t\t\t\tredraw_commandline();\n\t\t\t}\n\t\t} else if (env->mode == MODE_COL_INSERT) {\n\t\t\tint key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT);\n\t\t\tif (key == KEY_TIMEOUT) {\n\t\t\t\tif (refresh) {\n\t\t\t\t\tredraw_commandline();\n\t\t\t\t\tredraw_text();\n\t\t\t\t}\n\t\t\t\trefresh = 0;\n\t\t\t} else if (handle_action(COL_INSERT_MAP, key)) {\n\t\t\t\trefresh = 2;\n\t\t\t} else if (key < KEY_ESCAPE) {\n\t\t\t\tinsert_char_at_column(key);\n\t\t\t\trefresh = 1;\n\t\t\t}\n\t\t} if (env->mode == MODE_DIRECTORY_BROWSE) {\n\t\t\tplace_cursor_actual();\n\t\t\tint key = bim_getkey(DEFAULT_KEY_WAIT);\n\t\t\tif (handle_nav_buffer(key)) {\n\t\t\t\tif (!handle_action(DIRECTORY_BROWSE_MAP, key))\n\t\t\t\t\tif (!handle_action(NAVIGATION_MAP, key))\n\t\t\t\t\t\thandle_action(ESCAPE_MAP, key);\n\t\t\t}\n\t\t\treset_nav_buffer(key);\n\t\t}\n\t}\n\n}\n\nKrkClass * CommandDef;\nstruct CommandDef {\n\tKrkInstance inst;\n\tstruct command_def * command;\n};\n\nint process_krk_command(const char * cmd, KrkValue * outVal) {\n\tplace_cursor(global_config.term_width, global_config.term_height);\n\tfprintf(stdout, \"\\n\");\n\t/* By resetting, we're at 0 frames. */\n\tkrk_resetStack();\n\t/* Push something so we're not at the bottom of the stack when an\n\t * exception happens, or we'll get the normal interpreter behavior\n\t * and won't be able to examine the exception ourselves. */\n\tkrk_push(NONE_VAL());\n\t/* If we don't set outSlots for the top frame a syntax error will\n\t * get printed by the interpreter and we can't catch it here. */\n\tkrk_currentThread.frames[0].outSlots = 1;\n\t/* Call the interpreter */\n\tKrkValue out = krk_interpret(cmd,\"<bim>\");\n\t/* If the user typed just a command name, try to execute it. */\n\tif (krk_isInstanceOf(out,CommandDef)) {\n\t\tkrk_push(out);\n\t\tout = krk_callStack(0);\n\t}\n\tif (outVal) *outVal = out;\n\tint retval = (IS_INTEGER(out)) ? AS_INTEGER(out) : 0;\n\tint hadOutput = 0;\n\t/* If we got an exception during execution, print it now */\n\tif (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) {\n\t\tif (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) {\n\t\t}\n\t\tset_fg_color(COLOR_RED);\n\t\tfflush(stdout);\n\t\tkrk_dumpTraceback();\n\t\tset_fg_color(COLOR_FG);\n\t\tfflush(stdout);\n\t\thadOutput = 1;\n\t\tkrk_resetStack();\n\t}\n\t/* Otherwise, we can look at the result here. */\n\tif (!IS_NONE(out) && !(IS_INTEGER(out) && AS_INTEGER(out) == 0)) {\n\t\tkrk_attachNamedValue(&vm.builtins->fields, \"_\", out);\n\t\tkrk_push(out);\n\t\tKrkValue repr = krk_callDirect(krk_getType(out)->_reprer, 1);\n\t\tif (IS_STRING(repr)) {\n\t\t\tfprintf(stdout, \" => %s\\n\", AS_CSTRING(repr));\n\t\t\tclear_to_end();\n\t\t}\n\t\tkrk_resetStack();\n\t\thadOutput = 1;\n\t}\n\t/* If we had either an exception or a non-zero, non-None result,\n\t * we want to wait for a key press before continuing, and avoid\n\t * clearing the screen if the user is going to enter another command. */\n\tif (hadOutput) {\n\t\tint c;\n\t\twhile ((c = bim_getch())== -1);\n\t\tif (c != ':') {\n\t\t\tbim_unget(c);\n\t\t} else {\n\t\t\tenter_command();\n\t\t\tglobal_config.command_offset = 0;\n\t\t\tglobal_config.command_col_no = 1;\n\t\t\trender_command_input_buffer();\n\t\t\treturn retval;\n\t\t}\n\t}\n\tglobal_config.break_from_selection = 1;\n\tif (!global_config.had_error) redraw_all();\n\tglobal_config.had_error = 0;\n\treturn retval;\n}\n\n/**\n * Show help text for -?\n */\nstatic void show_usage(char * argv[]) {\n#define _s \"\\033[3m\"\n#define _e \"\\033[0m\\n\"\n\tprintf(\n\t\t\t\"bim - Text editor\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [options] [file]\\n\"\n\t\t\t\"       %s [options] -- -\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -R     \" _s \"open initial buffer read-only\" _e\n\t\t\t\" -O     \" _s \"set various options; examples:\" _e\n\t\t\t\"        noaltscreen \" _s \"disable alternate screen buffer\" _e\n\t\t\t\"        nounicode   \" _s \"disable unicode display\" _e\n\t\t\t\"        nosyntax    \" _s \"disable syntax highlighting on load\" _e\n\t\t\t\"        nohistory   \" _s \"disable undo/redo\" _e\n\t\t\t\"        nomouse     \" _s \"disable mouse support\" _e\n\t\t\t\"        cansgrmouse \" _s \"enable SGR mouse escape sequences\" _e\n\t\t\t\" -c,-C  \" _s \"print file to stdout with syntax highlighting\" _e\n\t\t\t\"        \" _s \"-C includes line numbers, -c does not\" _e\n\t\t\t\" -u     \" _s \"override bimrc file\" _e\n\t\t\t\" -?     \" _s \"show this help text\" _e\n\t\t\t\"\\n\"\n\t\t\t\"Long options:\\n\"\n\t\t\t\" --help          \" _s \"show this help text\" _e\n\t\t\t\" --version       \" _s \"show version information and available plugins\" _e\n\t\t\t\" --dump-mappings \" _s \"dump markdown description of key mappings\" _e\n\t\t\t\" --dump-commands \" _s \"dump markdown description of all commands\" _e\n\t\t\t\" --dump-config   \" _s \"dump key mappings as a bimscript\" _e\n\t\t\t\" --html FILE     \" _s \"convert FILE to syntax-highlighted HTML\" _e\n\t\t\t\"\\n\", argv[0], argv[0]);\n#undef _e\n#undef _s\n}\n\nBIM_COMMAND(runkrk,\"runkrk\", \"Run a kuroko script\") {\n\tif (argc < 2) return 1;\n\tkrk_runfile(argv[1],argv[1]);\n\tredraw_all();\n\treturn 0;\n}\n\n/**\n * Enable or disable terminal features/quirks and other options.\n * Used by -O and by the `quirks` bimrc command.\n */\nint set_capability(char * arg) {\n\tchar * argname;\n\tint value;\n\tif (strstr(arg,\"no\") == arg) {\n\t\targname = &arg[2];\n\t\tvalue = 0;\n\t} else if (strstr(arg,\"can\") == arg) {\n\t\targname = &arg[3];\n\t\tvalue = 1;\n\t} else {\n\t\trender_error(\"Capabilities must by 'no{CAP}' or 'can{CAP}': %s\", arg);\n\t\treturn 2;\n\t}\n\n\t/* Terminal features / quirks */\n\tif (!strcmp(argname, \"24bit\")) global_config.can_24bit = value;\n\telse if (!strcmp(argname, \"256color\")) global_config.can_256color = value;\n\telse if (!strcmp(argname, \"altscreen\")) global_config.can_altscreen = value;\n\telse if (!strcmp(argname, \"bce\")) global_config.can_bce = value;\n\telse if (!strcmp(argname, \"bright\")) global_config.can_bright = value;\n\telse if (!strcmp(argname, \"hideshow\")) global_config.can_hideshow = value;\n\telse if (!strcmp(argname, \"italic\")) global_config.can_italic = value;\n\telse if (!strcmp(argname, \"mouse\")) global_config.can_mouse = value;\n\telse if (!strcmp(argname, \"scroll\")) global_config.can_scroll = value;\n\telse if (!strcmp(argname, \"title\")) global_config.can_title = value;\n\telse if (!strcmp(argname, \"unicode\")) global_config.can_unicode = value;\n\telse if (!strcmp(argname, \"insert\")) global_config.can_insert = value;\n\telse if (!strcmp(argname, \"paste\")) global_config.can_bracketedpaste = value;\n\telse if (!strcmp(argname, \"sgrmouse\")) global_config.can_sgrmouse = value;\n\t/* Startup options */\n\telse if (!strcmp(argname, \"syntax\")) global_config.highlight_on_open = value;\n\telse if (!strcmp(argname, \"history\")) global_config.history_enabled = value;\n\telse {\n\t\trender_error(\"Unknown capability: %s\", argname);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(setcap, \"setcap\", \"Enable or disable quirks/features.\") {\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (set_capability(argv[i])) return 1;\n\t}\n\treturn 0;\n}\n\nBIM_COMMAND(quirk,\"quirk\",\"Handle quirks based on environment variables\") {\n\tif (argc < 3) goto _quirk_arg_error;\n\tchar * varname = argv[1];\n\tchar * teststr = argv[2];\n\tchar * value = getenv(varname);\n\tif (!value) return 0;\n\n\tif (strstr(value, teststr) == value) {\n\t\t/* Process quirk strings */\n\t\tfor (int i = 3; i < argc; ++i) {\n\t\t\tset_capability(argv[i]);\n\t\t}\n\t}\n\n\treturn 0;\n_quirk_arg_error:\n\trender_error(\"Usage: quirk ENVVAR value no... can...\");\n\treturn 1;\n}\n\n/**\n * Load bimrc configuration file.\n *\n * At the moment, this a simple key=value list.\n */\nvoid load_bimrc(void) {\n\tif (!global_config.bimrc_path) return;\n\n\t/* Default is ~/.bimrc */\n\tchar * tmp = strdup(global_config.bimrc_path);\n\n\tif (!*tmp) {\n\t\tfree(tmp);\n\t\treturn;\n\t}\n\n\t/* Parse ~ at the front of the path. */\n\tif (*tmp == '~') {\n\t\tchar path[1024] = {0};\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (!home) {\n\t\t\t/* $HOME is unset? */\n\t\t\tfree(tmp);\n\t\t\treturn;\n\t\t}\n\n\t\t/* New path is $HOME/.bimrc */\n\t\tsnprintf(path, 1024, \"%s%s\", home, tmp+1);\n\t\tfree(tmp);\n\t\ttmp = strdup(path);\n\t}\n\n\tstruct stat statbuf;\n\tif (stat(tmp, &statbuf)) {\n\t\tfree(tmp);\n\t\treturn;\n\t}\n\n\tkrk_runfile(tmp,tmp);\n\tfree(tmp);\n}\n\nstatic KrkValue krk_bim_syntax_dict;\nKRK_Function(bindHighlighter) {\n\tKrkValue cls;\n\tif (!krk_parseArgs(\"V!\", (const char*[]){\"cls\"}, KRK_BASE_CLASS(type), &cls)) return NONE_VAL();\n\tif (!krk_isSubClass(AS_CLASS(cls), syntaxStateClass))\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"Can not register '%s' as a syntax highlighter, expected subclass of SyntaxState.\", krk_typeName(cls));\n\n\tKrkValue name = krk_valueGetAttribute_default(cls, \"name\", NONE_VAL());\n\tKrkValue extensions = krk_valueGetAttribute_default(cls, \"extensions\", NONE_VAL());\n\tKrkValue spaces = krk_valueGetAttribute_default(cls, \"spaces\", BOOLEAN_VAL(0));\n\tKrkValue calculate = krk_valueGetAttribute_default(cls, \"calculate\", NONE_VAL());\n\n\tif (!IS_STRING(name))\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%s.name must be str\", AS_CLASS(cls)->name->chars);\n\tif (!IS_TUPLE(extensions))\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%s.extensions must be tuple<str>\", AS_CLASS(cls)->name->chars);\n\tif (!IS_BOOLEAN(spaces))\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%s.spaces must be bool\", AS_CLASS(cls)->name->chars);\n\tif (!IS_CLOSURE(calculate))\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%s.calculate must be method, not '%s'\", AS_CLASS(cls)->name->chars, krk_typeName(calculate));\n\n\t/* Convert tuple of strings */\n\tchar ** ext = malloc(sizeof(char *) * (AS_TUPLE(extensions)->values.count + 1)); /* +1 for NULL */\n\text[AS_TUPLE(extensions)->values.count] = NULL;\n\tfor (size_t i = 0; i < AS_TUPLE(extensions)->values.count; ++i) {\n\t\tif (!IS_STRING(AS_TUPLE(extensions)->values.values[i])) {\n\t\t\tfree(ext);\n\t\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%s.extensions must by tuple<str>\", AS_CLASS(cls)->name->chars);\n\t\t}\n\t\text[i] = AS_CSTRING(AS_TUPLE(extensions)->values.values[i]);\n\t}\n\n\tadd_syntax((struct syntax_definition) {\n\t\tAS_CSTRING(name), /* name */\n\t\text, /* NULL-terminated array of extensions */\n\t\tNULL, /* calculate function */\n\t\tAS_BOOLEAN(spaces), /* spaces */\n\t\tNULL, /* qualifier */\n\t\tNULL, /* matcher */\n\t\tAS_OBJECT(calculate), /* krkFunc */\n\t\tAS_OBJECT(cls),\n\t});\n\n\t/* And save it in the module stuff. */\n\tkrk_tableSet(AS_DICT(krk_bim_syntax_dict), name, cls);\n\n\treturn NONE_VAL();\n}\n\nstatic KrkValue krk_bim_theme_dict;\nKRK_Function(defineTheme) {\n\tKrkClosure * theme;\n\tif (!krk_parseArgs(\"O!\", (const char*[]){\"theme\"}, KRK_BASE_CLASS(function), &theme)) return NONE_VAL();\n\n\tKrkValue name = OBJECT_VAL(theme->function->name);\n\tadd_colorscheme((struct theme_def) {\n\t\tAS_CSTRING(name),\n\t\ttheme\n\t});\n\n\tkrk_tableSet(AS_DICT(krk_bim_theme_dict), name, OBJECT_VAL(theme));\n\treturn OBJECT_VAL(theme);\n}\n\nstatic int c_keyword_qualifier(int c) {\n\treturn isalnum(c) || (c == '_');\n}\n\n#define KRK_BIM_STATE() struct syntax_state * state = &self->state;\n\nstatic KrkTuple * _bim_state_chars = NULL;\n\n#define IS_SyntaxState(o) (krk_isInstanceOf(o,syntaxStateClass))\n#define AS_SyntaxState(o) ((struct SyntaxState*)AS_OBJECT(o))\n\n#define CURRENT_CTYPE struct SyntaxState*\n\nKRK_Method(SyntaxState,state) {\n\tKRK_BIM_STATE();\n\treturn INTEGER_VAL(state->state);\n}\n\nKRK_Method(SyntaxState,__mod__) {\n\tKRK_BIM_STATE();\n\treturn INTEGER_VAL(state->state);\n}\n\nKRK_Method(SyntaxState,__lshift__) {\n\tKRK_BIM_STATE();\n\tMETHOD_TAKES_EXACTLY(1);\n\tstate->state = AS_INTEGER(argv[1]); /* Be fast about it */\n\treturn INTEGER_VAL(state->state);\n}\n\nKRK_Method(SyntaxState,i) {\n\tKRK_BIM_STATE();\n\treturn INTEGER_VAL(state->i);\n}\n\nKRK_Method(SyntaxState,lineno) {\n\tKRK_BIM_STATE();\n\treturn INTEGER_VAL(state->line_no);\n}\n\nKRK_Method(SyntaxState,__getitem__) {\n\tKRK_BIM_STATE();\n\tMETHOD_TAKES_EXACTLY(1);\n\n\t/* non-slice item */\n\tif (IS_INTEGER(argv[1])) {\n\t\tlong arg = AS_INTEGER(argv[1]);\n\t\tint charRel = charrel(arg);\n\t\tif (charRel == -1) return NONE_VAL();\n\t\tif (charRel >= 32 && charRel <= 126) return _bim_state_chars->values.values[charRel - 32];\n\t\tchar tmp[8] = {0};\n\t\tsize_t len = to_eight(charRel, tmp);\n\t\treturn OBJECT_VAL(krk_copyString(tmp,len));\n\t} else if (IS_slice(argv[1])) {\n\t\tstruct StringBuilder sb = {0};\n\n\t\textern int krk_extractSlicer(const char * _method_name, KrkValue slicerVal, krk_integer_type count, krk_integer_type *start, krk_integer_type *end, krk_integer_type *step);\n\t\tkrk_integer_type start, end, step;\n\t\tif (krk_extractSlicer(\"__getitem__\", argv[1], state->line->actual - state->i, &start, &end, &step)) {\n\t\t\treturn NONE_VAL();\n\t\t}\n\n\t\tkrk_integer_type i = start;\n\n\t\twhile ((step < 0) ? (i > end) : (i < end)) {\n\t\t\tint charRel = charrel(i);\n\t\t\tif (charRel == -1) break;\n\t\t\tchar tmp[8] = {0};\n\t\t\tsize_t len = to_eight(charRel, tmp);\n\t\t\tpushStringBuilderStr(&sb, tmp, len);\n\t\t\ti += step;\n\t\t}\n\n\t\treturn finishStringBuilder(&sb);\n\t} else {\n\t\treturn TYPE_ERROR(int or slice,argv[1]);\n\t}\n}\n\nKRK_StaticMethod(SyntaxState,isdigit) {\n\tFUNCTION_TAKES_EXACTLY(1);\n\tif (IS_NONE(argv[0])) return BOOLEAN_VAL(0);\n\tif (!IS_STRING(argv[0]) || AS_STRING(argv[0])->codesLength != 1) return TYPE_ERROR(str of length 1,argv[0]);\n\tunsigned int c = krk_unicodeCodepoint(AS_STRING(argv[0]), 0);\n\treturn BOOLEAN_VAL(!!isdigit(c));\n}\n\nKRK_StaticMethod(SyntaxState,isxdigit) {\n\tFUNCTION_TAKES_EXACTLY(1);\n\tif (IS_NONE(argv[0])) return BOOLEAN_VAL(0);\n\tif (!IS_STRING(argv[0]) || AS_STRING(argv[0])->codesLength != 1) return TYPE_ERROR(str of length 1,argv[0]);\n\tunsigned int c = krk_unicodeCodepoint(AS_STRING(argv[0]), 0);\n\treturn BOOLEAN_VAL(!!isxdigit(c));\n}\n\nKRK_Method(SyntaxState,paint) {\n\tKRK_BIM_STATE();\n\tMETHOD_TAKES_EXACTLY(2);\n\tlong howMuch = AS_INTEGER(argv[1]);\n\tlong whatFlag = AS_INTEGER(argv[2]);\n\tif (howMuch == -1) howMuch = state->line->actual;\n\tpaint(howMuch, whatFlag);\n\treturn NONE_VAL();\n}\n\nKRK_Method(SyntaxState,paintComment) {\n\tKRK_BIM_STATE();\n\tpaint_comment(state);\n\treturn NONE_VAL();\n}\n\nKRK_Method(SyntaxState,skip) {\n\tKRK_BIM_STATE();\n\tskip();\n\treturn NONE_VAL();\n}\n\n/* Identical to the above 'paint' */\nKRK_Method(SyntaxState,__setitem__) {\n\tKRK_BIM_STATE();\n\tMETHOD_TAKES_EXACTLY(2);\n\tlong howMuch = AS_INTEGER(argv[1]);\n\tlong whatFlag = AS_INTEGER(argv[2]);\n\tif (howMuch == -1) howMuch = state->line->actual;\n\tpaint(howMuch, whatFlag);\n\treturn NONE_VAL();\n}\n\n#define KRK_STRING_FAST(string,offset)  (uint32_t)\\\n\t((string->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) <= (KRK_OBJ_FLAGS_STRING_UCS1) ? ((uint8_t*)string->codes)[offset] : \\\n\t((string->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == (KRK_OBJ_FLAGS_STRING_UCS2) ? ((uint16_t*)string->codes)[offset] : \\\n\t((uint32_t*)string->codes)[offset]))\n\nKRK_Method(SyntaxState,__contains__) {\n\tKRK_BIM_STATE();\n\tMETHOD_TAKES_EXACTLY(1);\n\tint c = charrel(0);\n\tKrkValue arg = argv[1];\n\n\tif (IS_NONE(arg)) return BOOLEAN_VAL((c == -1));\n\tif (!IS_STRING(arg)) return TYPE_ERROR(str,arg);\n\n\tKrkString * s = AS_STRING(arg);\n\tkrk_unicodeString(s);\n\n\tfor (size_t i = 0; i < s->codesLength; ++i) {\n\t\tint cp = (int)KRK_STRING_FAST(s,i);\n\t\tif (c == cp) return BOOLEAN_VAL(1);\n\t}\n\treturn BOOLEAN_VAL(0);\n}\n\nKRK_StaticMethod(SyntaxState,cKeywordQualifier) {\n\tFUNCTION_TAKES_EXACTLY(1);\n\tif (IS_INTEGER(argv[0])) return BOOLEAN_VAL(!!c_keyword_qualifier(AS_INTEGER(argv[0])));\n\tif (!IS_STRING(argv[0])) return BOOLEAN_VAL(0);\n\tif (AS_STRING(argv[0])->length > 1) return BOOLEAN_VAL(0);\n\treturn BOOLEAN_VAL(!!c_keyword_qualifier(AS_CSTRING(argv[0])[0]));\n}\n\nstatic int callQualifier(KrkValue qualifier, int codepoint) {\n\tif (IS_NATIVE(qualifier) && AS_NATIVE(qualifier)->function == FUNC_NAME(SyntaxState,cKeywordQualifier)) return AS_BOOLEAN(!!c_keyword_qualifier(codepoint));\n\tkrk_push(qualifier);\n\tkrk_push(INTEGER_VAL(codepoint));\n\tKrkValue result = krk_callStack(1);\n\tif (IS_BOOLEAN(result)) return AS_BOOLEAN(result);\n\treturn 0;\n}\n\nKRK_Method(SyntaxState,findKeywords) {\n\tKRK_BIM_STATE();\n\tKrkValue kwList;\n\tint flag;\n\tKrkValue qualifier;\n\n\tif (!krk_parseArgs(\".V!iV\",(const char*[]){\"keywords\",\"flag\",\"qualifier\"},\n\t\tKRK_BASE_CLASS(list), &kwList,\n\t\t&flag,\n\t\t&qualifier)) return NONE_VAL();\n\n\tif (callQualifier(qualifier, lastchar())) return BOOLEAN_VAL(0);\n\tif (!callQualifier(qualifier, charat()))  return BOOLEAN_VAL(0);\n\n\tfor (size_t keyword = 0; keyword < AS_LIST(kwList)->count; ++keyword) {\n\t\tif (!IS_STRING(AS_LIST(kwList)->values[keyword])) {\n\t\t\treturn TYPE_ERROR(list of str,AS_LIST(kwList)->values[keyword]);\n\t\t}\n\n\t\tKrkString * me = AS_STRING(AS_LIST(kwList)->values[keyword]);\n\t\tsize_t d = 0;\n\t\tif ((me->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == KRK_OBJ_FLAGS_STRING_ASCII) {\n\t\t\twhile (state->i + (int)d < state->line->actual &&\n\t\t\t       d < me->codesLength &&\n\t\t\t       state->line->text[state->i+d].codepoint == me->chars[d]) d++;\n\t\t} else {\n\t\t\tkrk_unicodeString(me);\n\t\t\twhile (state->i + (int)d < state->line->actual &&\n\t\t\t       d < me->codesLength &&\n\t\t\t       state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++;\n\t\t}\n\t\tif (d == me->codesLength && (state->i + (int)d >= state->line->actual ||\n\t\t\t!callQualifier(qualifier,state->line->text[state->i+d].codepoint))) {\n\t\t\tpaint((int)me->codesLength, flag);\n\t\t\treturn BOOLEAN_VAL(1);\n\t\t}\n\t}\n\treturn BOOLEAN_VAL(0);\n}\n\nKRK_Method(SyntaxState,matchAndPaint) {\n\tKRK_BIM_STATE();\n\n\tKrkString * me;\n\tint flag;\n\tKrkValue qualifier;\n\n\tif (!krk_parseArgs(\".O!iV\",(const char*[]){\"match\",\"flag\",\"qualifier\"},\n\t\tKRK_BASE_CLASS(str), &me,\n\t\t&flag,\n\t\t&qualifier)) return NONE_VAL();\n\n\tsize_t d = 0;\n\tif ((me->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == KRK_OBJ_FLAGS_STRING_ASCII) {\n\t\twhile (state->i + (int)d < state->line->actual &&\n\t\t       d < me->codesLength &&\n\t\t       state->line->text[state->i+d].codepoint == me->chars[d]) d++;\n\t} else {\n\t\tkrk_unicodeString(me);\n\t\twhile (state->i + (int)d < state->line->actual &&\n\t\t       d < me->codesLength &&\n\t\t       state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++;\n\t}\n\tif (d == me->codesLength && (state->i + (int)d >= state->line->actual ||\n\t\t!callQualifier(qualifier,state->line->text[state->i+d].codepoint))) {\n\t\tpaint((int)me->codesLength, flag);\n\t\treturn BOOLEAN_VAL(1);\n\t}\n\treturn BOOLEAN_VAL(0);\n}\n\nKRK_Method(SyntaxState,rewind) {\n\tKRK_BIM_STATE();\n\tint offset;\n\tif (!krk_parseArgs(\".i\",(const char*[]){\"offset\"}, &offset)) return NONE_VAL();\n\tstate->i -= offset;\n\treturn NONE_VAL();\n}\n\nKRK_Method(SyntaxState,commentBuzzwords) {\n\tKRK_BIM_STATE();\n\treturn BOOLEAN_VAL(common_comment_buzzwords(state));\n}\n\nKRK_Method(SyntaxState,__init__) {\n\tKRK_BIM_STATE();\n\tstruct SyntaxState * other;\n\tif (!krk_parseArgs(\".O!\",(const char*[]){\"other\"},syntaxStateClass,&other)) return NONE_VAL();\n\t*state = other->state;\n\treturn NONE_VAL();\n}\n\nKRK_Function(getCommands) {\n\tKrkValue myList = krk_list_of(0, NULL,0);\n\tkrk_push(myList);\n\tfor (struct command_def * c = regular_commands; regular_commands && c->name; ++c) {\n\t\tkrk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name))));\n\t}\n\tfor (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) {\n\t\tkrk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name))));\n\t}\n\treturn krk_pop();\n}\n\nKrkClass * ActionDef;\nstruct ActionDef {\n\tKrkInstance inst;\n\tstruct action_def * action;\n};\n\n#define IS_Action(o) (krk_isInstanceOf(o,ActionDef))\n#define AS_Action(o) ((struct ActionDef*)AS_OBJECT(o))\n\n#undef CURRENT_CTYPE\n#define CURRENT_CTYPE struct ActionDef*\n\nKRK_Method(Action,__call__) {\n\t/* Figure out arguments */\n\tint args = 0;\n\tif (self->action->options & ARG_IS_CUSTOM) args++;\n\tif (self->action->options & ARG_IS_INPUT) args++;\n\tif (self->action->options & ARG_IS_PROMPT) args++;\n\n\tint argsAsInts[3] = { 0, 0, 0 };\n\tfor (int i = 0; i < args; i++) {\n\t\tif (argc < i + 2)\n\t\t\treturn krk_runtimeError(vm.exceptions->argumentError, \"%s() takes %d argument%s\",\n\t\t\t\tself->action->name, args, args == 1 ? \"\" : \"s\");\n\t\tif (IS_INTEGER(argv[i+1])) {\n\t\t\targsAsInts[i] = AS_INTEGER(argv[i+1]);\n\t\t} else if (IS_STRING(argv[i+1]) && AS_STRING(argv[i+1])->codesLength == 1) {\n\t\t\targsAsInts[i] = krk_unicodeCodepoint(AS_STRING(argv[i+1]), 0);\n\t\t} else if (IS_BOOLEAN(argv[i+1])) {\n\t\t\targsAsInts[i] = AS_BOOLEAN(argv[i+1]);\n\t\t} else {\n\t\t\treturn krk_runtimeError(vm.exceptions->typeError,\n\t\t\t\t\"argument to %s() must be int, bool, or str of len 1\",\n\t\t\t\tself->action->name);\n\t\t}\n\t}\n\n\t((action_three_arg)self->action->action)(argsAsInts[0], argsAsInts[1], argsAsInts[2]);\n\n\treturn NONE_VAL();\n}\n\n#define IS_Command(o) (krk_isInstanceOf(o,CommandDef))\n#define AS_Command(o) ((struct CommandDef*)AS_OBJECT(o))\n\n#undef CURRENT_CTYPE\n#define CURRENT_CTYPE struct CommandDef*\n\nKRK_Method(Command,__call__) {\n\tchar ** args = malloc(sizeof(char*)*argc);\n\targs[0] = strdup(self->command->name);\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (IS_STRING(argv[i])) {\n\t\t\targs[i] = strdup(AS_CSTRING(argv[i]));\n\t\t} else {\n\t\t\tkrk_push(argv[i]);\n\t\t\tKrkValue asString = krk_callDirect(krk_getType(argv[i])->_tostr, 1);\n\t\t\targs[i] = strdup(AS_CSTRING(asString));\n\t\t}\n\t}\n\n\tint result = self->command->command(args[0], argc, args);\n\n\tfor (int i = 0; i < argc; ++i) {\n\t\tfree(args[i]);\n\t}\n\tfree(args);\n\n\treturn INTEGER_VAL(result);\n}\n\nvoid import_directory(char * dirName) {\n\tconst char * extra = \"\";\n\tchar * dirpath = NULL;\n\tchar file[4096];\n\tif (vm.binpath) {\n\t\tchar * tmp = strdup(vm.binpath);\n\t\tdirpath = strdup(dirname(tmp));\n\t\textra = \"/\";\n\t\tfree(tmp);\n\t\tsprintf(file, \"%s/%s\", dirpath, dirName);\n\t} else {\n\t\tsprintf(file, \"%s\", dirName);\n\t}\n\n\tDIR * dirp = opendir(file);\n\tif (!dirp && dirpath) {\n\t\t/* Try ../share/bim/dirName */\n\t\tsprintf(file, \"%s/../share/bim/%s\", dirpath, dirName);\n\t\textra = \"/../share/bim/\";\n\t\tdirp = opendir(file);\n\t}\n\tif (!dirp) {\n\t\t/* Try /usr/share/bim */\n\t\tif (dirpath) free(dirpath);\n\t\tdirpath = strdup(\"/usr/share/bim\");\n\t\tsprintf(file, \"%s/%s\", dirpath, dirName);\n\t\textra = \"/\";\n\t\tdirp = opendir(file);\n\t}\n\tif (!dirp) {\n\t\t/* Try one last fallback */\n\t\tif (dirpath) free(dirpath);\n\t\tdirpath = strdup(\"/usr/local/share/bim\");\n\t\tsprintf(file, \"%s/%s\", dirpath, dirName);\n\t\textra = \"/\";\n\t\tdirp = opendir(file);\n\t}\n\tif (!dirp) {\n\t\tfprintf(stderr, \"Could not find startup files: %s\\n\", dirName);\n\t\texit(1);\n\t}\n\n\tif (dirpath) {\n\t\t/* get kuroko.module_paths */\n\t\tkrk_push(krk_valueGetAttribute(OBJECT_VAL(vm.system), \"module_paths\"));\n\t\tkrk_push(krk_valueGetAttribute(krk_peek(0), \"insert\"));\n\t\tkrk_push(INTEGER_VAL(0));\n\t\t/* calculate dirpath + extra */\n\t\tkrk_push(OBJECT_VAL(krk_copyString(dirpath,strlen(dirpath))));\n\t\tkrk_push(OBJECT_VAL(krk_copyString(extra,strlen(extra))));\n\t\tkrk_addObjects();\n\t\tkrk_callStack(2); /* result value is popped */\n\t\tkrk_pop(); /* should just be the list */\n\t}\n\n\tif (dirpath) free(dirpath);\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent) {\n\t\tif (str_ends_with(ent->d_name,\".krk\")) {\n\t\t\t/* put \"dir.file\" onto the stack */\n\t\t\tkrk_push(OBJECT_VAL(krk_copyString(dirName,strlen(dirName))));\n\t\t\tif (!str_ends_with(ent->d_name,\"__init__.krk\")) {\n\t\t\t\tkrk_push(OBJECT_VAL(S(\".\")));\n\t\t\t\tkrk_addObjects();\n\t\t\t\tkrk_push(OBJECT_VAL(krk_copyString(ent->d_name,strlen(ent->d_name)-4)));\n\t\t\t\tkrk_addObjects();\n\t\t\t}\n\n\t\t\t/* import that */\n\t\t\tkrk_doRecursiveModuleLoad(AS_STRING(krk_peek(0)));\n\n\t\t\tif (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) {\n\t\t\t\tkrk_dumpTraceback();\n\t\t\t\trender_error(\"The above exception was encountered while loading '%s/%s'.\", dirName, ent->d_name);\n\n\t\t\t\tif (global_config.has_terminal) {\n\t\t\t\t\t/* Prompt to continue */\n\t\t\t\t\trender_commandline_message(\"Continue loading modules? (y/N) \");\n\t\t\t\t\tint key;\n\t\t\t\t\twhile ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\t\t\t\t\tif (key != 'y') {\n\t\t\t\t\t\tkrk_resetStack();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\trender_error(\"Press ENTER to continue loading.\");\n\t\t\t\t\tint c;\n\t\t\t\t\twhile ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* reset the stack */\n\t\t\tkrk_resetStack();\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n}\n\nstatic void findBim(char * argv[]) {\n\t/* Try asking /proc */\n\tchar * binpath = realpath(\"/proc/self/exe\", NULL);\n\tif (!binpath || (access(binpath, X_OK) != 0)) {\n\t\tif (strchr(argv[0], '/')) {\n\t\t\tbinpath = realpath(argv[0], NULL);\n\t\t} else {\n\t\t\t/* Search PATH for argv[0] */\n\t\t\tchar * _path = strdup(getenv(\"PATH\"));\n\t\t\tchar * path = _path;\n\t\t\twhile (path) {\n\t\t\t\tchar * next = strchr(path,':');\n\t\t\t\tif (next) *next++ = '\\0';\n\n\t\t\t\tchar tmp[4096];\n\t\t\t\tsprintf(tmp, \"%s/%s\", path, argv[0]);\n\t\t\t\tif (access(tmp, X_OK)) {\n\t\t\t\t\tbinpath = strdup(tmp);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tpath = next;\n\t\t\t}\n\t\t\tfree(_path);\n\t\t}\n\t}\n\tif (binpath) {\n\t\tvm.binpath = binpath;\n\t} /* Else, give up at this point and just don't attach it at all. */\n}\n\nstatic void do_kuroko_imports(void) {\n\tkrk_resetStack();\n\tkrk_startModule(\"<bim-site>\");\n\timport_directory(\"site\");\n\tkrk_startModule(\"<bim-syntax>\");\n\timport_directory(\"syntax\");\n\tkrk_startModule(\"<bim-themes>\");\n\timport_directory(\"themes\");\n\tkrk_startModule(\"<bim-repl>\");\n\tload_bimrc();\n\tkrk_resetStack();\n}\n\nBIM_COMMAND(reload,\"reload\",\"Reloads all the Kuroko stuff.\") {\n\t/* Unload everything syntax-y */\n\tKrkValue result = krk_interpret(\n\t\t\"if True:\\n\"\n\t\t\" import kuroko\\n\"\n\t\t\" for mod in kuroko.modules():\\n\"\n\t\t\"  if mod.startswith('syntax.') or mod.startswith('themes.'):\\n\"\n\t\t\"   kuroko.unload(mod)\\n\", \"<bim>\");\n\n\tif (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) {\n\t\tkrk_dumpTraceback();\n\t\treturn 1;\n\t}\n\n\t/* Reload everything */\n\tdo_kuroko_imports();\n\treturn 0;\n}\n\nKRK_Function(getDocumentText) {\n\tstruct StringBuilder sb = {0};\n\n\tint i, j;\n\tfor (i = 0; i < env->line_count; ++i) {\n\t\tline_t * line = env->lines[i];\n\t\tfor (j = 0; j < line->actual; j++) {\n\t\t\tchar_t c = line->text[j];\n\t\t\tif (c.codepoint == 0) {\n\t\t\t\tpushStringBuilder(&sb, 0);\n\t\t\t} else {\n\t\t\t\tchar tmp[8] = {0};\n\t\t\t\tint len = to_eight(c.codepoint, tmp);\n\t\t\t\tpushStringBuilderStr(&sb, tmp, len);\n\t\t\t}\n\t\t}\n\t\tpushStringBuilder(&sb, '\\n');\n\t}\n\n\treturn finishStringBuilder(&sb);\n}\n\nKRK_Function(renderError) {\n\tchar * message = NULL;\n\tsize_t message_len = 0;\n\n\tif (!krk_parseArgs(\"|z#\",(const char*[]){\"message\"},\n\t\t&message, &message_len)) return NONE_VAL();\n\n\tif (!message || !message_len) redraw_commandline();\n\telse render_error(\"%s\", message);\n\n\treturn NONE_VAL();\n}\n\nKRK_Function(renderMessage) {\n\tchar * message = NULL;\n\tsize_t message_len = 0;\n\n\tif (!krk_parseArgs(\"|z#\",(const char*[]){\"message\"},\n\t\t&message, &message_len)) return NONE_VAL();\n\n\tif (!message || !message_len) redraw_commandline();\n\telse render_commandline_message(\"%s\", message);\n\n\treturn NONE_VAL();\n}\n\nKRK_Function(renderStatus) {\n\tchar * message = NULL;\n\tsize_t message_len = 0;\n\n\tif (!krk_parseArgs(\"|z#\",(const char*[]){\"message\"},\n\t\t&message, &message_len)) return NONE_VAL();\n\n\tif (!message || !message_len) redraw_commandline();\n\telse render_status_message(\"%s\", message);\n\n\treturn NONE_VAL();\n}\n\nKRK_Function(getDocumentFilename) {\n\tif (!env || !env->file_name) return NONE_VAL();\n\treturn OBJECT_VAL(krk_copyString(env->file_name,strlen(env->file_name)));\n}\n\nstatic KrkValue krk_bim_custom_action_dict;\nKRK_Function(bindkey) {\n\tconst char * key = NULL;\n\tconst char * mode = NULL;\n\tKrkValue callable;\n\n\tif (!krk_parseArgs(\"ssV\", (const char*[]){\"key\",\"mode\",\"callable\"},\n\t\t&key, &mode, &callable)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tstruct action_map ** mode_map = NULL;\n\tfor (struct mode_names * m = mode_names; m->name; ++m) {\n\t\tif (!strcmp(m->name, mode)) {\n\t\t\tmode_map = m->mode;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!mode_map) return krk_runtimeError(vm.exceptions->valueError, \"invalid mode: %s\", mode);\n\n\tenum Key keycode = key_from_name(key);\n\tif (keycode == -1) return krk_runtimeError(vm.exceptions->valueError, \"invalid key: %s\", key);\n\n\tstruct action_map * candidate = NULL;\n\tfor (struct action_map * m = *mode_map; m->key != -1; ++m) {\n\t\tif (m->key == keycode) {\n\t\t\tcandidate = m;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!candidate) {\n\t\t/* get size */\n\t\tint len = 0;\n\t\tfor (struct action_map * m = *mode_map; m->key != -1; m++, len++);\n\t\t*mode_map = realloc(*mode_map, sizeof(struct action_map) * (len + 2));\n\t\tcandidate = &(*mode_map)[len];\n\t\t(*mode_map)[len+1].key = -1;\n\t}\n\n\tcandidate->key = keycode;\n\tcandidate->options = opt_krk;\n\tcandidate->callable = callable;\n\n\treturn 0;\n\n\tkrk_tableSet(AS_DICT(krk_bim_custom_action_dict), callable, BOOLEAN_VAL(1));\n\treturn NONE_VAL();\n}\n\nKRK_Function(getkey) {\n\tint timeout = DEFAULT_KEY_WAIT;\n\tif (!krk_parseArgs(\"|i\",(const char*[]){\"timeout\"},&timeout)) return NONE_VAL();\n\tint key = bim_getkey(timeout);\n\treturn INTEGER_VAL(key);\n}\n\nKRK_Function(displayWidth) {\n\tchar * str;\n\tif (!krk_parseArgs(\"s\",(const char*[]){\"str\"},&str)) return NONE_VAL();\n\treturn INTEGER_VAL(display_width_of_string(str));\n}\n\nKRK_Function(pauseForKey) {\n\tpause_for_key();\n\treturn NONE_VAL();\n}\n\nKRK_Function(paren_pairs) {\n\tKrkString * pairs = NULL;\n\tif (!krk_parseArgs(\"|O!\", (const char *[]){\"pairs\"},\n\t\tKRK_BASE_CLASS(str), &pairs)) return NONE_VAL();\n\n\tif (pairs) {\n\t\tkrk_unicodeString(pairs);\n\n\t\tuint32_t * new_pairs = calloc(sizeof(uint32_t), pairs->codesLength + 1);\n\t\tfor (size_t i = 0; i < pairs->codesLength; ++i) {\n\t\t\tuint32_t cp = (uint32_t)KRK_STRING_FAST(pairs,i);\n\t\t\tnew_pairs[i] = cp;\n\t\t}\n\n\t\tif (global_config.paren_pairs) free(global_config.paren_pairs);\n\t\tglobal_config.paren_pairs = new_pairs;\n\t}\n\n\tif (!global_config.paren_pairs) return NONE_VAL();\n\n\t/* Somehow we don't have a good API for building a string from a char32 array. */\n\tstruct StringBuilder sb = {0};\n\n\tfor (uint32_t * value = global_config.paren_pairs; *value; ++value) {\n\t\tunsigned char bytes[5] = {0};\n\t\tsize_t len = krk_codepointToBytes(*value, bytes);\n\t\tkrk_pushStringBuilderStr(&sb, (char*)bytes, len);\n\t}\n\n\treturn krk_finishStringBuilder(&sb);\n}\n\n/**\n * Run global initialization tasks\n */\nvoid initialize(void) {\n\t/* Force empty locale */\n#ifdef __APPLE__\n\t/* TODO figure out a better way to do this; maybe just LC_CTYPE? */\n\tsetlocale(LC_ALL, \"en_US.UTF-8\");\n#else\n\tsetlocale(LC_ALL, \"\");\n#endif\n\n\t/* Set up default key mappings */\n#define CLONE_MAP(map) do { \\\n\tint len = 0, i = 0; \\\n\tfor (struct action_map * m = _ ## map; m->key != -1; m++, len++); \\\n\tmap = malloc(sizeof(struct action_map) * (len + 1)); \\\n\tfor (struct action_map * m = _ ## map; m->key != -1; m++, i++) { \\\n\t\tmemcpy(&map[i], m, sizeof(struct action_map)); \\\n\t} \\\n\tmap[i].key = -1; \\\n} while (0)\n\n\tCLONE_MAP(NORMAL_MAP);\n\tCLONE_MAP(INSERT_MAP);\n\tCLONE_MAP(REPLACE_MAP);\n\tCLONE_MAP(LINE_SELECTION_MAP);\n\tCLONE_MAP(CHAR_SELECTION_MAP);\n\tCLONE_MAP(COL_SELECTION_MAP);\n\tCLONE_MAP(COL_INSERT_MAP);\n\tCLONE_MAP(NAVIGATION_MAP);\n\tCLONE_MAP(ESCAPE_MAP);\n\tCLONE_MAP(COMMAND_MAP);\n\tCLONE_MAP(SEARCH_MAP);\n\tCLONE_MAP(FILESEARCH_MAP);\n\tCLONE_MAP(INPUT_BUFFER_MAP);\n\n#undef CLONE_MAP\n\n\t/* Simple ASCII defaults, but you could use \" \" as a config option */\n\tglobal_config.tab_indicator = strdup(\">\");\n\tglobal_config.space_indicator = strdup(\"-\");\n\n\tuint32_t pairs[] = U\"()[]{}<>\";\n\tglobal_config.paren_pairs = malloc(sizeof(pairs));\n\tmemcpy(global_config.paren_pairs, pairs, sizeof(pairs));\n\n\t/* Initialize Kuroko runtime context */\n\tkrk_initVM(0);\n\n\t/**\n\t * Build the 'bim' module:\n\t * @c bindHighlighter Applied to syntax highlighter classes to register them.\n\t * @c getCommands     Returns a list of bim command objects.\n\t * @c themes          dict, theme names to theme functions.\n\t * @c defineTheme     Applied to theme functions to register them.\n\t * @c highlighters    dict, syntax highlighter names to highlighter classes.\n\t * @c getDocumentText Return a string with the full contents of the current buffer.\n\t * @c renderError     Binding to render_error.\n\t */\n\tKrkInstance * bimModule = krk_newInstance(vm.baseClasses->moduleClass);\n\tkrk_attachNamedObject(&vm.modules, \"bim\", (KrkObj*)bimModule);\n\tkrk_attachNamedObject(&bimModule->fields, \"__name__\", (KrkObj*)S(\"bim\"));\n\tBIND_FUNC(bimModule, bindHighlighter);\n\tBIND_FUNC(bimModule, getCommands);\n\tBIND_FUNC(bimModule, defineTheme);\n\tBIND_FUNC(bimModule, getDocumentText);\n\tBIND_FUNC(bimModule, renderError);\n\tBIND_FUNC(bimModule, renderMessage);\n\tBIND_FUNC(bimModule, renderStatus);\n\tBIND_FUNC(bimModule, bindkey);\n\tBIND_FUNC(bimModule, getDocumentFilename);\n\tBIND_FUNC(bimModule, getkey);\n\tBIND_FUNC(bimModule, displayWidth);\n\tBIND_FUNC(bimModule, pauseForKey);\n\tBIND_FUNC(bimModule, paren_pairs);\n\n\t/* Direct access and GC references */\n\tkrk_bim_theme_dict = krk_dict_of(0,NULL,0);\n\tkrk_attachNamedValue(&bimModule->fields, \"themes\", krk_bim_theme_dict);\n\tkrk_bim_syntax_dict = krk_dict_of(0,NULL,0);\n\tkrk_attachNamedValue(&bimModule->fields, \"highlighters\", krk_bim_syntax_dict);\n\tkrk_bim_custom_action_dict = krk_dict_of(0,NULL,0);\n\tkrk_attachNamedValue(&bimModule->fields,\"customActions\", krk_bim_custom_action_dict);\n\n\t/* Helpful info */\n\tkrk_attachNamedObject(&bimModule->fields, \"version\", (KrkObj*)S(BIM_VERSION));\n\tkrk_attachNamedObject(&bimModule->fields, \"copyright\", (KrkObj*)S(BIM_COPYRIGHT));\n\tkrk_attachNamedObject(&bimModule->fields, \"builddate\", (KrkObj*)S(BIM_BUILD_DATE));\n\n\t/**\n\t * Class representing a BIM_ACTION.\n\t * Actions end up in __builtins__, which is dirty, but done for config reasons.\n\t * Calling an action executes it.\n\t */\n\tKrkClass * Action = krk_makeClass(bimModule, &ActionDef, \"Action\", vm.baseClasses->objectClass);\n\tAction->allocSize = sizeof(struct ActionDef);\n\tBIND_METHOD(Action,__call__);\n\tkrk_finalizeClass(Action);\n\n\tfor (struct action_def * a = mappable_actions; mappable_actions && a->name; ++a) {\n\t\tstruct ActionDef * actionObj = (void*)krk_newInstance(Action);\n\t\tactionObj->action = a;\n\t\tkrk_attachNamedObject(&vm.builtins->fields, a->name, (KrkObj*)actionObj);\n\t}\n\n\t/* Class representing a BIM_COMMAND. Works the same as actions. */\n\tKrkClass * Command = krk_makeClass(bimModule, &CommandDef, \"Command\", vm.baseClasses->objectClass);\n\tCommand->allocSize = sizeof(struct CommandDef);\n\tBIND_METHOD(Command,__call__);\n\tkrk_finalizeClass(Command);\n\n\t/* For silly legacy config reasons, we have a special 'global' namespace\n\t * that we just shove into __builtins__. This contains all of the command\n\t * objects that are bound with names starting with \"global.\", naturally. */\n\tKrkInstance * global = krk_newInstance(vm.baseClasses->objectClass);\n\tkrk_attachNamedObject(&vm.builtins->fields, \"global\", (KrkObj*)global);\n\n\tfor (struct command_def * c = regular_commands; regular_commands && c->name; ++c) {\n\t\tstruct CommandDef * commandObj = (void*)krk_newInstance(CommandDef);\n\t\tcommandObj->command = c;\n\t\tif (strstr(c->name,\"global.\") == c->name) {\n\t\t\tkrk_attachNamedObject(&global->fields, c->name + 7, (KrkObj*)commandObj);\n\t\t} else {\n\t\t\tkrk_attachNamedObject(&vm.builtins->fields, c->name, (KrkObj*)commandObj);\n\t\t}\n\t}\n\n\t/**\n\t * SyntaxState is the base class for syntax highlighters.\n\t *\n\t * @class SyntaxState\n\t *  @e Properties\n\t *   @b state  Read-write access to the underlying state number (used for passing context between lines)\n\t *   @b i      Read access to the offset into the line.\n\t *   @b lineno Read access to the line number of the line being highlighted.\n\t *\n\t *  @e Methods\n\t *   @b findKeywords()     Takes a list of keywords and highlights with a given flag based on a qualifier.\n\t *   @b isdigit()          Determines if the argument character is a \"digit\" (0-9)\n\t *   @b isxdigit()         Determines if the argument character is a \"hex digit\" (0-9, a-f, A-F)\n\t *   @b paint()            Paints a number of character cells a given color and advances the highlighter.\n\t *   @b paintComment()     Paints an end-of-line comment, with buzzword handling. Legacy convenience function.\n\t *   @b skip()             Moves the highlighter forward one character cell without painting.\n\t *   @b matchAndPaint()    Similar to @c findKeywords but only highlights one thing.\n\t *   @b commentBuzzwords() Detects and automatically highlights common comment buzzwords. Legacy convenience function.\n\t *   @b rewind()           Rewinds the highlighter, moving it back to a previous character cell.\n\t *   @b __getitem__()      Index into character cells of the current line from the highlighter.\n\t *                         Note, negative indexes will reference cells before the 'cursor', but this\n\t *                         does not apply to slicing, which treats the rest of the line (starting at the\n\t *                         cursor) as a single string, thus -1 is the last character of the line.\n\t *                         Indexing returns @c None rather than raising an IndexError if the requested\n\t *                         character is out of range, similar to behavior of the C @c charrel interface.\n\t *  @e Flags\n\t *   These flags supply the C FLAG_ constants. Unfortunately, this is kinda slow, and\n\t *   it would be nice to have some sort of compile-time constant available so that these\n\t *   don't have to imply attribute lookups at runtime...\n\t */\n\tKrkClass * SyntaxState = krk_makeClass(bimModule, &syntaxStateClass, \"SyntaxState\", vm.baseClasses->objectClass);\n\tSyntaxState->allocSize = sizeof(struct SyntaxState);\n\tBIND_METHOD(SyntaxState,__init__);\n\tBIND_PROP(SyntaxState,state);\n\tBIND_PROP(SyntaxState,i);\n\tBIND_PROP(SyntaxState,lineno);\n\tBIND_METHOD(SyntaxState,findKeywords);\n\tBIND_STATICMETHOD(SyntaxState,cKeywordQualifier);\n\tBIND_STATICMETHOD(SyntaxState,isdigit);\n\tBIND_STATICMETHOD(SyntaxState,isxdigit);\n\tBIND_METHOD(SyntaxState,paint);\n\tBIND_METHOD(SyntaxState,paintComment);\n\tBIND_METHOD(SyntaxState,skip);\n\tBIND_METHOD(SyntaxState,matchAndPaint);\n\tBIND_METHOD(SyntaxState,commentBuzzwords);\n\tBIND_METHOD(SyntaxState,rewind);\n\tBIND_METHOD(SyntaxState,__getitem__);\n\tBIND_METHOD(SyntaxState,__setitem__);\n\tBIND_METHOD(SyntaxState,__contains__);\n\tBIND_METHOD(SyntaxState,__mod__);\n\tBIND_METHOD(SyntaxState,__lshift__);\n#define ATTACH_STATE_FLAG(flag) krk_attachNamedValue(&SyntaxState->methods, #flag, INTEGER_VAL(flag))\n\tATTACH_STATE_FLAG(FLAG_NONE);\n\tATTACH_STATE_FLAG(FLAG_KEYWORD);\n\tATTACH_STATE_FLAG(FLAG_STRING);\n\tATTACH_STATE_FLAG(FLAG_COMMENT);\n\tATTACH_STATE_FLAG(FLAG_TYPE);\n\tATTACH_STATE_FLAG(FLAG_PRAGMA);\n\tATTACH_STATE_FLAG(FLAG_NUMERAL);\n\tATTACH_STATE_FLAG(FLAG_ERROR);\n\tATTACH_STATE_FLAG(FLAG_DIFFPLUS);\n\tATTACH_STATE_FLAG(FLAG_DIFFMINUS);\n\tATTACH_STATE_FLAG(FLAG_NOTICE);\n\tATTACH_STATE_FLAG(FLAG_BOLD);\n\tATTACH_STATE_FLAG(FLAG_LINK);\n\tATTACH_STATE_FLAG(FLAG_ESCAPE);\n\tATTACH_STATE_FLAG(FLAG_EXTRA);\n\tATTACH_STATE_FLAG(FLAG_SPECIAL);\n\tATTACH_STATE_FLAG(FLAG_UNDERLINE);\n\n\t/* This is a dumb cache of characters to avoid recreating them all the time */\n\t_bim_state_chars = krk_newTuple(95);\n\tkrk_attachNamedObject(&syntaxStateClass->methods, \"__chars__\", (KrkObj*)_bim_state_chars);\n\tfor (int c = 0; c < 95; ++c) {\n\t\tchar tmp = c + 32;\n\t\t_bim_state_chars->values.values[_bim_state_chars->values.count++] = OBJECT_VAL(krk_copyString(&tmp,1));\n\t}\n\n\tkrk_finalizeClass(syntaxStateClass);\n\n\tdo_kuroko_imports();\n\n\t/* Disable default traceback printing */\n\tvm.globalFlags |= KRK_GLOBAL_CLEAN_OUTPUT;\n\n\t/* Initialize space for buffers */\n\tbuffers_avail = 4;\n\tbuffers = malloc(sizeof(buffer_t *) * buffers_avail);\n}\n\n/**\n * Initialize terminal for editor display.\n */\nvoid init_terminal(void) {\n\tif (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {\n\t\tglobal_config.tty_in = STDERR_FILENO;\n\t}\n\tset_alternate_screen();\n\tset_bracketed_paste();\n\tupdate_screen_size();\n\tget_initial_termios();\n\tset_unbuffered();\n\tmouse_enable();\n\tglobal_config.has_terminal = 1;\n\n\tsignal(SIGWINCH, SIGWINCH_handler);\n\tsignal(SIGCONT,  SIGCONT_handler);\n\tsignal(SIGTSTP,  SIGTSTP_handler);\n\tsignal(SIGINT,   SIGINT_handler);\n}\n\nstruct action_def * find_action(uintptr_t action) {\n\tif (!action) return NULL;\n\tfor (int i = 0; i < flex_mappable_actions_count; ++i) {\n\t\tif (action == mappable_actions[i].action) return &mappable_actions[i];\n\t}\n\treturn NULL;\n}\n\nvoid dump_mapping(const char * description, struct action_map * map) {\n\tprintf(\"## %s\\n\", description);\n\tprintf(\"\\n\");\n\tprintf(\"| **Key** | **Action** | **Description** |\\n\");\n\tprintf(\"|---------|------------|-----------------|\\n\");\n\tstruct action_map * m = map;\n\twhile (m->key != -1) {\n\t\t/* Find the name of this action */\n\t\tstruct action_def * action = find_action(m->method);\n\t\tprintf(\"| `%s` | `%s` | %s |\\n\", name_from_key(m->key),\n\t\t\taction ? action->name : \"(unbound)\",\n\t\t\taction ? action->description : \"(unbound)\");\n\t\tm++;\n\t}\n\tprintf(\"\\n\");\n}\n\nint sort_regular_commands(const void * a, const void * b) {\n\treturn strcmp(regular_commands[*(int *)a].name, regular_commands[*(int *)b].name);\n}\n\nint sort_prefix_commands(const void * a, const void * b) {\n\treturn strcmp(prefix_commands[*(int *)a].name, prefix_commands[*(int *)b].name);\n}\n\nvoid dump_commands(void) {\n\tprintf(\"## Regular Commands\\n\");\n\tprintf(\"\\n\");\n\tprintf(\"| **Command** | **Description** |\\n\");\n\tprintf(\"|-------------|-----------------|\\n\");\n\tint * offsets = malloc(sizeof(int) * flex_regular_commands_count);\n\tfor (int i = 0; i < flex_regular_commands_count; ++i) {\n\t\toffsets[i] = i;\n\t}\n\tqsort(offsets, flex_regular_commands_count, sizeof(int), sort_regular_commands);\n\tfor (int i = 0; i < flex_regular_commands_count; ++i) {\n\t\tprintf(\"| `:%s` | %s |\\n\", regular_commands[offsets[i]].name, regular_commands[offsets[i]].description);\n\t}\n\tfree(offsets);\n\tprintf(\"\\n\");\n\tprintf(\"## Prefix Commands\\n\");\n\tprintf(\"\\n\");\n\tprintf(\"| **Command** | **Description** |\\n\");\n\tprintf(\"|-------------|-----------------|\\n\");\n\toffsets = malloc(sizeof(int) * flex_prefix_commands_count);\n\tfor (int i = 0; i < flex_prefix_commands_count; ++i) offsets[i] = i;\n\tqsort(offsets, flex_prefix_commands_count, sizeof(int), sort_prefix_commands);\n\tfor (int i = 0; i < flex_prefix_commands_count; ++i) {\n\t\tprintf(\"| `:%s...` | %s |\\n\", !strcmp(prefix_commands[offsets[i]].name, \"`\") ? \"`(backtick)`\" : \n\t\t\tprefix_commands[offsets[i]].name, prefix_commands[offsets[i]].description);\n\t}\n\tfree(offsets);\n\tprintf(\"\\n\");\n}\n\nBIM_COMMAND(whatis,\"whatis\",\"Describe actions bound to a key in different modes.\") {\n\tint key = 0;\n\n\tif (argc < 2) {\n\t\trender_commandline_message(\"(press a key)\");\n\t\twhile ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT);\n\t} else if (strlen(argv[1]) > 1 && argv[1][0] == '^') {\n\t\t/* See if it's a valid ctrl key */\n\t\tif (argv[1][2] != '\\0') {\n\t\t\tgoto _invalid_key_name;\n\t\t}\n\t\tif ((unsigned char)argv[1][1] < '@' || (unsigned char)argv[1][1] > '@' + '_') {\n\t\t\tgoto _invalid_key_name;\n\t\t}\n\t\tkey = argv[1][1] - '@';\n\t} else if (argv[1][1] != '\\0') {\n\t\tfor (unsigned int i = 0; i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) {\n\t\t\tif (!strcmp(KeyNames[i].name,argv[1])) {\n\t\t\t\tkey = KeyNames[i].keycode;\n\t\t\t}\n\t\t}\n\t\tif (!key) goto _invalid_key_name;\n\t} else {\n\t\tkey = (unsigned char)argv[1][0];\n\t}\n\n\tstruct MappingNames {\n\t\tchar * name;\n\t\tstruct action_map * map;\n\t} maps[] = {\n\t\t{\"Normal\", NORMAL_MAP},\n\t\t{\"Insert\", INSERT_MAP},\n\t\t{\"Replace\", REPLACE_MAP},\n\t\t{\"Line Selection\", LINE_SELECTION_MAP},\n\t\t{\"Char Selection\", CHAR_SELECTION_MAP},\n\t\t{\"Col Selection\", COL_SELECTION_MAP},\n\t\t{\"Col Insert\", COL_INSERT_MAP},\n\t\t{\"Navigation (Select)\", NAVIGATION_MAP},\n\t\t{\"Escape (Select, Insert)\", ESCAPE_MAP},\n\t\t{\"Command\", COMMAND_MAP},\n\t\t{\"Search\", SEARCH_MAP},\n\t\t{\"Input (Command, Search)\", INPUT_BUFFER_MAP},\n\t\t{NULL, NULL},\n\t};\n\n\trender_commandline_message(\"\");\n\tint found_something = 0;\n\n\tfor (struct MappingNames * map = maps; map->name; ++map) {\n\t\t/* See if this key is mapped */\n\t\tstruct action_map * m = map->map;\n\t\twhile (m->key != -1) {\n\t\t\tif (m->key == key) {\n\t\t\t\tif (m->options & opt_krk) {\n\t\t\t\t\tstruct StringBuilder sb = {0};\n\t\t\t\t\tkrk_pushStringBuilderFormat(&sb, \"%R\", m->callable);\n\t\t\t\t\tkrk_pushStringBuilder(&sb, '\\0');\n\t\t\t\t\trender_commandline_message(\"%s: %s\\n\", map->name, sb.bytes);\n\t\t\t\t\tkrk_discardStringBuilder(&sb);\n\t\t\t\t} else {\n\t\t\t\t\tstruct action_def * action = find_action(m->method);\n\t\t\t\t\trender_commandline_message(\"%s: %s\\n\", map->name, action ? action->description : \"(unmapped)\");\n\t\t\t\t\tfound_something = 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tm++;\n\t\t}\n\t}\n\n\tif (!found_something) {\n\t\trender_commandline_message(\"Nothing bound for this key\");\n\t}\n\n\tpause_for_key();\n\n\treturn 0;\n_invalid_key_name:\n\trender_error(\"Invalid key name\");\n\treturn 1;\n}\n\nBIM_COMMAND(setcolor, \"setcolor\", \"Set colorscheme colors\") {\n#define PRINT_COLOR do { \\\n\trender_commandline_message(\"%20s = \", c->name); \\\n\tset_colors(*c->value, *c->value); \\\n\tprintf(\"   \"); \\\n\tset_colors(COLOR_FG, COLOR_BG); \\\n\tprintf(\" %s\\n\", *c->value); \\\n\t} while (0)\n\tif (argc < 2) {\n\t\t/* Print colors */\n\t\tstruct ColorName * c = color_names;\n\t\twhile (c->name) {\n\t\t\tPRINT_COLOR;\n\t\t\tc++;\n\t\t}\n\t\tpause_for_key();\n\t} else {\n\t\tchar * colorname = argv[1];\n\t\tif (argc == 2) {\n\t\t\tstruct ColorName * c = color_names;\n\t\t\twhile (c->name) {\n\t\t\t\tif (!strcmp(c->name, colorname)) {\n\t\t\t\t\tPRINT_COLOR;\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tc++;\n\t\t\t}\n\t\t\trender_error(\":setcolor <colorname> <colorvalue>\");\n\t\t\treturn 1;\n\t\t}\n\t\tchar * colorvalue = argv[2];\n\t\tstruct ColorName * c = color_names;\n\t\twhile (c->name) {\n\t\t\tif (!strcmp(c->name, colorname)) {\n\t\t\t\t*(c->value) = strdup(colorvalue);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tc++;\n\t\t}\n\t\trender_error(\"Unknown color: %s\", colorname);\n\t\treturn 1;\n\t}\n\treturn 0;\n#undef PRINT_COLOR\n}\n\nBIM_COMMAND(checkprop,\"checkprop\",\"Check a property value; returns the inverse of the property\") {\n\tif (argc < 2) {\n\t\treturn 1;\n\t}\n\tif (!strcmp(argv[1],\"can_scroll\")) return !global_config.can_scroll;\n\telse if (!strcmp(argv[1],\"can_hideshow\")) return !global_config.can_hideshow;\n\telse if (!strcmp(argv[1],\"can_altscreen\")) return !global_config.can_altscreen;\n\telse if (!strcmp(argv[1],\"can_mouse\")) return !global_config.can_mouse;\n\telse if (!strcmp(argv[1],\"can_unicode\")) return !global_config.can_unicode;\n\telse if (!strcmp(argv[1],\"can_bright\")) return !global_config.can_bright;\n\telse if (!strcmp(argv[1],\"can_title\")) return !global_config.can_title;\n\telse if (!strcmp(argv[1],\"can_bce\")) return !global_config.can_bce;\n\telse if (!strcmp(argv[1],\"can_24bit\")) return !global_config.can_24bit;\n\telse if (!strcmp(argv[1],\"can_256color\")) return !global_config.can_256color;\n\telse if (!strcmp(argv[1],\"can_italic\")) return !global_config.can_italic;\n\trender_error(\"Unknown property '%s'\", argv[1]);\n\treturn 1;\n}\n\nBIM_COMMAND(action,\"action\",\"Execute a bim action\") {\n\tif (argc < 2) {\n\t\trender_error(\"Expected :action <action-name> [arg [arg [arg...]]]\");\n\t\treturn 1;\n\t}\n\n\t/* Split argument on spaces */\n\tchar * action = argv[1];\n\tchar * arg1 = NULL, * arg2 = NULL, * arg3 = NULL;\n\tif (argc > 2) arg1 = argv[2];\n\tif (argc > 3) arg2 = argv[3];\n\tif (argc > 4) arg3 = argv[4];\n\n\t/* Find the action */\n\tfor (int i = 0; i < flex_mappable_actions_count; ++i) {\n\t\tif (!strcmp(mappable_actions[i].name, action)) {\n\t\t\t/* Count arguments */\n\t\t\tint args = 0;\n\t\t\tif (mappable_actions[i].options & ARG_IS_CUSTOM) args++;\n\t\t\tif (mappable_actions[i].options & ARG_IS_INPUT) args++;\n\t\t\tif (mappable_actions[i].options & ARG_IS_PROMPT) args++;\n\n\t\t\tif (args == 0) {\n\t\t\t\t((action_no_arg)mappable_actions[i].action)();\n\t\t\t} else if (args == 1) {\n\t\t\t\tif (!arg1) { render_error(\"Expected one argument\"); return 1; }\n\t\t\t\t((action_one_arg)mappable_actions[i].action)(atoi(arg1));\n\t\t\t} else if (args == 2) {\n\t\t\t\tif (!arg2) { render_error(\"Expected two arguments\"); return 1; }\n\t\t\t\t((action_two_arg)mappable_actions[i].action)(atoi(arg1), atoi(arg2));\n\t\t\t} else if (args == 3) {\n\t\t\t\tif (!arg3) { render_error(\"Expected three arguments\"); return 1; }\n\t\t\t\t((action_three_arg)mappable_actions[i].action)(atoi(arg1), atoi(arg2), atoi(arg3));\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\trender_error(\"Unknown action: %s\", action);\n\treturn 1;\n}\n\nchar * describe_options(int options) {\n\tstatic char out[16];\n\n\tmemset(out,0,sizeof(out));\n\tif (options & opt_rep)  strcat(out,\"r\"); /* Repeats */\n\tif (options & opt_arg)  strcat(out,\"a\"); /* takes Argument */\n\tif (options & opt_char) strcat(out,\"c\"); /* takes Character */\n\tif (options & opt_nav)  strcat(out,\"n\"); /* consumes Nav buffer */\n\tif (options & opt_rw)   strcat(out,\"w\"); /* read-Write */\n\tif (options & opt_norm) strcat(out,\"m\"); /* changes Mode */\n\tif (options & opt_byte) strcat(out,\"b\"); /* takes Byte */\n\n\treturn out;\n}\n\nvoid dump_map_commands(const char * name, struct action_map * map) {\n\tstruct action_map * m = map;\n\twhile (m->key != -1) {\n\t\tif (m->options & opt_krk) {\n\t\t\tfprintf(stdout,\"# key %s bound in %s mode to a krk function\",\n\t\t\t\tname_from_key(m->key), name);\n\t\t} else {\n\t\t\tstruct action_def * action = find_action(m->method);\n\t\t\tfprintf(stdout,\"mapkey %s %s %s\",\n\t\t\t\tname,\n\t\t\t\tname_from_key(m->key),\n\t\t\t\taction ? action->name : \"none\");\n\t\t\tif (m->options) {\n\t\t\t\tprintf(\" %s\", describe_options(m->options));\n\t\t\t\tif (m->options & opt_arg) {\n\t\t\t\t\tprintf(\" %d\", m->arg);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tprintf(\"\\n\");\n\t\tm++;\n\t}\n}\n\nBIM_COMMAND(mapkey,\"mapkey\",\"Map a key to an action.\") {\n\tif (argc < 2) goto _argument_error;\n\n\tchar * mode = argv[1];\n\n\tchar * key = strstr(mode,\" \");\n\tif (!key) goto _argument_error;\n\t*key = '\\0';\n\tkey++;\n\n\tchar * action = strstr(key,\" \");\n\tif (!action) goto _argument_error;\n\t*action = '\\0';\n\taction++;\n\n\t/* Options are optional */\n\tchar * options = strstr(action, \" \");\n\tchar * arg = NULL;\n\tif (options) {\n\t\t*options = '\\0';\n\t\toptions++;\n\n\t\targ = strstr(options, \" \");\n\t\tif (arg) {\n\t\t\t*arg = '\\0';\n\t\t\targ++;\n\t\t}\n\t}\n\n\trender_status_message(\"Going to map key %s in mode %s to action %s with options %s, %s\",\n\t\tkey, mode, action, options, arg);\n\n\t/* Convert mode to mode name */\n\tstruct action_map ** mode_map = NULL;\n\tfor (struct mode_names * m = mode_names; m->name; ++m) {\n\t\tif (!strcmp(m->name, mode)) {\n\t\t\tmode_map = m->mode;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!mode_map) {\n\t\trender_error(\"invalid mode: %s\", mode);\n\t\treturn 1;\n\t}\n\n\tenum Key keycode = key_from_name(key);\n\tif (keycode == -1) {\n\t\trender_error(\"invalid key: %s\", key);\n\t\treturn 1;\n\t}\n\n\tstruct action_def * action_def = NULL;\n\tfor (int i = 0; i < flex_mappable_actions_count; ++i) {\n\t\tif (!strcmp(mappable_actions[i].name, action)) {\n\t\t\taction_def = &mappable_actions[i];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!action_def) {\n\t\trender_error(\"invalid action: %s\", action);\n\t\treturn 1;\n\t}\n\n\t/* Sanity check required options */\n\tif ((action_def->options & ARG_IS_CUSTOM) &&\n\t\t(!options || (!strchr(options,'a') && !strchr(options,'n')))) goto _action_sanity;\n\tif ((action_def->options & ARG_IS_PROMPT) &&\n\t\t(!options || (!strchr(options,'c') && !strchr(options,'b')))) goto _action_sanity;\n\tif ((action_def->options & ACTION_IS_RW)  &&\n\t\t(!options || !strchr(options,'w'))) goto _action_sanity;\n\n\tint option_map = 0;\n\n\tif (options) {\n\t\tfor (char * o = options; *o; ++o) {\n\t\t\tswitch (*o) {\n\t\t\t\tcase 'r': option_map |= opt_rep; break;\n\t\t\t\tcase 'a': option_map |= opt_arg; break;\n\t\t\t\tcase 'c': option_map |= opt_char; break;\n\t\t\t\tcase 'n': option_map |= opt_nav; break;\n\t\t\t\tcase 'w': option_map |= opt_rw; break;\n\t\t\t\tcase 'm': option_map |= opt_norm; break;\n\t\t\t\tcase 'b': option_map |= opt_byte; break;\n\t\t\t\tdefault:\n\t\t\t\t\trender_error(\"Invalid option flag: %c\", *o);\n\t\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif ((option_map & opt_arg) && !arg) {\n\t\trender_error(\"flag 'a' requires an additional argument\");\n\t\treturn 1;\n\t}\n\n\tint arg_value = (option_map & opt_arg) ? atoi(arg) : 0;\n\n\t/* Make space */\n\tstruct action_map * candidate = NULL;\n\tfor (struct action_map * m = *mode_map; m->key != -1; ++m) {\n\t\tif (m->key == keycode) {\n\t\t\tcandidate = m;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!candidate) {\n\t\t/* get size */\n\t\tint len = 0;\n\t\tfor (struct action_map * m = *mode_map; m->key != -1; m++, len++);\n\t\t*mode_map = realloc(*mode_map, sizeof(struct action_map) * (len + 2));\n\t\tcandidate = &(*mode_map)[len];\n\t\t(*mode_map)[len+1].key = -1;\n\t}\n\n\tcandidate->key = keycode;\n\tcandidate->method = action_def->action;\n\tcandidate->options = option_map;\n\tcandidate->arg = arg_value;\n\n\treturn 0;\n\n_action_sanity:\n\trender_error(\"action %s requires missing flag\", action);\n\treturn 1;\n\n_argument_error:\n\trender_error(\"usage: mapkey MODE KEY ACTION [OPTIONS [ARG]]\");\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tfindBim(argv);\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?c:C:u:q:RS:O:-:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'R':\n\t\t\t\tglobal_config.initial_file_is_read_only = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':;\n\t\t\t\tinitialize();\n\t\t\t\tglobal_config.use_biminfo = 0;\n\t\t\t\tglobal_config.go_to_line = 0;\n\t\t\t\topen_file(optarg);\n\t\t\t\tenv->loading = 1;\n\t\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\t\trecalculate_syntax(env->lines[i], i);\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\tcase 'C':\n\t\t\t\t/* Print file to stdout using our syntax highlighting and color theme */\n\t\t\t\tinitialize();\n\t\t\t\tglobal_config.use_biminfo = 0;\n\t\t\t\tglobal_config.go_to_line = 0;\n\t\t\t\topen_file(optarg);\n\t\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\t\trecalculate_syntax(env->lines[i], i);\n\t\t\t\t\tif (opt == 'C') {\n\t\t\t\t\t\tdraw_line_number(i);\n\t\t\t\t\t}\n\t\t\t\t\trender_line(env->lines[i], 6 * (env->lines[i]->actual + 1), 0, -1);\n\t\t\t\t\treset();\n\t\t\t\t\tfprintf(stdout, \"\\n\");\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\tcase 'u':\n\t\t\t\tglobal_config.bimrc_path = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'S':\n\t\t\t\tglobal_config.syntax_fallback = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'O':\n\t\t\t\t/* Set various display options */\n\t\t\t\tif (set_capability(optarg)) {\n\t\t\t\t\tfprintf(stderr, \"%s: unrecognized -O option: %s\\n\", argv[0], optarg);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase '-':\n\t\t\t\tif (!strcmp(optarg,\"version\")) {\n\t\t\t\t\tinitialize(); /* Need to load bimrc to get themes */\n\t\t\t\t\tupdate_screen_size(); /* Get terminal size if possible */\n\t\t\t\t\tfprintf(stderr, \"\\033[1mbim\\033[0m %s%s\\n%s\\n\\n\", BIM_VERSION, BIM_BUILD_DATE, BIM_COPYRIGHT);\n\t\t\t\t\t#define SECTION(title) do { \\\n\t\t\t\t\t\tint x, width; \\\n\t\t\t\t\t\twidth = 2 + display_width_of_string(title); \\\n\t\t\t\t\t\tfprintf(stderr, \" \\033[1m%s\\033[0m:\", title); \\\n\t\t\t\t\t\tx = width;\n\t\t\t\t\t#define ENDSECTION() fprintf(stderr, \"\\n\\n\"); } while (0)\n\t\t\t\t\t#define ITEM(str) do { \\\n\t\t\t\t\t\tint my_width = display_width_of_string(str); \\\n\t\t\t\t\t\tif (x + my_width + 1 >= global_config.term_width) { \\\n\t\t\t\t\t\t\tfprintf(stderr, \"\\n\"); \\\n\t\t\t\t\t\t\tfor (x = 0; x <= width; ++x) fprintf(stderr, \" \"); \\\n\t\t\t\t\t\t\tfprintf(stderr, \"%s\", str); \\\n\t\t\t\t\t\t\tx += width; \\\n\t\t\t\t\t\t} else { \\\n\t\t\t\t\t\t\tfprintf(stderr, \" %s\", str); \\\n\t\t\t\t\t\t\tx += my_width + 1; \\\n\t\t\t\t\t\t} } while(0)\n\n\t\t\t\t\tSECTION(\"Syntax\");\n\t\t\t\t\tfor (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) {\n\t\t\t\t\t\tITEM(s->name);\n\t\t\t\t\t}\n\t\t\t\t\tENDSECTION();\n\t\t\t\t\tSECTION(\"Themes\");\n\t\t\t\t\tfor (struct theme_def * d = themes; themes && d->name; ++d) {\n\t\t\t\t\t\tITEM(d->name);\n\t\t\t\t\t}\n\t\t\t\t\tENDSECTION();\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (!strcmp(optarg,\"help\")) {\n\t\t\t\t\tshow_usage(argv);\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (!strcmp(optarg,\"dump-mappings\")) {\n\t\t\t\t\tinitialize();\n\t\t\t\t\tfor (struct mode_names * m = mode_names; m->name; ++m) {\n\t\t\t\t\t\tdump_mapping(m->description, *m->mode);\n\t\t\t\t\t}\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (!strcmp(optarg,\"dump-commands\")) {\n\t\t\t\t\tinitialize();\n\t\t\t\t\tdump_commands();\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (!strcmp(optarg,\"html\")) {\n\t\t\t\t\tif (optind >= argc) {\n\t\t\t\t\t\tshow_usage(argv);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t\tinitialize();\n\t\t\t\t\tglobal_config.go_to_line = 0;\n\t\t\t\t\topen_file(argv[optind]);\n\t\t\t\t\tfor (int i = 0; i < env->line_count; ++i) {\n\t\t\t\t\t\trecalculate_syntax(env->lines[i], i);\n\t\t\t\t\t}\n\t\t\t\t\tconvert_to_html();\n\t\t\t\t\t/* write to stdout */\n\t\t\t\t\toutput_file(env, stdout);\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (!strcmp(optarg,\"dump-config\")) {\n\t\t\t\t\tinitialize();\n\t\t\t\t\t/* Dump a config file representing the current key mappings */\n\t\t\t\t\tfor (struct mode_names * m = mode_names; m->name; ++m) {\n\t\t\t\t\t\tdump_map_commands(m->name, *m->mode);\n\t\t\t\t\t}\n\t\t\t\t\treturn 0;\n\t\t\t\t} else if (strlen(optarg)) {\n\t\t\t\t\tfprintf(stderr, \"bim: unrecognized option `%s'\\n\", optarg);\n\t\t\t\t\treturn 1;\n\t\t\t\t} /* Else, this is -- to indicate end of arguments */\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\t/* Set up terminal */\n\tinitialize();\n\tinit_terminal();\n\n\t/* Open file */\n\tif (argc > optind) {\n\t\twhile (argc > optind) {\n\t\t\topen_file(argv[optind]);\n\t\t\tupdate_title();\n\t\t\tif (global_config.initial_file_is_read_only) {\n\t\t\t\tenv->readonly = 1;\n\t\t\t}\n\t\t\toptind++;\n\t\t}\n\t\tenv = buffers[0];\n\t} else {\n\t\tenv = buffer_new();\n\t\tsetup_buffer(env);\n\t}\n\n\tupdate_title();\n\n\t/* Draw the screen once */\n\tredraw_all();\n\n\t/* Start accepting key commands */\n\tnormal_mode();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/bim.h",
    "content": "#ifndef _BIM_CORE_H\n#define _BIM_CORE_H\n\n#define _XOPEN_SOURCE 700\n#define _DARWIN_C_SOURCE\n#define _DEFAULT_SOURCE\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdint.h>\n#include <stdarg.h>\n#include <unistd.h>\n#include <termios.h>\n#include <signal.h>\n#include <libgen.h>\n#include <locale.h>\n#include <wchar.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <poll.h>\n#include <limits.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n#include <sys/wait.h>\n#include <kuroko/vm.h>\n\n#ifdef __DATE__\n# define BIM_BUILD_DATE \" built \" __DATE__ \" at \" __TIME__\n#else\n# define BIM_BUILD_DATE DATE \"\"\n#endif\n\n#ifdef GIT_TAG\n# define TAG \"-\" GIT_TAG\n#else\n# define TAG \"-alpha\"\n#endif\n\n#define BLOCK_SIZE 4096\n#define ENTER_KEY     '\\r'\n#define LINE_FEED     '\\n'\n#define BACKSPACE_KEY 0x08\n#define DELETE_KEY    0x7F\n#define DEFAULT_KEY_WAIT (global_config.background_task ? 0 : -1)\n\nenum Key {\n\tKEY_TIMEOUT = -1,\n\tKEY_CTRL_AT = 0, /* Base */\n\tKEY_CTRL_A, KEY_CTRL_B, KEY_CTRL_C, KEY_CTRL_D, KEY_CTRL_E, KEY_CTRL_F, KEY_CTRL_G, KEY_CTRL_H,\n\tKEY_CTRL_I, KEY_CTRL_J, KEY_CTRL_K, KEY_CTRL_L, KEY_CTRL_M, KEY_CTRL_N, KEY_CTRL_O, KEY_CTRL_P,\n\tKEY_CTRL_Q, KEY_CTRL_R, KEY_CTRL_S, KEY_CTRL_T, KEY_CTRL_U, KEY_CTRL_V, KEY_CTRL_W, KEY_CTRL_X,\n\tKEY_CTRL_Y, KEY_CTRL_Z, /* Note we keep ctrl-z mapped in termios as suspend */\n\tKEY_CTRL_OPEN, KEY_CTRL_BACKSLASH, KEY_CTRL_CLOSE, KEY_CTRL_CARAT, KEY_CTRL_UNDERSCORE,\n\t/* Space... */\n\t/* Some of these are equivalent to things above */\n\tKEY_BACKSPACE = 0x08,\n\tKEY_LINEFEED = '\\n',\n\tKEY_ENTER = '\\r',\n\tKEY_TAB = '\\t',\n\t/* Basic printable characters go here. */\n\t/* Delete is special */\n\tKEY_DELETE = 0x7F,\n\t/* Unicode codepoints go here */\n\tKEY_ESCAPE = 0x400000, /* Escape would normally be 27, but is special because reasons */\n\tKEY_F1, KEY_F2, KEY_F3, KEY_F4,\n\tKEY_F5, KEY_F6, KEY_F7, KEY_F8,\n\tKEY_F9, KEY_F10, KEY_F11, KEY_F12,\n\t/* TODO ALT, SHIFT, etc., for F keys */\n\tKEY_MOUSE, /* Must be followed with a 3-byte mouse read */\n\tKEY_MOUSE_SGR, /* Followed by an SGR-style sequence of mouse data */\n\tKEY_HOME, KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN,\n\tKEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT,\n\tKEY_SHIFT_UP, KEY_SHIFT_DOWN, KEY_SHIFT_RIGHT, KEY_SHIFT_LEFT,\n\tKEY_CTRL_UP, KEY_CTRL_DOWN, KEY_CTRL_RIGHT, KEY_CTRL_LEFT,\n\tKEY_ALT_UP, KEY_ALT_DOWN, KEY_ALT_RIGHT, KEY_ALT_LEFT,\n\tKEY_ALT_SHIFT_UP, KEY_ALT_SHIFT_DOWN, KEY_ALT_SHIFT_RIGHT, KEY_ALT_SHIFT_LEFT,\n\tKEY_SHIFT_TAB,\n\t/* Special signals for paste start, paste end */\n\tKEY_PASTE_BEGIN, KEY_PASTE_END,\n};\n\nstruct key_name_map {\n\tenum Key keycode;\n\tchar * name;\n};\n\nextern struct key_name_map KeyNames[];\n\n/**\n * Syntax highlighting flags.\n */\n#define FLAG_NONE      0\n#define FLAG_KEYWORD   1\n#define FLAG_STRING    2\n#define FLAG_COMMENT   3\n#define FLAG_TYPE      4\n#define FLAG_PRAGMA    5\n#define FLAG_NUMERAL   6\n#define FLAG_ERROR     7\n#define FLAG_DIFFPLUS  8\n#define FLAG_DIFFMINUS 9\n#define FLAG_NOTICE    10\n#define FLAG_BOLD      11\n#define FLAG_LINK      (12 + (1<<4))\n#define FLAG_ESCAPE    13\n#define FLAG_EXTRA     14\n#define FLAG_SPECIAL   15\n\n#define FLAG_LINK_COLOR 12\n\n#define FLAG_UNDERLINE (1 << 4)\n#define FLAG_SELECT    (1 << 5)\n#define FLAG_SEARCH    (1 << 6)\n\n#define FLAG_MASK_COLORS 0x0F\n#define FLAG_MASK_ATTRIB 0x70\n\n/**\n * Line buffer definitions\n *\n * Lines are essentially resizable vectors of char_t structs,\n * which represent single codepoints in the file.\n */\ntypedef struct {\n\tuint32_t display_width:4;\n\tuint32_t flags:7;\n\tuint32_t codepoint:21;\n} __attribute__((packed)) char_t;\n\n/**\n * Lines have available and actual lengths, describing\n * how much space was allocated vs. how much is being\n * used at the moment.\n */\ntypedef struct {\n\tint available;\n\tint actual;\n\tint istate;\n\tint is_current;\n\tint rev_status;\n\tchar_t   text[];\n} line_t;\n\ntypedef struct background_task {\n\tstruct _env * env;\n\tvoid (*func)(struct background_task*);\n\tstruct background_task * next;\n\tint _private_i;\n\tvoid * _private_p;\n} background_task_t;\n\n/**\n * Global configuration state\n *\n * At the moment, this is all in a global, but in the future\n * this should be passed around to various functions.\n */\ntypedef struct {\n\t/* Terminal size */\n\tint term_width, term_height;\n\tint bottom_size;\n\n\tline_t ** yanks;\n\tsize_t    yank_count;\n\tint       yank_is_full_lines;\n\n\tint tty_in;\n\n\tconst char * bimrc_path;\n\tconst char * syntax_fallback;\n\tuint32_t * search;\n\n\tint overlay_mode;\n\tline_t * command_buffer;\n\n\tint command_offset, command_col_no;\n\tstruct syntax_definition * command_syn, * command_syn_back;\n\tint history_point;\n\tint search_point;\n\tint search_direction;\n\tint prev_line, prev_col, prev_coffset, prev_offset;\n\n\tunsigned int highlight_on_open:1;\n\tunsigned int initial_file_is_read_only:1;\n\tunsigned int go_to_line:1;\n\tunsigned int break_from_selection:1;\n\tunsigned int can_scroll:1;\n\tunsigned int can_hideshow:1;\n\tunsigned int can_altscreen:1;\n\tunsigned int can_mouse:1;\n\tunsigned int can_unicode:1;\n\tunsigned int can_bright:1;\n\tunsigned int can_title:1;\n\tunsigned int can_bce:1;\n\tunsigned int can_24bit:1;\n\tunsigned int can_256color:1;\n\tunsigned int can_italic:1;\n\tunsigned int can_insert:1;\n\tunsigned int can_bracketedpaste:1;\n\tunsigned int can_sgrmouse:1;\n\n\tunsigned int history_enabled:1;\n\tunsigned int highlight_parens:1;\n\tunsigned int smart_case:1;\n\tunsigned int highlight_current_line:1;\n\tunsigned int shift_scrolling:1;\n\tunsigned int check_git:1;\n\tunsigned int color_gutter:1;\n\tunsigned int relative_lines:1;\n\tunsigned int numbers:1;\n\tunsigned int horizontal_shift_scrolling:1;\n\tunsigned int hide_statusbar:1;\n\tunsigned int tabs_visible:1;\n\tunsigned int autohide_tabs:1;\n\tunsigned int smart_complete:1;\n\tunsigned int has_terminal:1;\n\tunsigned int search_wraps:1;\n\tunsigned int had_error:1;\n\tunsigned int use_biminfo:1;\n\n\tint cursor_padding;\n\tint split_percent;\n\tint scroll_amount;\n\tint tab_offset;\n\n\tchar * tab_indicator;\n\tchar * space_indicator;\n\n\tbackground_task_t * background_task;\n\tbackground_task_t * tail_task;\n\n\tuint32_t * paren_pairs;\n\n} global_config_t;\n\n#define OVERLAY_MODE_NONE     0\n#define OVERLAY_MODE_READ_ONE 1\n#define OVERLAY_MODE_COMMAND  2\n#define OVERLAY_MODE_SEARCH   3\n#define OVERLAY_MODE_COMPLETE 4\n#define OVERLAY_MODE_FILESEARCH 5\n\n#define HISTORY_SENTINEL     0\n#define HISTORY_INSERT       1\n#define HISTORY_DELETE       2\n#define HISTORY_REPLACE      3\n#define HISTORY_REMOVE_LINE  4\n#define HISTORY_ADD_LINE     5\n#define HISTORY_REPLACE_LINE 6\n#define HISTORY_MERGE_LINES  7\n#define HISTORY_SPLIT_LINE   8\n\n#define HISTORY_BREAK        10\n\ntypedef struct history {\n\tstruct history * previous;\n\tstruct history * next;\n\tint type;\n\tint line;\n\tint col;\n\tunion {\n\t\tstruct {\n\t\t\tint lineno;\n\t\t\tint offset;\n\t\t\tint codepoint;\n\t\t\tint old_codepoint;\n\t\t} insert_delete_replace;\n\n\t\tstruct {\n\t\t\tint lineno;\n\t\t\tline_t * contents;\n\t\t\tline_t * old_contents;\n\t\t} remove_replace_line;\n\n\t\tstruct {\n\t\t\tint lineno;\n\t\t\tint split;\n\t\t} add_merge_split_lines;\n\t} contents;\n} history_t;\n\n/**\n * Buffer data\n *\n * A buffer describes a file, and stores\n * its name as well as the editor state\n * (cursor offsets, etc.) and the actual\n * line buffers.\n */\ntypedef struct _env {\n\tunsigned int loading:1;\n\tunsigned int tabs:1;\n\tunsigned int modified:1;\n\tunsigned int readonly:1;\n\tunsigned int indent:1;\n\tunsigned int checkgitstatusonwrite:1;\n\tunsigned int crnl:1;\n\tunsigned int numbers:1;\n\tunsigned int gutter:1;\n\tunsigned int slowop:1;\n\n\tint highlighting_paren;\n\tint maxcolumn;\n\n\tshort  mode;\n\tshort  tabstop;\n\n\tchar * file_name;\n\tint    offset;\n\tint    coffset;\n\tint    line_no;\n\tint    line_count;\n\tint    line_avail;\n\tint    col_no;\n\tint    preferred_column;\n\tstruct syntax_definition * syntax;\n\tline_t ** lines;\n\n\thistory_t * history;\n\thistory_t * last_save_history;\n\n\tint width;\n\tint left;\n\n\tint start_line;\n\tint sel_col;\n\tint start_col;\n\tint prev_line;\n} buffer_t;\n\nstruct theme_def {\n\tconst char * name;\n\tvoid * callable;\n};\n\nextern struct theme_def * themes;\n\nextern void add_colorscheme(struct theme_def theme);\n\nstruct syntax_state {\n\tbuffer_t * env;\n\tline_t * line;\n\tint line_no;\n\tint state;\n\tint i;\n};\n\nstruct completion_match {\n\tchar * string;\n\tchar * file;\n\tchar * search;\n};\n\nstruct syntax_definition {\n\tchar * name;\n\tchar ** ext;\n\tint (*calculate)(struct syntax_state *);\n\tint prefers_spaces;\n\tint (*completion_qualifier)(int c);\n\tint (*completion_matcher)(uint32_t * comp, struct completion_match ** matches, int * matches_count, int complete_match, int * matches_len, buffer_t * env);\n\tvoid * krkFunc;\n\tvoid * krkClass;\n};\n\nextern struct syntax_definition * syntaxes;\n\n/**\n * Editor mode states\n */\n#define MODE_NORMAL 0\n#define MODE_INSERT 1\n#define MODE_LINE_SELECTION 2\n#define MODE_REPLACE 3\n#define MODE_CHAR_SELECTION 4\n#define MODE_COL_SELECTION 5\n#define MODE_COL_INSERT 6\n#define MODE_DIRECTORY_BROWSE 7\n\nstruct action_def {\n\tchar * name;\n\tuintptr_t action;\n\tint options;\n\tconst char * description;\n};\n\nextern struct action_def * mappable_actions;\n\n#define ARG_IS_INPUT   0x01 /* Takes the key that triggered it as the first argument */\n#define ARG_IS_CUSTOM  0x02 /* Takes a custom argument which is specific to the method */\n#define ARG_IS_PROMPT  0x04 /* Prompts for an argument. */\n#define ACTION_IS_RW   0x08 /* Needs to be able to write. */\n\n#define BIM_ACTION(name, options, description, ...) \\\n\textern void name(__VA_ARGS__); /* Define the action with unknown arguments */ \\\n\tvoid __attribute__((constructor)) _install_ ## name (void) { \\\n\t\tadd_action((struct action_def){#name, (uintptr_t)name, options, description}); \\\n\t} \\\n\tvoid name(__VA_ARGS__)\n\nstruct command_def {\n\tchar * name;\n\tint (*command)(char *, int, char * arg[]);\n\tconst char * description;\n};\n\n#define BIM_COMMAND(cmd_name, cmd_str, description) \\\n\tint bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \\\n\tvoid __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \\\n\t\tadd_command((struct command_def){cmd_str, bim_command_ ## cmd_name, description}); \\\n\t} \\\n\tint bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused)))\n\n#define BIM_ALIAS(alias, alias_name, cmd_name) \\\n\tvoid __attribute__((constructor)) _install_alias_ ## alias_name (void) { \\\n\t\tadd_command((struct command_def){alias, bim_command_ ## cmd_name, \"Alias for \" #cmd_name}); \\\n\t}\n\n#define BIM_PREFIX_COMMAND(cmd_name, cmd_prefix, description) \\\n\tint bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \\\n\tvoid __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \\\n\t\tadd_prefix_command((struct command_def){cmd_prefix, bim_command_ ## cmd_name, description}); \\\n\t} \\\n\tint bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused)))\n\nextern const char * flag_to_color(int _flag);\nextern void redraw_line(int x);\nextern int git_examine(char * filename);\nextern void search_next(void);\nextern void set_preferred_column(void);\nextern void quit(const char * message);\nextern void close_buffer(void);\nextern void set_syntax_by_name(const char * name);\nextern void rehighlight_search(line_t * line);\nextern void try_to_center(void);\nextern int read_one_character(char * message);\nextern void bim_unget(int c);\n#define bim_getch() bim_getch_timeout(200)\nextern int bim_getch_timeout(int timeout);\nextern buffer_t * buffer_new(void);\nextern FILE * open_biminfo(void);\nextern int fetch_from_biminfo(buffer_t * buf);\nextern int update_biminfo(buffer_t * buf, int is_open);\nextern buffer_t * buffer_close(buffer_t * buf);\nextern int to_eight(uint32_t codepoint, char * out);\nextern char * name_from_key(enum Key keycode);\nextern void add_action(struct action_def action);\nextern void open_file(char * file);\nextern void recalculate_selected_lines(void);\nextern void add_command(struct command_def command);\nextern void add_prefix_command(struct command_def command);\nextern void render_command_input_buffer(void);\nextern void unhighlight_matching_paren(void);\n\nextern void add_syntax(struct syntax_definition def);\n\nstruct ColorName {\n\tconst char * name;\n\tconst char ** value;\n};\n\nextern struct ColorName color_names[];\n\nstruct bim_function {\n\tchar * command;\n\tstruct bim_function * next;\n};\n\nextern struct bim_function ** user_functions;\nextern int run_function(char * name);\nextern int has_function(char * name);\nextern void find_matching_paren(int * out_line, int * out_col, int in_col);\nextern void render_error(char * message, ...);\nextern void render_commandline_message(char * message, ...);\nextern void pause_for_key(void);\n\n#define add_match(match_string, match_file, match_search) do { \\\n\tif (*matches_count == *matches_len) { \\\n\t\t(*matches_len) *= 2; \\\n\t\t*matches = realloc(*matches, sizeof(struct completion_match) * (*matches_len)); \\\n\t} \\\n\t(*matches)[*matches_count].string = strdup(match_string); \\\n\t(*matches)[*matches_count].file = strdup(match_file); \\\n\t(*matches)[*matches_count].search = strdup(match_search); \\\n\t(*matches_count)++; \\\n} while (0)\n\nstruct action_map {\n\tint key;\n\tint options;\n\tunion {\n\t\tstruct {\n\t\t\tuintptr_t method;\n\t\t\tint arg;\n\t\t};\n\t\tKrkValue callable;\n\t};\n};\n\n#define opt_rep  0x1 /* This action will be repeated */\n#define opt_arg  0x2 /* This action will take a specified argument */\n#define opt_char 0x4 /* This action will read a character to pass as an argument */\n#define opt_nav  0x8 /* This action will consume the nav buffer as its argument */\n#define opt_rw   0x10 /* Must not be read-only */\n#define opt_norm 0x20 /* Returns to normal mode */\n#define opt_byte 0x40 /* Same as opt_char but forces a byte */\n#define opt_krk  0x80\n\nstruct mode_names {\n\tconst char * description;\n\tconst char * name;\n\tstruct action_map ** mode;\n};\n\nextern struct mode_names mode_names[];\n\n#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { \\\n\tstate->line->text[state->i].flags = (state->line->text[state->i].flags & (3 << 5)) | (flag); \\\n} } while (0)\n#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1)\n#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1)\n#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1)\n#define skip() (state->i++)\n#define charrel(x) ((state->i + (x) >= 0 && state->i + (x) < state->line->actual) ? state->line->text[(state->i+(x))].codepoint : -1)\n\nstatic int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c));\nstatic int common_comment_buzzwords(struct syntax_state * state);\nstatic int paint_comment(struct syntax_state * state);\nstatic struct syntax_definition * find_syntax_calculator(const char * name);\n\n/* Hacky workaround for isdigit not really accepting Unicode stuff */\nstatic __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); }\nstatic __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); }\n\n#undef isdigit\n#undef isxdigit\n#define isdigit(c) _isdigit(c)\n#define isxdigit(c) _isxdigit(c)\n\n#endif /* _BIM_CORE_H */\n"
  },
  {
    "path": "apps/block-dev-stats.c",
    "content": "/**\n * @brief Show block device statistics, where available.\n *\n * Shows cache hit/miss/write counts for ATA devices, mostly.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <stdint.h>\n\n#include <sys/ioctl.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) return 1;\n\n\tint fd = open(argv[1],O_RDONLY);\n\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"open: %d\\n\", fd);\n\t\treturn 2;\n\t}\n\n\tuint64_t stats[4] = {-1};\n\n\tlong res = ioctl(fd, 0x2A01234UL, &stats);\n\n\tif (res < 0) {\n\t\tfprintf(stderr, \"ioctl: %ld\\n\", res);\n\t\treturn 3;\n\t}\n\n\tfprintf(stderr, \"hits:\\t%zu\\n\", stats[0]);\n\tfprintf(stderr, \"misses:\\t%zu\\n\", stats[1]);\n\tfprintf(stderr, \"evicts:\\t%zu\\n\", stats[2]);\n\tfprintf(stderr, \"writes:\\t%zu\\n\", stats[3]);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/cal.c",
    "content": "/**\n * @brief cal - print a calendar\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2019 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/time.h>\n\nconst char * month_names[] = {\n\t\"January\",\n\t\"February\",\n\t\"March\",\n\t\"April\",\n\t\"May\",\n\t\"June\",\n\t\"July\",\n\t\"August\",\n\t\"September\",\n\t\"October\",\n\t\"November\",\n\t\"December\",\n};\n\nint days_in_months[] = {\n\t31, 0, 31, 30, 31, 30, 31,\n\t31, 30, 31, 30, 31,\n};\n\nint main(int argc, char * argv[]) {\n\tif (argc > 1) {\n\t\tfprintf(stderr, \"%s: arguments not currently supported\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\n\tstruct tm actual;\n\tstruct tm * timeinfo;\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tmemcpy(&actual, timeinfo, sizeof(struct tm));\n\ttimeinfo = &actual;\n\n\tchar month[20];\n\tsprintf(month, \"%s %d\", month_names[timeinfo->tm_mon], timeinfo->tm_year + 1900);\n\n\tint len = (20 - strlen(month)) / 2;\n\twhile (len > 0) {\n\t\tprintf(\" \");\n\t\tlen--;\n\t}\n\n\t/* Heading */\n\tprintf(\"%s\\n\", month);\n\tprintf(\"Su Mo Tu We Th Fr Sa\\n\");\n\n\t/* Now's the fun part. */\n\n\tint days_in_month = days_in_months[timeinfo->tm_mon];\n\tif (days_in_month == 0) {\n\t\t/* How many days in February? */\n\t\tstruct tm tmp;\n\t\tmemcpy(&tmp, timeinfo, sizeof(struct tm));\n\t\ttmp.tm_mday = 29;\n\t\ttmp.tm_hour = 12;\n\t\ttime_t tmp3 = mktime(&tmp);\n\t\tstruct tm * tmp2 = localtime(&tmp3);\n\t\tif (tmp2->tm_mday == 29) {\n\t\t\tdays_in_month = 29;\n\t\t} else {\n\t\t\tdays_in_month = 28;\n\t\t}\n\t}\n\n\tint mday = timeinfo->tm_mday;\n\tint wday = timeinfo->tm_wday; /* 0 == sunday */\n\n\twhile (mday > 1) {\n\t\tmday--;\n\t\twday = (wday + 6) % 7;\n\t}\n\n\tfor (int i = 0; i < wday; ++i) {\n\t\tprintf(\"   \");\n\t}\n\n\twhile (mday <= days_in_month) {\n\t\tif (mday == timeinfo->tm_mday) {\n\t\t\tprintf(\"\\033[7m%2d\\033[0m \", mday);\n\t\t} else {\n\t\t\tprintf(\"%2d \", mday);\n\t\t}\n\n\t\tif (wday == 6) {\n\t\t\tprintf(\"\\n\");\n\t\t}\n\n\t\tmday += 1;\n\t\twday = (wday + 1) % 7;\n\t}\n\n\tif (wday != 0) {\n\t\tprintf(\"\\n\");\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/calculator.c",
    "content": "/**\n * @file apps/calculator.c\n * @brief Four-function calculator app.\n *\n * This calculator app is intended to be a more straightforward playground\n * for building out a widget toolkit. The calculator presents buttons in\n * a grid layout alongside a text input box and a menubar.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/button.h>\n#include <toaru/text.h>\n#include <toaru/markup_text.h>\n#include <kuroko/kuroko.h>\n#include <kuroko/vm.h>\n#include <kuroko/util.h>\n\nstatic struct menu_bar menu_bar = {0};\nstatic struct menu_bar_entries menu_entries[] = {\n\t{\"File\", \"file\"},\n\t{\"Help\", \"help\"},\n\t{NULL, NULL},\n};\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\n\nstatic int32_t width = 600;\nstatic int32_t height = 240;\n\nstatic char * title_str = \"Calculator\";\n\nstatic int textInputIsAccumulatorValue = 0;\nstatic char accumulator[1024] = {0};\nstatic char textInput[1024] = {0};\n\nstruct CalculatorButton {\n\tstruct TTKButton ttkButton;\n\tchar * label;\n\tvoid (*onClick)(struct CalculatorButton *);\n};\n\nstatic void clear_result(void) {\n\tif (textInputIsAccumulatorValue) {\n\t\ttextInputIsAccumulatorValue = 0;\n\t\t*textInput = '\\0';\n\t\t*accumulator = '\\0';\n\t}\n}\n\nstatic void calc_numeric(char * text) {\n\tclear_result();\n\tstrcat(textInput, text);\n}\n\nstatic void calc_func(char * txt) {\n\tclear_result();\n\tstrcat(accumulator, textInput);\n\tstrcat(accumulator, txt);\n\t*textInput = '\\0';\n\ttextInputIsAccumulatorValue = 0;\n}\n\nstatic void calc_backspace(void) {\n\tif (textInputIsAccumulatorValue) {\n\t\tclear_result();\n\t} else if (!*textInput) {\n\t\tsize_t l = strlen(accumulator);\n\t\tif (l) {\n\t\t\taccumulator[l-1] = '\\0';\n\t\t}\n\t} else {\n\t\tsize_t l = strlen(textInput);\n\t\tif (l) {\n\t\t\ttextInput[l-1] = '\\0';\n\t\t}\n\t}\n}\n\nstatic void btn_numeric(struct CalculatorButton * self) { calc_numeric(self->label); }\nstatic void btn_func_div(struct CalculatorButton * self) { calc_func(\"/\"); }\nstatic void btn_func_mul(struct CalculatorButton * self) { calc_func(\"*\"); }\nstatic void btn_func_sub(struct CalculatorButton * self) { calc_func(\"-\"); }\nstatic void btn_func_add(struct CalculatorButton * self) { calc_func(\"+\"); }\nstatic void btn_func_pct(struct CalculatorButton * self) { calc_func(\"%\"); }\nstatic void btn_func_opr(struct CalculatorButton * self) { calc_func(\"(\"); }\nstatic void btn_func_cpr(struct CalculatorButton * self) { calc_func(\")\"); }\nstatic void btn_func_clr(struct CalculatorButton * self) {\n\tif (!*textInput) {\n\t\t*accumulator = '\\0';\n\t} else {\n\t\t*textInput = '\\0';\n\t}\n}\nstatic void btn_func_equ(struct CalculatorButton * self) {\n\tif (textInputIsAccumulatorValue) return;\n\tif (*textInput) {\n\t\tstrcat(accumulator, textInput);\n\t\t*textInput = '\\0';\n\t}\n\n\tKrkValue result = krk_interpret(accumulator, \"<stdin>\");\n\tif (!IS_NONE(result)) {\n\t\tkrk_attachNamedValue(&vm.builtins->fields, \"_\", result);\n\t\tkrk_push(result);\n\t\tkrk_push(krk_stringFromFormat(\"%R\", result));\n\t\tkrk_swap(1);\n\t\tkrk_pop();\n\t\tif (IS_STRING(krk_peek(0))) {\n\t\t\tsnprintf(textInput, 1024, \"%s\", AS_CSTRING(krk_peek(0)));\n\t\t}\n\t\tkrk_pop();\n\t} else if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) {\n\t\tstrcat(textInput, \"Error.\");\n\t} else {\n\t\tstrcat(textInput, \"*\");\n\t}\n\tkrk_resetStack();\n\n\ttextInputIsAccumulatorValue = 1;\n}\n\n#define N(n) {{0},#n,btn_numeric}\n#define F(n,func) {{0},n,btn_func_ ## func}\n\n#define BTN_ROWS 4\n#define BTN_COLS 5\n\nstruct CalculatorButton buttons[] = {\n\tN(7), N(8), N(9),         F(\"÷\",div), F(\"(\",opr),\n\tN(4), N(5), N(6),         F(\"×\",mul), F(\")\",cpr),\n\tN(1), N(2), N(3),         F(\"-\",sub), F(\"C\",clr),\n\tN(0), N(.), F(\"mod\",pct), F(\"+\",add), F(\"=\",equ),\n};\n\nstatic void redraw(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\tdraw_fill(ctx, rgb(204,204,204));\n\n\tdraw_rectangle_solid(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT + 4, window->width - bounds.width, 42, rgb(255,255,255));\n\n\tstruct MarkupState * renderer = markup_setup_renderer(ctx, bounds.left_width + 5, bounds.top_height + MENU_BAR_HEIGHT + 14, rgb(0,0,0), 0);\n\tmarkup_set_base_font_size(renderer, 10);\n\tmarkup_set_base_state(renderer, MARKUP_TEXT_STATE_MONO);\n\tmarkup_push_raw_string(renderer, accumulator);\n\tif (!textInputIsAccumulatorValue && !textInput[0]) markup_push_raw_string(renderer, \"_\");\n\tmarkup_finish_renderer(renderer);\n\n\trenderer = markup_setup_renderer(ctx, bounds.left_width + 5, bounds.top_height + MENU_BAR_HEIGHT + 35, rgb(0,0,0), 0);\n\tmarkup_set_base_font_size(renderer, 16);\n\tmarkup_set_base_state(renderer, (textInputIsAccumulatorValue ? MARKUP_TEXT_STATE_BOLD : 0) | MARKUP_TEXT_STATE_MONO);\n\tmarkup_push_raw_string(renderer, textInput);\n\tif (!textInputIsAccumulatorValue && textInput[0]) markup_push_raw_string(renderer, \"_\");\n\tmarkup_finish_renderer(renderer);\n\n\tfor (int i = 0; i < (BTN_ROWS * BTN_COLS); ++i) {\n\t\tttk_button_draw(ctx, &buttons[i].ttkButton);\n\t}\n\n\tmenu_bar_render(&menu_bar, ctx);\n\n\trender_decorations(window, ctx, title_str);\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nstatic void redraw_window_callback(struct menu_bar * self) {\n\t(void)self;\n\tredraw();\n}\n\nint in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {\n\tif (me->new_y >= button->y && me->new_y < button->y  + button->height) {\n\t\tif (me->new_x >= button->x && me->new_x < button->x + button->width) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\n#define BASE_TOP 50\n\nvoid setup_buttons(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tmenu_bar.x = bounds.left_width;\n\tmenu_bar.y = bounds.top_height;\n\tmenu_bar.width = ctx->width - bounds.width;\n\tmenu_bar.window = window;\n\n\tsize_t ind = 0;\n\n\tint aWidth     = ctx->width - bounds.width - 10;\n\tint baseWidth  = aWidth / BTN_COLS;\n\tint extraWidth = aWidth - baseWidth * BTN_COLS;\n\n\tint aHeight     = ctx->height - bounds.height - 10 - MENU_BAR_HEIGHT - BASE_TOP;\n\tint baseHeight  = aHeight / BTN_ROWS;\n\tint extraHeight = aHeight - baseHeight * BTN_ROWS;\n\n\tfor (int row = 0; row < BTN_ROWS; ++row) {\n\t\tfor (int col = 0; col < BTN_COLS; ++col, ++ind) {\n\t\t\tbuttons[ind].ttkButton.title  = buttons[ind].label;\n\t\t\tbuttons[ind].ttkButton.width  = ((col + 1 < BTN_COLS) ? baseWidth : (baseWidth + extraWidth)) - 5;\n\t\t\tbuttons[ind].ttkButton.height = ((row + 1 < BTN_ROWS) ? baseHeight : (baseHeight + extraHeight)) - 5;\n\t\t\tbuttons[ind].ttkButton.x = 5 + bounds.left_width + baseWidth * col;\n\t\t\tbuttons[ind].ttkButton.y = MENU_BAR_HEIGHT + BASE_TOP + 5 + bounds.top_height + baseHeight * row;\n\t\t}\n\t}\n\n}\n\nvoid resize_finish(int w, int h) {\n\tif (w < 300 || h < 240) {\n\t\tyutani_window_resize_offer(yctx, window, w < 300 ? 300 : w, h < 240 ? 240 : h);\n\t\treturn;\n\t}\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\twidth  = w;\n\theight = h;\n\tsetup_buttons();\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n}\n\nstatic void clear_highlights(int *changed) {\n\tfor (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) {\n\t\tif (buttons[i].ttkButton.hilight) {\n\t\t\t*changed = 1;\n\t\t\tbuttons[i].ttkButton.hilight = 0;\n\t\t}\n\t}\n}\n\nvoid set_hilight(struct TTKButton * button, int hilight) {\n\tint changed = 0;\n\tif (!button) {\n\t\tclear_highlights(&changed);\n\t} else if (button && (button->hilight != hilight)) {\n\t\tchanged = 1;\n\t\tclear_highlights(&changed);\n\t\tbutton->hilight = hilight;\n\t}\n\tif (changed) redraw();\n}\n\nstatic void update_buttons(struct yutani_msg_window_mouse_event * me, int hilight) {\n\tstruct TTKButton * inButton = NULL;\n\tfor (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) {\n\t\tif (in_button(&buttons[i].ttkButton, me)) {\n\t\t\tinButton = &buttons[i].ttkButton;\n\t\t\tbreak;\n\t\t}\n\t}\n\tset_hilight(inButton, inButton ? hilight : 0);\n}\n\nstatic void _menu_action_exit(struct MenuEntry * entry) {\n\texit(0);\n}\n\nstatic void _menu_action_help(struct MenuEntry * entry) {\n\tsystem(\"help-browser calculator.trt &\");\n\tredraw();\n}\n\nstatic void _menu_action_about(struct MenuEntry * entry) {\n\t/* Show About dialog */\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About Calculator\\\" /usr/share/icons/48/calculator.png \\\"Calculator\\\" \\\"© 2021-2022 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)window->x + (int)window->width / 2, (int)window->y + (int)window->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\tredraw();\n}\n\nint main(int argc, char * argv[]) {\n\tint req_center_x, req_center_y;\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\tmarkup_text_init();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\twindow = yutani_window_create(yctx, width + bounds.width, height + bounds.height);\n\treq_center_x = yctx->display_width / 2;\n\treq_center_y = yctx->display_height / 2;\n\n\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\n\tyutani_window_advertise_icon(yctx, window, title_str, \"calculator\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\tmenu_bar.entries = menu_entries;\n\tmenu_bar.redraw_callback = redraw_window_callback;\n\tmenu_bar.set = menu_set_create();\n\n\tstruct MenuList * m = menu_create(); /* File */\n\tmenu_insert(m, menu_create_normal(\"exit\",NULL,\"Exit\", _menu_action_exit));\n\tmenu_set_insert(menu_bar.set, \"file\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(\"help\",NULL,\"Contents\",_menu_action_help));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"star\",NULL,\"About Calculator\",_menu_action_about));\n\tmenu_set_insert(menu_bar.set, \"help\", m);\n\n\tsetup_buttons();\n\tredraw();\n\n\tstruct TTKButton * _down_button = NULL;\n\n\tvm.binpath = strdup(\"/bin/calculator\"); /* Just assume this so we can get module imports */\n\tkrk_initVM(KRK_GLOBAL_CLEAN_OUTPUT);\n\tkrk_startModule(\"__main__\");\n\n\tint playing = 1;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)ke->wid);\n\t\t\t\t\t\tif (win == window) {\n\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\t\t\tif (ke->event.key == '\\n') btn_func_equ(NULL);\n\t\t\t\t\t\t\t\telse if ((ke->event.key >= '0' && ke->event.key <= '9') || ke->event.key == '.') {\n\t\t\t\t\t\t\t\t\tchar tmp[2] = {ke->event.key, '\\0'};\n\t\t\t\t\t\t\t\t\tcalc_numeric(tmp);\n\t\t\t\t\t\t\t\t} else if ((ke->event.key == KEY_BACKSPACE)) {\n\t\t\t\t\t\t\t\t\tcalc_backspace();\n\t\t\t\t\t\t\t\t} else if ((ke->event.key)) {\n\t\t\t\t\t\t\t\t\tchar tmp[2] = {ke->event.key, '\\0'};\n\t\t\t\t\t\t\t\t\tcalc_func(tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tmenu_bar_mouse_event(yctx, window, &menu_bar, me, me->new_x, me->new_y);\n\n\t\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t\tdecor_get_bounds(window, &bounds);\n\t\t\t\t\t\t\tif (me->new_y > bounds.top_height) {\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\tfor (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) {\n\t\t\t\t\t\t\t\t\t\tif (in_button(&buttons[i].ttkButton, me)) {\n\t\t\t\t\t\t\t\t\t\t\tset_hilight(&buttons[i].ttkButton, 2);\n\t\t\t\t\t\t\t\t\t\t\t_down_button = &buttons[i].ttkButton;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\t\t\tif (_down_button) {\n\t\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\t\t((struct CalculatorButton*)_down_button)->onClick((struct CalculatorButton*)_down_button);\n\t\t\t\t\t\t\t\t\t\t\t_down_button->hilight = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t_down_button = NULL;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\t\tupdate_buttons(me, 1);\n\t\t\t\t\t\t\t\t} else if (_down_button) {\n\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(_down_button, 2);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL, 0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/cat-img.c",
    "content": "/**\n * @brief Display images in a Toaru terminal.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <getopt.h>\n#include <unistd.h>\n#include <sys/ioctl.h>\n#include <termios.h>\n\n#include <toaru/graphics.h>\n#include <toaru/termemu.h>\n\nvoid get_cell_sizes(int * w, int * h) {\n\tstruct winsize wsz;\n\tioctl(0, TIOCGWINSZ, &wsz);\n\n\tif (!wsz.ws_col || !wsz.ws_row) {\n\t\t*w = 0;\n\t\t*h = 0;\n\t}\n\n\t*w = wsz.ws_xpixel / wsz.ws_col;\n\t*h = wsz.ws_ypixel / wsz.ws_row;\n}\n\nvoid raw_output(void) {\n\tstruct termios new;\n\ttcgetattr(fileno(stdin), &new);\n\tnew.c_oflag &= (~ONLCR);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\nvoid unraw_output(void) {\n\tstruct termios new;\n\ttcgetattr(fileno(stdin), &new);\n\tnew.c_oflag |= ONLCR;\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\nint usage(char * argv[]) {\n\tprintf(\n\t\t\t\"usage: %s [-?ns] [path]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -n     \\033[3mdon't print a new line after image\\033[0m\\n\"\n\t\t\t\" -s     \\033[3mscale to cell height (up or down)\\033[0m\\n\"\n\t\t\t\" -w     \\033[3mscale to terminal width (up or down)\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nint main (int argc, char * argv[]) {\n\tif (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {\n\t\tfprintf(stderr, \"Can't cat-img to a non-terminal.\\n\");\n\t\texit(1);\n\t}\n\n\tint opt;\n\tint no_newline = 0;\n\tint scale_to_cell_height = 0;\n\tint scale_to_term_width = 0;\n\n\twhile ((opt = getopt(argc, argv, \"?nsw\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'n':\n\t\t\t\tno_newline = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\tscale_to_term_width = 1;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tscale_to_cell_height = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind >= argc ) {\n\t\treturn usage(argv);\n\t}\n\n\tint w, h;\n\tget_cell_sizes(&w, &h);\n\n\tif (!w || !h) return 1;\n\n\tint ret = 0;\n\n\twhile (optind < argc) {\n\t\tsprite_t * image = calloc(sizeof(sprite_t),1);\n\t\tif (load_sprite(image, argv[optind])) {\n\t\t\tfree(image);\n\t\t\tret |= 1;\n\t\t\toptind++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tsprite_t * source = image;\n\n\t\tif (scale_to_cell_height) {\n\t\t\tint new_width = (h * image->width) / image->height;\n\t\t\tsource = create_sprite(new_width,h,1);\n\t\t\tgfx_context_t * g = init_graphics_sprite(source);\n\t\t\tdraw_fill(g, 0x00000000);\n\t\t\tdraw_sprite_scaled(g, image, 0, 0, new_width, h);\n\t\t\tsprite_free(image);\n\t\t}\n\n\t\tif (scale_to_term_width) {\n\t\t\tstruct winsize w;\n\t\t\tioctl(0, TIOCGWINSZ, &w);\n\t\t\tint new_height = (w.ws_xpixel * image->height) / image->width;\n\t\t\tsource = create_sprite(w.ws_xpixel, new_height, 1);\n\t\t\tgfx_context_t * g = init_graphics_sprite(source);\n\t\t\tdraw_fill(g, 0x00000000);\n\t\t\tdraw_sprite_scaled(g, image, 0, 0, w.ws_xpixel, new_height);\n\t\t\tsprite_free(image);\n\t\t}\n\n\t\tint width_in_cells = source->width / w;\n\t\tif (source->width % w) width_in_cells++;\n\n\t\tint height_in_cells = source->height / h;\n\t\tif (source->height % h) height_in_cells++;\n\n\t\traw_output();\n\t\tprintf(\"\\033[?25l\");\n\n\t\tfor (int y = 0; y < height_in_cells; y++) {\n\t\t\tfor (int x = 0; x < width_in_cells; x++) {\n\t\t\t\tprintf(\"\\033Ts\");\n\t\t\t\tuint32_t * tmp = malloc(sizeof(uint32_t) * w * h);\n\t\t\t\tfor (int yy = 0; yy < h; yy++) {\n\t\t\t\t\tfor (int xx = 0; xx < w; xx++) {\n\t\t\t\t\t\tif (x*w + xx >= source->width || y*h + yy >= source->height) {\n\t\t\t\t\t\t\ttmp[yy * w + xx] = rgba(0,0,0,TERM_DEFAULT_OPAC);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tuint32_t data = alpha_blend_rgba(\n\t\t\t\t\t\t\t\trgba(0,0,0,TERM_DEFAULT_OPAC),\n\t\t\t\t\t\t\t\tpremultiply(source->bitmap[(x*w+xx)+(y*h+yy)*source->width]));\n\t\t\t\t\t\t\ttmp[yy * w + xx] = data;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfwrite(tmp, sizeof(uint32_t) * w * h, 1, stdout);\n\t\t\t\tfree(tmp);\n\t\t\t\tfflush(stdout);\n\t\t\t}\n\t\t\tif (y != height_in_cells - 1 || !no_newline) {\n\t\t\t\tprintf(\"\\r\\n\");\n\t\t\t}\n\t\t}\n\n\t\tsprite_free(source);\n\n\t\tprintf(\"\\033[?25h\");\n\t\tunraw_output();\n\t\tfflush(stdout);\n\t\toptind++;\n\t}\n\n\treturn ret;\n}\n \n"
  },
  {
    "path": "apps/cat.c",
    "content": "/**\n * @brief cat - Concatenate files\n *\n * Concatenates files together to standard output.\n * In a supporting terminal, you can then pipe\n * standard out to another file or other useful\n * things like that.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/stat.h>\n\n#define CHUNK_SIZE 4096\n\nstatic char * _argv_0;\nstatic char * _file;\n\nvoid doit(int fd) {\n\twhile (1) {\n\t\tchar buf[CHUNK_SIZE];\n\t\tmemset(buf, 0, CHUNK_SIZE);\n\t\tssize_t r = read(fd, buf, CHUNK_SIZE);\n\t\tif (!r) return;\n\t\tif (r < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", _argv_0, _file, strerror(errno));\n\t\t\treturn;\n\t\t}\n\t\twrite(STDOUT_FILENO, buf, r);\n\t}\n}\n\nint main(int argc, char ** argv) {\n\tint ret = 0;\n\n\t_argv_0 = argv[0];\n\n\tif (argc == 1) {\n\t\t_file = \"stdin\";\n\t\tdoit(0);\n\t}\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (!strcmp(argv[i],\"-\")) {\n\t\t\t_file = \"stdin\";\n\t\t\tdoit(0);\n\t\t\tcontinue;\n\t\t}\n\t\t_file = argv[i];\n\t\tint fd = open(argv[i], O_RDONLY);\n\t\tif (fd < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tret = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tstruct stat _stat;\n\t\tfstat(fd, &_stat);\n\n\t\tif (S_ISDIR(_stat.st_mode)) {\n\t\t\tfprintf(stderr, \"%s: %s: Is a directory\\n\", argv[0], argv[i]);\n\t\t\tclose(fd);\n\t\t\tret = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tdoit(fd);\n\n\t\tclose(fd);\n\t}\n\n\treturn ret;\n}\n\n"
  },
  {
    "path": "apps/chmod.c",
    "content": "/**\n * @brief chmod - change file permissions\n *\n * This implementation is likely non-compliant, though it does\n * attempt to look similar to the standard POSIX syntax,\n * supporting both octal mode setings and +/-rwx flavors.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <ctype.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <string.h>\n#include <sys/stat.h>\n\nenum mode_set {\n\tMODE_SET,\n\tMODE_ADD,\n\tMODE_REMOVE,\n};\n\nstatic int calc(int mode, int users) {\n\tint out = 0;\n\tif (users & 1) {\n\t\tout |= (mode << 6);\n\t}\n\tif (users & 2) {\n\t\tout |= (mode << 3);\n\t}\n\tif (users & 4) {\n\t\tout |= (mode << 0);\n\t}\n\treturn out;\n}\n\nint main(int argc, char * argv[]) {\n\tif (argc < 3) {\n\t\tfprintf(stderr, \"usage: %s OCTAL-MODE FILE...\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* Parse mode */\n\tint mode = 0;\n\tenum mode_set mode_set = MODE_SET;\n\tchar * c = argv[1];\n\tint user_modes = 0;\n\tint all_users = 7;\n\n\twhile (*c) {\n\t\tswitch (*c) {\n\t\t\tcase '0' ... '7':\n\t\t\t\twhile (*c >= '0' && *c <= '7') {\n\t\t\t\t\tmode *= 8;\n\t\t\t\t\tmode += (*c - '0');\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'u':\n\t\t\t\tall_users = 0;\n\t\t\t\tuser_modes |= 1;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\tall_users = 0;\n\t\t\t\tuser_modes |= 2;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\tall_users = 0;\n\t\t\t\tuser_modes |= 4;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'a':\n\t\t\t\tall_users = 7;\n\t\t\t\tuser_modes = 7;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase '-':\n\t\t\t\tmode_set = MODE_REMOVE;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase '+':\n\t\t\t\tmode_set = MODE_ADD;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase '=':\n\t\t\t\tmode_set = MODE_SET;\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tmode |= calc(S_IROTH, user_modes | all_users);\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\tmode |= calc(S_IWOTH, user_modes | all_users);\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tcase 'x':\n\t\t\t\tmode |= calc(S_IXOTH, user_modes | all_users);\n\t\t\t\tc++;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"%s: invalid mode '%s'\\n\", argv[0], argv[1]);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tint out = 0;\n\tfor (int i = 2; i < argc; ++i) {\n\t\tint actual_mode = 0;\n\t\tstruct stat _stat;\n\t\tif (stat(argv[i], &_stat) < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tout |= 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (mode_set) {\n\t\t\tcase MODE_SET:\n\t\t\t\tactual_mode = mode;\n\t\t\t\tbreak;\n\t\t\tcase MODE_ADD:\n\t\t\t\tactual_mode = (_stat.st_mode & 07777) | mode;\n\t\t\t\tbreak;\n\t\t\tcase MODE_REMOVE:\n\t\t\t\tactual_mode = (_stat.st_mode & 07777) & ~(mode);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (chmod(argv[i], actual_mode) < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tout |= 1;\n\t\t\tcontinue;\n\t\t}\n\t}\n\n\treturn out;\n}\n"
  },
  {
    "path": "apps/chown.c",
    "content": "/**\n * @brief chown - bad implementation thereof\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <pwd.h>\n\nstatic int usage(char * argv[]) {\n\tfprintf(stderr, \"usage: %s [OWNER][:[GROUP]] FILE...\\n\", argv[0]);\n\treturn 1;\n}\n\nstatic int invalid(char * argv[], char c) {\n\tfprintf(stderr, \"%s: %c: unrecognized option\\n\", argv[0], c);\n\treturn 1;\n}\n\nstatic int parse_user_group(char * argv[], char * arg, uid_t * user, gid_t * group) {\n\n\t/* Does this look like a number? */\n\tif (*arg >= '0' && *arg <= '9') {\n\t\t/* Try to extract */\n\t\tchar * endptr;\n\t\tunsigned long int number = strtoul(arg, &endptr, 10);\n\t\tif (*endptr != ':' && *endptr != '\\0') {\n\t\t\tfprintf(stderr, \"%s: %s: Invalid user/group specification\\n\", argv[0], arg);\n\t\t}\n\t\t*user = number;\n\t\targ = endptr;\n\t} else if (*arg == ':') {\n\t\t*user = -1;\n\t\targ++;\n\t} else {\n\t\tchar * colon = strstr(arg, \":\");\n\t\tif (colon) {\n\t\t\t*colon = '\\0';\n\t\t}\n\t\t/* Check name */\n\t\tstruct passwd * userEnt = getpwnam(arg);\n\t\tif (!userEnt) {\n\t\t\tfprintf(stderr, \"%s: %s: Invalid user\\n\", argv[0], arg);\n\t\t\treturn 1;\n\t\t}\n\t\t*user = userEnt->pw_uid;\n\t\tif (colon) {\n\t\t\targ = colon + 1;\n\t\t} else {\n\t\t\targ = NULL;\n\t\t}\n\t}\n\n\tif (arg && *arg) {\n\t\tif (*arg >= '0' && *arg <= '9') {\n\t\t\tchar * endptr;\n\t\t\tunsigned long int number = strtoul(arg, &endptr, 10);\n\t\t\tif (*endptr != '\\0') {\n\t\t\t\tfprintf(stderr, \"%s: %s: Invalid group specification\\n\", argv[0], arg);\n\t\t\t}\n\t\t\t*group = number;\n\t\t\targ = endptr;\n\t\t} else {\n\t\t\tstruct passwd * userEnt = getpwnam(arg);\n\t\t\tif (!userEnt) {\n\t\t\t\tfprintf(stderr, \"%s: %s: Invalid group\\n\", argv[0], arg);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\t*group = userEnt->pw_uid;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\n\tint i = 1;\n\tfor (; i < argc; i++) {\n\t\tif (argv[i][0] != '-') break;\n\n\t\tswitch (argv[i][0]) {\n\t\t\tcase 'h':\n\t\t\t\treturn usage(argv);\n\t\t\tdefault:\n\t\t\t\treturn invalid(argv,argv[i][0]);\n\t\t}\n\t}\n\n\tif (i + 1 >= argc) return usage(argv);\n\n\tuid_t user = -1;\n\tuid_t group = -1;\n\n\tif (parse_user_group(argv, argv[i++], &user, &group)) return 1;\n\tif (user == -1 && group == -1) return 0;\n\n\tint retval = 0;\n\n\tfor (; i < argc; i++) {\n\t\tif (chown(argv[i], user, group)) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tretval = 1;\n\t\t}\n\t}\n\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/clear.c",
    "content": "/**\n * @brief clear - Clear the terminal\n *\n * Sends an escape code to clear the screen. Ideally, this should\n * come from a database of terminal escape codes (eg. terminfo),\n * but we don't have one of those yet, so just send a code that\n * makes sense for a lot of terminals.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013 K. Lange\n */\n#include <stdio.h>\n\nint main(int argc, char ** argv) {\n\tprintf(\"\\033[H\\033[2J\");\n\tfflush(stdout);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/cmp.c",
    "content": "/**\n * @brief Compare files\n *\n * Standard POSIX utility.\n *\n * XXX Only use this with normal files; errors in special files\n *     will not be handled well with our current libc.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2025 K. Lange\n */\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n\nstatic int usage(char * argv[]) {\n\tfprintf(stderr, \"usage: %s [-l | -s] file1 file2\\n\", argv[0]);\n\treturn 2;\n}\n\nint main(int argc, char * argv[]) {\n\tint c;\n\tint format = 0;\n\tint retval = 0;\n\twhile ((c = getopt(argc, argv, \"ls\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'l':\n\t\t\t\tformat = 'l';\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tformat = 's';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn usage(argv);\n\t\t}\n\t}\n\n\tif (optind + 1 >= argc) return usage(argv);\n\n\tconst char * file_a = argv[optind];\n\tconst char * file_b = argv[optind+1];\n\tFILE * a;\n\tFILE * b;\n\n\n\tif (!strcmp(file_a, \"-\")) {\n\t\ta = stdin;\n\t\tfile_a = \"stdin\";\n\t} else {\n\t\ta = fopen(file_a, \"r\");\n\t\tif (!a) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], file_a, strerror(errno));\n\t\t\treturn 2;\n\t\t}\n\t}\n\n\tif (!strcmp(file_b, \"-\")) {\n\t\tb = stdin;\n\t\tfile_b = \"stdin\";\n\t} else {\n\t\tb = fopen(file_b, \"r\");\n\t\tif (!b) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], file_b, strerror(errno));\n\t\t\tfclose(a);\n\t\t\treturn 2;\n\t\t}\n\t}\n\n\tif (a == stdin && b == stdin) {\n\t\tfprintf(stderr, \"stdin may only be specified for one argument\\n\");\n\t\treturn 2;\n\t}\n\n\tsize_t count = 1;\n\tsize_t line = 1;\n\n\twhile (!feof(a) && !feof(b)) {\n\t\tint _a = fgetc(a);\n\t\tif (_a < 0 && ferror(a)) { fprintf(stderr, \"%s: %s: %s\\n\", argv[0], file_a, strerror(errno)); retval = 2; goto finish; }\n\t\tint _b = fgetc(b);\n\t\tif (_b < 0 && ferror(b)) { fprintf(stderr, \"%s: %s: %s\\n\", argv[0], file_b, strerror(errno)); retval = 2; goto finish; }\n\n\t\tif (_a != _b) {\n\t\t\tif (_a == EOF || _b == EOF) {\n\t\t\t\tretval = 1;\n\t\t\t\tif (format != 's') fprintf(stderr, \"%s: EOF on %s\\n\", argv[0], _a == EOF ? file_a : file_b);\n\t\t\t\tgoto finish;\n\t\t\t}\n\t\t\tswitch (format) {\n\t\t\t\tcase 0:\n\t\t\t\t\tfprintf(stdout, \"%s %s differ: char %zu, line %zu\\n\", file_a, file_b, count, line);\n\t\t\t\t\t/* fallthrough */\n\t\t\t\tcase 's':\n\t\t\t\t\tretval = 1;\n\t\t\t\t\tgoto finish;\n\t\t\t\tcase 'l':\n\t\t\t\t\tfprintf(stdout, \"%zu %o %o\\n\", count, _a, _b);\n\t\t\t\t\tretval = 1;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t}\n\n\t\tcount += 1;\n\t\tif (_a == '\\n') line += 1;\n\t}\n\nfinish:\n\tif (a != stdin) fclose(a);\n\tif (b != stdin) fclose(b);\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/color-picker.c",
    "content": "/**\n * @file apps/color-picker.c\n * @brief Color picker\n *\n * Color Picker widget demo, eventually maybe a paint app again...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <math.h>\n#include <wait.h>\n#include <sched.h>\n#include <signal.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/spinlock.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n\n#define dist(a,b,c,d) sqrt((double)(((a) - (c)) * ((a) - (c)) + ((b) - (d)) * ((b) - (d))))\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic int should_exit = 0;\n\nuint16_t win_width;\nuint16_t win_height;\n\nuint16_t off_x;\nuint16_t off_y;\n\nstatic int needs_redraw = 0;\n\ngfx_context_t * ctx;\nstatic struct TT_Font * tt_font_thin = NULL;\n\nvoid redraw_borders() {\n\trender_decorations(wina, ctx, \"Color Picker\");\n}\n\ndouble fmin(double a, double b) {\n\treturn a < b ? a : b;\n}\n\ndouble fmax(double a, double b) {\n\treturn a > b ? a : b;\n}\n\nuint32_t hsv_to_rgb(float h, float s, float v) {\n\tfloat c  = v * s;\n\tfloat hp = fmod(h, 2 * M_PI);\n\tfloat x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0));\n\tfloat m = v - c;\n\tfloat rp, gp, bp;\n\tif (hp <= 1.0472)      { rp = c; gp = x; bp = 0; }\n\telse if (hp <= 2.0944) { rp = x; gp = c; bp = 0; }\n\telse if (hp <= 3.1416) { rp = 0; gp = c; bp = x; }\n\telse if (hp <= 4.1888) { rp = 0; gp = x; bp = c; }\n\telse if (hp <= 5.2360) { rp = x; gp = 0; bp = c; }\n\telse                   { rp = c; gp = 0; bp = x; }\n\treturn rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255);\n}\n\nvoid rgb_to_hsv(uint32_t c, double *h, double *s, double *v) {\n\tfloat r = _RED(c) / 255.0;\n\tfloat g = _GRE(c) / 255.0;\n\tfloat b = _BLU(c) / 255.0;\n\n\tfloat c_max = fmax(r,fmax(g,b));\n\tfloat c_min = fmin(r,fmin(g,b));\n\n\tfloat delta = c_max - c_min;\n\n\tif (!delta) {\n\t\t*h = 0;\n\t} else if (c_max == r) {\n\t\t*h = 1.0471975512 * fmod((g - b) / delta, 6.0);\n\t} else if (c_max == g) {\n\t\t*h = 1.0471975512 * ((b - r) / delta + 2.0);\n\t} else {\n\t\t*h = 1.0471975512 * ((r - g) / delta + 4.0);\n\t}\n\n\tif (c_max == 0) {\n\t\t*s = 0;\n\t} else {\n\t\t*s = delta / c_max;\n\t}\n\n\t*v = c_max;\n}\n\nstruct Picker {\n\tint x;\n\tint y;\n\tint radius;\n\tstruct gfx_point red;\n\tstruct gfx_point white;\n\tstruct gfx_point black;\n\tdouble dp;\n\tdouble hue;\n};\n\nstatic double pt_sign(const struct gfx_point *p1, const struct gfx_point *p2, const struct gfx_point *p3) {\n\treturn (p1->x - p3->x) * (p2->y - p3->y) - (p2->x - p3->x) * (p1->y - p3->y);\n}\n\nstatic int in_triangle(const struct gfx_point * pt, const struct gfx_point * v1, const struct gfx_point * v2, const struct gfx_point * v3, double *proximity) {\n\tdouble d1 = pt_sign(pt,v1,v2);\n\tdouble d2 = pt_sign(pt,v2,v3);\n\tdouble d3 = pt_sign(pt,v3,v1);\n\tint neg = (d1 < 0) || (d2 < 0) || (d3 < 0);\n\tint pos = (d1 > 0) || (d2 > 0) || (d3 > 0);\n\n\t*proximity = 1.0;\n\t*proximity = fmin(*proximity, gfx_line_distance(pt, v1, v2));\n\t*proximity = fmin(*proximity, gfx_line_distance(pt, v2, v3));\n\t*proximity = fmin(*proximity, gfx_line_distance(pt, v3, v1));\n\n\treturn !(neg && pos);\n}\n\nstatic uint32_t gfx_fill_magic(int32_t x, int32_t y, double alpha, void * extra) {\n\tif (alpha > 1.0) alpha = 1.0;\n\tif (alpha < 0.0) alpha = 0.0;\n\n\tstruct Picker * picker = extra;\n\n\t/* Picker center */\n\tint _x = picker->x + picker->radius;\n\tint _y = picker->y + picker->radius;\n\n\t/* hole in the middle */\n\tdouble r = dist(x,y,_x,_y);\n\tint inner_radius = picker->radius * 0.8;\n\n\tuint32_t c;\n\n\tif (r < inner_radius) {\n\t\tstruct gfx_point p = {(float)x,(float)y};\n\n\t\t/* Are we in the triangle? */\n\t\tdouble proximity;\n\t\tif (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) {\n\t\t\treturn rgba(0,0,0,0);\n\t\t}\n\n\t\talpha = proximity;\n\n\t\tdouble h = picker->hue;\n\t\tdouble v = 1.0 - (gfx_line_distance(&p, &picker->red, &picker->white) / picker->dp);\n\t\tdouble _l = gfx_line_distance(&p, &picker->black, &picker->white);\n\t\tdouble _h = gfx_line_distance(&p, &picker->black, &picker->red);\n\t\tdouble s = _l + _h > 0.0 ? (_l / (_l+_h)) : 1.0;\n\t\tc = hsv_to_rgb(h,s,v);\n\t} else {\n\t\tdouble angle = atan2(y-_y,_x-x) + M_PI;\n\n\t\tif (r < inner_radius + 1) {\n\t\t\talpha *= r - inner_radius;\n\t\t}\n\n\t\tc = hsv_to_rgb(angle, 1.0, 1.0);\n\t\t\n\t}\n\n\n\treturn premultiply(rgba(_RED(c),_GRE(c),_BLU(c),(int)(255 * alpha)));\n}\n\nstatic void fill_picker(struct Picker *picker) {\n\t/* Triangle stuff */\n\tpicker->red.x   = 0.8 * picker->radius * cos(-picker->hue) + picker->x + picker->radius;\n\tpicker->red.y   = 0.8 * picker->radius * sin(-picker->hue) + picker->y + picker->radius;\n\tpicker->white.x = 0.8 * picker->radius * cos(-picker->hue + 2.09439510239) + picker->x + picker->radius;\n\tpicker->white.y = 0.8 * picker->radius * sin(-picker->hue + 2.09439510239) + picker->y + picker->radius;\n\tpicker->black.x = 0.8 * picker->radius * cos(-picker->hue + 4.18879020479) + picker->x + picker->radius;\n\tpicker->black.y = 0.8 * picker->radius * sin(-picker->hue + 4.18879020479) + picker->y + picker->radius;\n\n\t/* Midpoint */\n\tstruct gfx_point midpoint = {(picker->white.x + picker->black.x) / 2.0, (picker->white.y + picker->black.y) / 2.0};\n\tpicker->dp = gfx_point_distance(&picker->red, &midpoint);\n}\n\nstatic double _hue = 0;\nstatic double _sat = 1.0;\nstatic double _val = 1.0;\nstatic uint32_t my_color = 0xFFFF0000;\n\nstatic void draw_ring(gfx_context_t * ctx, double x, double y, double radius, double thickness, uint32_t c) {\n\tstruct gfx_point p = {x,y};\n\tfor (int _y = y - radius - thickness; _y <= y + radius + thickness; ++_y) {\n\t\tif (_y < 0) continue;\n\t\tif (_y >= ctx->height) break;\n\t\tfor (int _x = x - radius - thickness; _x <= x + radius + thickness; ++_x) {\n\t\t\tif (_x < 0) continue;\n\t\t\tif (_x >= ctx->width) break;\n\n\t\t\tstruct gfx_point pt = {_x,_y};\n\n\t\t\tdouble dist = gfx_point_distance(&p,&pt);\n\t\t\tif (dist > radius - thickness && dist < radius + thickness) {\n\t\t\t\tdouble alpha = fmin(1.0,thickness - fabs(radius - dist));\n\t\t\t\tGFX(ctx,_x,_y) = alpha_blend_rgba(GFX(ctx,_x,_y), premultiply(rgba(_RED(c),_GRE(c),_BLU(c),_ALP(c)*alpha)));\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void redraw_everything(void) {\n\tdraw_fill(ctx, rgb(200,200,200));\n\n\tstruct Picker picker = {off_x, off_y, win_width / 2, {0,0}, {0,0}, {0,0}, 0, _hue};\n\tfill_picker(&picker);\n\tdraw_rounded_rectangle_pattern(ctx,picker.x,picker.y,picker.radius*2,picker.radius*2,picker.radius,gfx_fill_magic, &picker);\n\n\t/* Now figure out where the s + v goes */\n\n\tdouble x = picker.white.x * (1.0 - _sat) + picker.red.x * (_sat);\n\tdouble y = picker.white.y * (1.0 - _sat) + picker.red.y * (_sat);\n\n\tx = x * (_val) + picker.black.x * (1.0 - _val);\n\ty = y * (_val) + picker.black.y * (1.0 - _val);\n\n\tdraw_ring(ctx, x, y, 5, 1.5, _val < 0.5 ? rgb(255,255,255) : rgb(0,0,0));\n\n\tdraw_rounded_rectangle(ctx,off_x + 5, off_y + picker.radius * 2 + 5, 15, 15, 5, my_color);\n\n\tchar colorName[11];\n\tsprintf(colorName,\"#%02x%02x%02x\", _RED(my_color), _GRE(my_color), _BLU(my_color));\n\n\ttt_set_size(tt_font_thin, 13);\n\ttt_draw_string(ctx, tt_font_thin, off_x + 25, off_y + picker.radius * 2 + 18, colorName, rgb(0,0,0));\n\n\tredraw_borders();\n\tflip(ctx);\n\tyutani_flip(yctx, wina);\n}\n\nstatic double clamp_to_line(struct gfx_point *p, const struct gfx_point *v, const struct gfx_point *w, struct gfx_point *v_t) {\n\tfloat lengthlength = gfx_point_distance_squared(v,w);\n\tstruct gfx_point p_v = gfx_point_sub(p,v);\n\tstruct gfx_point w_v = gfx_point_sub(w,v);\n\tfloat tmp = gfx_point_dot(&p_v,&w_v) / lengthlength;\n\ttmp = fmin(1.0,tmp);\n\tfloat t = fmax(0.0, tmp);\n\tw_v.x *= t;\n\tw_v.y *= t;\n\t*v_t= gfx_point_add(v, &w_v);\n\treturn gfx_point_distance(p, v_t);\n}\n\nstatic int inside_circle = -1;\nstatic void handle_mouse(struct yutani_msg_window_mouse_event * me) {\n\n\tif (me->command != YUTANI_MOUSE_EVENT_DOWN &&\n\t\tme->command != YUTANI_MOUSE_EVENT_DRAG) return;\n\n\t/* Can we figure out a hue? */\n\tint32_t x = me->new_x;\n\tint32_t y = me->new_y;\n\tstruct Picker _picker = {off_x, off_y, win_width / 2, {0,0}, {0,0}, {0,0}, 0, _hue};\n\tstruct Picker * picker = &_picker;\n\tfill_picker(&_picker);\n\n\t/* Picker center */\n\tint _x = picker->x + picker->radius;\n\tint _y = picker->y + picker->radius;\n\n\t/* hole in the middle */\n\tdouble r = dist(x,y,_x,_y);\n\tint inner_radius = picker->radius * 0.8;\n\n\tstruct gfx_point p = {(float)x,(float)y};\n\n\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\tif (r > picker->radius) {\n\t\t\tinside_circle = -1;\n\t\t\treturn;\n\t\t}\n\n\t\tinside_circle = r < inner_radius;\n\n\t\tif (inside_circle) {\n\t\t\tdouble proximity;\n\t\t\tif (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) {\n\t\t\t\tinside_circle = -1;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (inside_circle == 1) {\n\t\tdouble proximity;\n\t\tif (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) {\n\t\t\tstruct gfx_point a;\n\t\t\tstruct gfx_point b;\n\t\t\tstruct gfx_point c;\n\t\t\tdouble a_d = clamp_to_line(&p,&picker->red,&picker->white,&a);\n\t\t\tdouble b_d = clamp_to_line(&p,&picker->black,&picker->white,&b);\n\t\t\tdouble c_d = clamp_to_line(&p,&picker->black,&picker->red,&c);\n\n\t\t\tif (a_d <= b_d && a_d <= c_d) {\n\t\t\t\tp = a;\n\t\t\t} else if (b_d <= a_d && b_d <= c_d) {\n\t\t\t\tp = b;\n\t\t\t} else if (c_d <= a_d && c_d <= b_d) {\n\t\t\t\tp = c;\n\t\t\t}\n\t\t}\n\n\t\tdouble v = 1.0 - (gfx_line_distance(&p, &picker->red, &picker->white) / picker->dp);\n\t\tdouble _l = gfx_line_distance(&p, &picker->black, &picker->white);\n\t\tdouble _h = gfx_line_distance(&p, &picker->black, &picker->red);\n\t\tdouble s = _l + _h > 0.0 ? (_l / (_l+_h)) : 1.0;\n\n\t\t_sat = s;\n\t\t_val = v;\n\t} else if (inside_circle == 0) {\n\t\t_hue = atan2(y-_y,_x-x) + M_PI;\n\t} else {\n\t\treturn;\n\t}\n\n\tmy_color = hsv_to_rgb(_hue,_sat,_val);\n\tneeds_redraw = 1;\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, wina, w, h);\n\treinit_graphics_yutani(ctx, wina);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\n\twin_width  = w - bounds.width;\n\twin_height = h - bounds.height;\n\toff_x = bounds.left_width;\n\toff_y = bounds.top_height;\n\n\tredraw_everything();\n\n\tyutani_window_resize_done(yctx, wina);\n}\n\nstatic uint32_t parseColor(const char * c) {\n\tif (*c != '#' || strlen(c) != 7) return rgba(0,0,0,255);\n\n\tchar r[3] = {c[1],c[2],'\\0'};\n\tchar g[3] = {c[3],c[4],'\\0'};\n\tchar b[3] = {c[5],c[6],'\\0'};\n\n\treturn rgba(strtoul(r,NULL,16),strtoul(g,NULL,16),strtoul(b,NULL,16),255);\n}\n\nint main (int argc, char ** argv) {\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tif (argc > 1) {\n\t\tmy_color = parseColor(argv[1]);\n\t\trgb_to_hsv(my_color, &_hue, &_sat, &_val);\n\t}\n\n\twin_width  = 160;\n\twin_height = 200;\n\n\ttt_font_thin = tt_font_from_shm(\"sans-serif\");\n\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\t/* Do something with a window */\n\twina = yutani_window_create(yctx, win_width + bounds.width, win_height + bounds.height);\n\tyutani_window_move(yctx, wina, 300, 300);\n\n\tdecor_get_bounds(wina, &bounds);\n\toff_x = bounds.left_width;\n\toff_y = bounds.top_height;\n\twin_width  = wina->width - bounds.width;\n\twin_height = wina->height - bounds.height;\n\n\tctx = init_graphics_yutani_double_buffer(wina);\n\n\tredraw_everything();\n\n\tyutani_window_advertise_icon(yctx, wina, \"Color Picker\", \"art\");\n\n\twhile (!should_exit) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tmenu_process_event(yctx, m);\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win && win == wina) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == wina->wid) {\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tswitch (decor_handle_event(yctx, m)) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\tdecor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (me->wid == wina->wid) {\n\t\t\t\t\t\t\thandle_mouse(me);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t\tif (needs_redraw) {\n\t\t\tredraw_everything();\n\t\t\tneeds_redraw = 0;\n\t\t}\n\t}\n\n\twait(NULL);\n\n\tyutani_close(yctx, wina);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/compositor.c",
    "content": "/**\n * @brief Yutani - The ToaruOS Window Compositor.\n *\n * Yutani is a canvas-based window compositor and manager.\n * It employs shared memory to provide clients access to\n * canvases in which they may render, while using a packet-based\n * socket interface to communicate actions between the server\n * and client such as keyboard activity, mouse movement, responses\n * to client events, etc., as well as to communicate requests from\n * the client to the server, such as creation of new windows,\n * movement, resizing, and display updates.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <signal.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <time.h>\n#include <math.h>\n#include <assert.h>\n#include <getopt.h>\n#include <errno.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/fswait.h>\n#include <sys/sysfunc.h>\n#include <sys/shm.h>\n#include <pthread.h>\n#include <dlfcn.h>\n\n#include <toaru/graphics.h>\n#include <toaru/mouse.h>\n#include <toaru/kbd.h>\n#include <toaru/pex.h>\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n#include <toaru/yutani-server.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n#include <toaru/text.h>\n\n#define _DEBUG_YUTANI\n#ifdef _DEBUG_YUTANI\n#include <toaru/trace.h>\n#define TRACE_APP_NAME \"yutani\"\n#else\n#define TRACE(msg,...)\n#endif\n\n/* Early definitions */\nstatic void mark_window(yutani_globals_t * yg, yutani_server_window_t * window);\nstatic void window_actually_close(yutani_globals_t * yg, yutani_server_window_t * w);\nstatic void notify_subscribers(yutani_globals_t * yg);\nstatic void mouse_stop_drag(yutani_globals_t * yg);\nstatic void window_move(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y);\nstatic yutani_server_window_t * top_at(yutani_globals_t * yg, uint16_t x, uint16_t y);\nstatic void window_unminimize(yutani_globals_t * yg, yutani_server_window_t * window);\nstatic void window_finish_minimize(yutani_globals_t * yg, yutani_server_window_t * w);\n\n#define ENABLE_BLUR_BEHIND\n#ifdef ENABLE_BLUR_BEHIND\n#define BLUR_CLIP_MAX 20\n#define BLUR_KERNEL   10\nstatic char * blur_texture = NULL;\nstatic gfx_context_t * blur_ctx = NULL;\nstatic gfx_context_t * clip_ctx = NULL;\n#endif\n\n/**\n * Print usage information.\n */\nstatic int usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"Yutani - Window Compositor\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-n [-g WxH]] [-h]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -n --nested     \\033[3mRun in a window.\\033[0m\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\"\n\t\t\t\" -g --geometry   \\033[3mSet the size of the server framebuffer.\\033[0m\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"  Yutani is the standard system compositor.\\n\"\n\t\t\t\"\\n\",\n\t\t\targv[0]);\n\treturn 1;\n}\n\n/**\n * Parse arguments\n */\nstatic int parse_args(int argc, char * argv[], int * out) {\n\tstatic struct option long_opts[] = {\n\t\t{\"nested\",     no_argument,       0, 'n'},\n\t\t{\"geometry\",   required_argument, 0, 'g'},\n\t\t{\"help\",       no_argument,       0, 'h'},\n\t\t{0,0,0,0}\n\t};\n\n\tint index, c;\n\twhile ((c = getopt_long(argc, argv, \"hg:n\", long_opts, &index)) != -1) {\n\t\tif (!c) {\n\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\tc = long_opts[index].val;\n\t\t\t}\n\t\t}\n\t\tswitch (c) {\n\t\t\tcase 'h':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'n':\n\t\t\t\tyutani_options.nested = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\t{\n\t\t\t\t\tchar * c = strstr(optarg, \"x\");\n\t\t\t\t\tif (c) {\n\t\t\t\t\t\t*c = '\\0';\n\t\t\t\t\t\tc++;\n\t\t\t\t\t\tyutani_options.nest_width  = atoi(optarg);\n\t\t\t\t\t\tyutani_options.nest_height = atoi(c);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"Unrecognized option: %c\\n\", c);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\t*out = optind;\n\treturn 0;\n}\n\nstatic int32_t min(int32_t a, int32_t b) {\n\treturn (a < b) ? a : b;\n}\n\nstatic int32_t max(int32_t a, int32_t b) {\n\treturn (a > b) ? a : b;\n}\n\nstatic int next_buf_id(void) {\n\tstatic int _next = 1;\n\treturn _next++;\n}\n\nstatic int next_wid(void) {\n\tstatic int _next = 1;\n\treturn _next++;\n}\n\nuint64_t yutani_current_time(yutani_globals_t * yg) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttime_t sec_diff = t.tv_sec - yg->start_time;\n\tsuseconds_t usec_diff = t.tv_usec - yg->start_subtime;\n\n\tif (t.tv_usec < yg->start_subtime) {\n\t\tsec_diff -= 1;\n\t\tusec_diff = (1000000 + t.tv_usec) - yg->start_subtime;\n\t}\n\n\treturn (uint64_t)(sec_diff * 1000 + usec_diff / 1000);\n}\n\nuint64_t yutani_time_since(yutani_globals_t * yg, uint64_t start_time) {\n\n\tuint64_t now = yutani_current_time(yg);\n\tuint64_t diff = now - start_time; /* Milliseconds */\n\n\treturn diff;\n}\n\n/**\n * Translate and transform coordinate from screen-relative to window-relative.\n */\nvoid yutani_device_to_window(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) {\n\tif (!window) {\n\t\t*out_x = 0;\n\t\t*out_y = 0;\n\t\treturn;\n\t}\n\t*out_x = x - window->x;\n\t*out_y = y - window->y;\n\n\tif (!window->rotation) return;\n\n\tdouble t_x = (double)*out_x - ((double)window->width / 2);\n\tdouble t_y = (double)*out_y - ((double)window->height / 2);\n\n\tdouble s = sin(-M_PI * (window->rotation/ 180.0));\n\tdouble c = cos(-M_PI * (window->rotation/ 180.0));\n\n\tdouble n_x = t_x * c - t_y * s;\n\tdouble n_y = t_x * s + t_y * c;\n\n\t*out_x = (int32_t)(n_x + ((double)window->width / 2));\n\t*out_y = (int32_t)(n_y + ((double)window->height / 2));\n}\n\n/**\n * Translate and transform coordinate from window-relative to screen-relative.\n */\nvoid yutani_window_to_device(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) {\n\n\tif (!window->rotation) {\n\t\t*out_x = window->x + x;\n\t\t*out_y = window->y + y;\n\t\treturn;\n\t}\n\n\tdouble t_x = (double)x - ((double)window->width / 2);\n\tdouble t_y = (double)y - ((double)window->height / 2);\n\n\tdouble s = sin((double)window->rotation * M_PI / 180.0);\n\tdouble c = cos((double)window->rotation * M_PI / 180.0);\n\n\tdouble n_x = t_x * c - t_y * s;\n\tdouble n_y = t_x * s + t_y * c;\n\n\t*out_x = (int32_t)(n_x + ((double)window->width / 2)  + (double)window->x);\n\t*out_y = (int32_t)(n_y + ((double)window->height / 2) + (double)window->y);\n}\n\nstatic list_t * window_zorder_owner(yutani_globals_t * yg, unsigned short index) {\n\tswitch (index) {\n\t\tcase YUTANI_ZORDER_BOTTOM:\n\t\tcase YUTANI_ZORDER_TOP:\n\t\t\treturn NULL;\n\t\tcase YUTANI_ZORDER_MENU:\n\t\t\treturn yg->menu_zs;\n\t\tcase YUTANI_ZORDER_OVERLAY:\n\t\t\treturn yg->overlay_zs;\n\t\tdefault:\n\t\t\treturn yg->mid_zs;\n\t}\n}\n\n/**\n * Remove a window from the z stack.\n */\nstatic void unorder_window(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tunsigned short index = w->z;\n\tw->z = -1;\n\tif (index == YUTANI_ZORDER_BOTTOM && yg->bottom_z == w) {\n\t\tyg->bottom_z = NULL;\n\t\treturn;\n\t}\n\tif (index == YUTANI_ZORDER_TOP && yg->top_z == w) {\n\t\tyg->top_z = NULL;\n\t\treturn;\n\t}\n\n\tlist_t * zorder_owner = window_zorder_owner(yg, index);\n\tif (!zorder_owner) return;\n\tnode_t * n = list_find(zorder_owner, w);\n\tif (!n) return;\n\tlist_delete(zorder_owner, n);\n\tfree(n);\n}\n\n/**\n * Move a window to a new stack order.\n */\nstatic void reorder_window(yutani_globals_t * yg, yutani_server_window_t * window, uint16_t new_zed) {\n\tif (!window) {\n\t\treturn;\n\t}\n\n\tunorder_window(yg, window);\n\n\twindow->z = new_zed;\n\n\tlist_t * zorder_owner = window_zorder_owner(yg, new_zed);\n\tif (zorder_owner) {\n\t\tlist_insert(zorder_owner, window);\n\t\treturn;\n\t}\n\n\tif (new_zed == YUTANI_ZORDER_TOP) {\n\t\tif (yg->top_z) {\n\t\t\tunorder_window(yg, yg->top_z);\n\t\t}\n\t\tyg->top_z = window;\n\t\treturn;\n\t}\n\n\tif (new_zed == YUTANI_ZORDER_BOTTOM) {\n\t\tif (yg->bottom_z) {\n\t\t\tunorder_window(yg, yg->bottom_z);\n\t\t}\n\t\tyg->bottom_z = window;\n\t\treturn;\n\t}\n}\n\n/**\n * Move a window to the top of if its z stack.\n */\nstatic void make_top(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tunsigned short index = w->z;\n\tlist_t * zorder_owner = window_zorder_owner(yg, index);\n\tif (!zorder_owner) return;\n\n\tnode_t * n = list_find(zorder_owner, w);\n\tif (!n) return; /* wat */\n\n\tlist_delete(zorder_owner, n);\n\tlist_append(zorder_owner, n);\n}\n\n/**\n * Set a window as the focused window.\n *\n * Currently, we only support one focused window.\n * In the future, we should support multiple windows as \"focused\" to account\n * for multiple \"seats\" on a single display.\n */\nstatic void set_focused_window(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tif (w == yg->focused_window) {\n\t\treturn; /* Already focused */\n\t}\n\n\tif (w && w->minimized) {\n\t\twindow_unminimize(yg,w);\n\t}\n\n\tif (yg->focused_window) {\n\t\t/* Send focus change to old focused window */\n\t\tyutani_msg_buildx_window_focus_change_alloc(response);\n\t\tyutani_msg_buildx_window_focus_change(response, yg->focused_window->wid, 0);\n\t\tpex_send(yg->server, yg->focused_window->owner, response->size, (char *)response);\n\t}\n\n\tif (!w) w = yg->bottom_z;\n\tyg->focused_window = w;\n\n\tif (w) {\n\t\t/* Send focus change to new focused window */\n\t\tyutani_msg_buildx_window_focus_change_alloc(response);\n\t\tyutani_msg_buildx_window_focus_change(response, w->wid, 1);\n\t\tpex_send(yg->server, w->owner, response->size, (char *)response);\n\t\tmake_top(yg, w);\n\t\tmark_window(yg, w);\n\t}\n\n\t/* Notify all subscribers of window changes */\n\tnotify_subscribers(yg);\n}\n\n/**\n * Get the focused window.\n *\n * In case there is no focused window, we return the bottom window.\n */\nstatic yutani_server_window_t * get_focused(yutani_globals_t * yg) {\n\tif (yg->focused_window) return yg->focused_window;\n\tif (yg->bottom_z) set_focused_window(yg, yg->bottom_z);\n\treturn yg->bottom_z;\n}\n\nstatic int yutani_pick_animation(uint32_t flags, int direction) {\n\tif (flags & YUTANI_WINDOW_FLAG_DIALOG_ANIMATION) {\n\t\treturn (direction == 0) ? YUTANI_EFFECT_SQUEEZE_IN : YUTANI_EFFECT_SQUEEZE_OUT;\n\t}\n\n\tif (flags & YUTANI_WINDOW_FLAG_NO_ANIMATION) {\n\t\treturn (direction == 0) ? YUTANI_EFFECT_NONE : YUTANI_EFFECT_DISAPPEAR;\n\t}\n\n\treturn (direction == 0) ? YUTANI_EFFECT_FADE_IN : YUTANI_EFFECT_FADE_OUT;\n}\n\n\n/**\n * Create a server window object.\n *\n * Initializes a window of the particular size for a given client.\n */\nstatic yutani_server_window_t * server_window_create(yutani_globals_t * yg, int width, int height, uintptr_t owner, uint32_t flags) {\n\tyutani_server_window_t * win = calloc(sizeof(yutani_server_window_t),1);\n\n\twin->wid = next_wid();\n\twin->owner = owner;\n\tlist_insert(yg->windows, win);\n\thashmap_set(yg->wids_to_windows, (void*)(uintptr_t)win->wid, win);\n\n\tlist_t * client_list = hashmap_get(yg->clients_to_windows, (void *)owner);\n\tlist_insert(client_list, win);\n\n\twin->x = 0;\n\twin->y = 0;\n\twin->z = 1;\n\twin->width = width;\n\twin->height = height;\n\twin->bufid = next_buf_id();\n\twin->rotation = 0;\n\twin->newbufid = 0;\n\twin->client_flags   = 0;\n\twin->client_icon = 0;\n\twin->client_length  = 0;\n\twin->client_strings = NULL;\n\twin->anim_mode = 0;\n\twin->anim_start = 0;\n\twin->alpha_threshold = 0;\n\twin->show_mouse = 1;\n\twin->tiled = 0;\n\twin->untiled_width = 0;\n\twin->untiled_height = 0;\n\twin->default_mouse = 1;\n\twin->server_flags = flags;\n\twin->opacity = 255;\n\twin->hidden = 1;\n\twin->minimized = 0;\n\n\tchar key[1024];\n\tYUTANI_SHMKEY(yg->server_ident, key, 1024, win);\n\n\tsize_t size = (width * height * 4);\n\n\twin->buffer = shm_obtain(key, &size);\n\tmemset(win->buffer, 0, size);\n\n\tlist_insert(yg->mid_zs, win);\n\n\treturn win;\n}\n\n/**\n * Update the shape threshold for a window.\n *\n * A shaping threshold is a byte representing the minimum\n * required alpha for a window to be considered \"solid\".\n * Eg., a value of 0 says all windows are solid, while a value of\n * 1 requires a window to have at least some opacity to it,\n * and a value of 255 requires fully opaque pixels.\n *\n * Not actually stored as a byte, so a value over 255 can be used.\n * This results in a window that passes through all clicks.\n */\nstatic void server_window_update_shape(yutani_globals_t * yg, yutani_server_window_t * window, int set) {\n\twindow->alpha_threshold = set;\n}\n\n/**\n * Start resizing a window.\n *\n * Resizing a multi-stage process.\n * The client and server agree on a size and the server prepares a buffer.\n * The client then needs to accept the resize, fill the buffer, and then\n * inform the server that it is ready, at which point we'll swap the\n * buffer we are rendering from.\n */\nstatic uint32_t server_window_resize(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) {\n\t/* A client has accepted our offer, let's make a buffer for them */\n\tif (win->newbufid) {\n\t\t/* Already in the middle of an accept/done, bail */\n\t\treturn win->newbufid;\n\t}\n\twin->newbufid = next_buf_id();\n\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, win->newbufid);\n\n\t\tsize_t size = (width * height * 4);\n\t\twin->newbuffer = shm_obtain(key, &size);\n\t}\n\n\treturn win->newbufid;\n}\n\n/**\n * Finish the resize process.\n *\n * We delete the unlink the old buffer and then swap the pointers\n * for the new buffer.\n */\nstatic void server_window_resize_finish(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) {\n\tif (!win->newbufid) {\n\t\treturn;\n\t}\n\n\tint oldbufid = win->bufid;\n\n\tmark_window(yg, win);\n\n\tif (yg->resizing_window == win) {\n\t\tyg->resize_release_time = 0;\n\t\tif (yg->mouse_state == YUTANI_MOUSE_STATE_NORMAL) {\n\t\t\tint32_t x, y;\n\t\t\tif (yg->resizing_window->rotation) {\n\t\t\t\t/* If the window is rotated, we need to move the center to be where the new center should be, but x/y are based on the unrotated upper left corner. */\n\t\t\t\t/* The center always moves by one-half the resize dimensions */\n\t\t\t\tint32_t center_x, center_y;\n\t\t\t\tyutani_server_window_t fake_window = {\n\t\t\t\t\t.width = yg->resizing_init_w,\n\t\t\t\t\t.height = yg->resizing_init_h,\n\t\t\t\t\t.x = yg->resizing_window->x,\n\t\t\t\t\t.y = yg->resizing_window->y,\n\t\t\t\t\t.rotation = yg->resizing_window->rotation,\n\t\t\t\t};\n\t\t\t\tyutani_window_to_device(&fake_window, yg->resizing_offset_x + yg->resizing_w / 2, yg->resizing_offset_y + yg->resizing_h / 2, &center_x, &center_y);\n\t\t\t\tx = center_x - yg->resizing_w / 2;\n\t\t\t\ty = center_y - yg->resizing_h / 2;\n\t\t\t} else {\n\t\t\t\tyutani_window_to_device(yg->resizing_window, yg->resizing_offset_x, yg->resizing_offset_y, &x, &y);\n\t\t\t}\n\t\t\tTRACE(\"resize complete, now %d x %d\", yg->resizing_w, yg->resizing_h);\n\t\t\twindow_move(yg, yg->resizing_window, x,y);\n\t\t\tyg->resizing_window = NULL;\n\t\t\tyg->mouse_window = NULL;\n\t\t}\n\t}\n\n\twin->width = width;\n\twin->height = height;\n\n\twin->bufid = win->newbufid;\n\twin->buffer = win->newbuffer;\n\n\twin->newbuffer = NULL;\n\twin->newbufid = 0;\n\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, oldbufid);\n\t\tshm_release(key);\n\t}\n\n\tmark_window(yg, win);\n}\n\n/**\n * Mark a screen region as damaged.\n */\nstatic void mark_screen(yutani_globals_t * yg, int32_t x, int32_t y, int32_t width, int32_t height) {\n\tyutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));\n\n\trect->x = x;\n\trect->y = y;\n\trect->width = width;\n\trect->height = height;\n\n\tlist_insert(yg->update_list, rect);\n}\n\n/**\n * Draw the cursor sprite.\n */\nstatic void draw_cursor(yutani_globals_t * yg, int x, int y, int cursor) {\n\tsprite_t * sprite = &yg->mouse_sprite;\n\tstatic sprite_t * previous = NULL;\n\tif (yg->resizing_window) {\n\t\tswitch (yg->resizing_direction) {\n\t\t\tcase SCALE_UP:\n\t\t\tcase SCALE_DOWN:\n\t\t\t\tsprite = &yg->mouse_sprite_resize_v;\n\t\t\t\tbreak;\n\t\t\tcase SCALE_LEFT:\n\t\t\tcase SCALE_RIGHT:\n\t\t\t\tsprite = &yg->mouse_sprite_resize_h;\n\t\t\t\tbreak;\n\t\t\tcase SCALE_DOWN_RIGHT:\n\t\t\tcase SCALE_UP_LEFT:\n\t\t\t\tsprite = &yg->mouse_sprite_resize_da;\n\t\t\t\tbreak;\n\t\t\tcase SCALE_DOWN_LEFT:\n\t\t\tcase SCALE_UP_RIGHT:\n\t\t\t\tsprite = &yg->mouse_sprite_resize_db;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t} else if (yg->mouse_state == YUTANI_MOUSE_STATE_MOVING) {\n\t\tsprite = &yg->mouse_sprite_drag;\n\t} else {\n\t\tswitch (cursor) {\n\t\t\tcase YUTANI_CURSOR_TYPE_DRAG:              sprite = &yg->mouse_sprite_drag; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_RESIZE_VERTICAL:   sprite = &yg->mouse_sprite_resize_v; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL: sprite = &yg->mouse_sprite_resize_h; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN:    sprite = &yg->mouse_sprite_resize_da; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP:    sprite = &yg->mouse_sprite_resize_db; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_POINT:             sprite = &yg->mouse_sprite_point; break;\n\t\t\tcase YUTANI_CURSOR_TYPE_IBEAM:             sprite = &yg->mouse_sprite_ibeam; break;\n\t\t}\n\t}\n\tif (sprite != previous) {\n\t\tmark_screen(yg, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n\t\tprevious = sprite;\n\t}\n\n\tif (yg->vbox_pointer > 0) {\n\t\tif (write(yg->vbox_pointer, sprite->bitmap, 48*48*4) > 0) {\n\t\t\t/* if that was successful, we don't need to draw the cursor */\n\t\t\treturn;\n\t\t}\n\t}\n\n\tyutani_server_window_t * cursor_window = yg->resizing_window ? yg->resizing_window :\n\t\ttop_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\tint16_t rotation = cursor_window ? cursor_window->rotation : 0;\n\n\tif (rotation) {\n\t\tdraw_sprite_rotate(yg->backend_ctx, sprite, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y, (double)rotation * M_PI / 180.0, 1.0);\n\t} else {\n\t\tdraw_sprite(yg->backend_ctx, sprite, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y);\n\t}\n}\n\n/**\n * Determine if a window has a solid pixel at a given screen-space coordinate.\n *\n * This is where we evaluate alpha thresholds. We only do this underneath\n * the cursor, and only when we move the cursor. It's reasonably fast\n * in those circumstances, but shouldn't be used for large regions. We\n * do have one debug method that indicates the top window in a box\n * around the cursor, but it is relatively slow.\n */\nstatic yutani_server_window_t * check_top_at(yutani_globals_t * yg, yutani_server_window_t * w, uint16_t x, uint16_t y){\n\tif (!w || w->hidden || w->minimized) return NULL;\n\tint32_t _x = -1, _y = -1;\n\tyutani_device_to_window(w, x, y, &_x, &_y);\n\tif (_x < 0 || _x >= w->width || _y < 0 || _y >= w->height) return NULL;\n\tuint32_t c = ((uint32_t *)w->buffer)[(w->width * _y + _x)];\n\tuint8_t a = _ALP(c);\n\tif (a >= w->alpha_threshold) {\n\t\treturn w;\n\t}\n\treturn NULL;\n}\n\n/**\n * Find the window that is at the top at a particular screen-space coordinate.\n *\n * This walks through each window from top to bottom (foreachr - reverse foreach)\n * until it finds one with a pixel at this coordinate. Again, we only call this\n * at the cursor coordinates, and it is not particularly fast, so don't use it\n * anywhere that needs to hit a lot of coordinates.\n */\nstatic yutani_server_window_t * top_at(yutani_globals_t * yg, uint16_t x, uint16_t y) {\n\tif (check_top_at(yg, yg->top_z, x, y)) return yg->top_z;\n\tforeachr(node, yg->menu_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (check_top_at(yg, w, x, y)) return w;\n\t}\n\tforeachr(node, yg->overlay_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (check_top_at(yg, w, x, y)) return w;\n\t}\n\tforeachr(node, yg->mid_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (check_top_at(yg, w, x, y)) return w;\n\t}\n\tif (check_top_at(yg, yg->bottom_z, x, y)) return yg->bottom_z;\n\treturn NULL;\n}\n\n/**\n * Get the window at a coordinate and focus it.\n *\n * See the docs for the proceeding functions, but added to this\n * focusing windows is also not particular fast as the reshuffle\n * is complicated.\n */\nstatic void set_focused_at(yutani_globals_t * yg, int x, int y) {\n\tyutani_server_window_t * n_focused = top_at(yg, x, y);\n\tset_focused_window(yg, n_focused);\n}\n\n/*\n * Convenience functions for checking if a window is in the top/bottom stack.\n *\n * In the future, these single-item \"stacks\" will be replaced with dedicated stacks\n * so we can have multiple background windows and multiple panels / always-top windows.\n */\nint yutani_window_is_top(yutani_globals_t * yg, yutani_server_window_t * window) {\n\t/* For now, just use simple z-order */\n\treturn window->z == YUTANI_ZORDER_TOP;\n}\n\nint yutani_window_is_bottom(yutani_globals_t * yg, yutani_server_window_t * window) {\n\t/* For now, just use simple z-order */\n\treturn window->z == YUTANI_ZORDER_BOTTOM;\n}\n\n/**\n * Get a color for a wid for debugging.\n *\n * Makes a pretty rainbow pattern.\n */\nuint32_t yutani_color_for_wid(yutani_wid_t wid) {\n\tstatic uint32_t colors[] = {\n\t\t0xFF19aeff,\n\t\t0xFFff4141,\n\t\t0xFFffff3e,\n\t\t0xFFff6600,\n\t\t0xFF9ade00,\n\t\t0xFFd76cff,\n\t\t0xFF364e59,\n\t\t0xFF0084c8,\n\t\t0xFFdc0000,\n\t\t0xFFff9900,\n\t\t0xFF009100,\n\t\t0xFFba00ff,\n\t\t0xFFb88100,\n\t\t0xFF9eabb0\n\t};\n\tint i = wid % (sizeof(colors) / sizeof(uint32_t));\n\treturn colors[i];\n}\n\n/**\n * Determine if a matrix has an identity transformation for its linear component.\n */\nstatic inline int matrix_is_translation(gfx_matrix_t m) {\n\treturn (m[0][0] == 1.0 && m[0][1] == 0.0 && m[1][0] == 0.0 && m[1][1] == 1.0);\n}\n\nstatic void apply_rotation(yutani_globals_t * yg, yutani_server_window_t * window, gfx_matrix_t m, int16_t r) {\n\tif (window == yg->resizing_window) {\n\t\tif (r) {\n\t\t\tgfx_matrix_translate(m, yg->resizing_init_w / 2, yg->resizing_init_h / 2);\n\t\t\tgfx_matrix_rotate(m, (double)r * M_PI / 180.0);\n\t\t\tgfx_matrix_translate(m, -yg->resizing_init_w / 2, -yg->resizing_init_h / 2);\n\t\t}\n\t\tdouble x_scale = (double)yg->resizing_w / (double)yg->resizing_window->width;\n\t\tdouble y_scale = (double)yg->resizing_h / (double)yg->resizing_window->height;\n\t\tif (x_scale < 0.00001) {\n\t\t\tx_scale = 0.00001;\n\t\t}\n\t\tif (y_scale < 0.00001) {\n\t\t\ty_scale = 0.00001;\n\t\t}\n\t\tgfx_matrix_translate(m, (int)yg->resizing_offset_x, (int)yg->resizing_offset_y);\n\t\tgfx_matrix_scale(m, x_scale, y_scale);\n\t} else if (r) {\n\t\tgfx_matrix_translate(m, window->width / 2, window->height / 2);\n\t\tgfx_matrix_rotate(m, (double)r * M_PI / 180.0);\n\t\tgfx_matrix_translate(m, -window->width / 2, -window->height / 2);\n\t}\n}\n\n/**\n * Blit a window to the framebuffer.\n *\n * Applies transformations (rotation, animations) and then renders\n * the window through alpha blitting.\n */\nstatic int yutani_blit_window(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y) {\n\n\tif (window->hidden || window->minimized) {\n\t\treturn 0;\n\t}\n\n\tsprite_t _win_sprite;\n\t_win_sprite.width = window->width;\n\t_win_sprite.height = window->height;\n\t_win_sprite.bitmap = (uint32_t *)window->buffer;\n\t_win_sprite.masks = NULL;\n\t_win_sprite.blank = 0;\n\t_win_sprite.alpha = ALPHA_EMBEDDED;\n\n\tdouble opacity = (double)(window->opacity) / 255.0;\n\n\tif (window->rotation || window == yg->resizing_window || window->anim_mode || (window->server_flags & YUTANI_WINDOW_FLAG_BLUR_BEHIND)) {\n\t\tdouble m[2][3];\n\n\t\tgfx_matrix_identity(m);\n\t\tgfx_matrix_translate(m,x,y);\n\n\t\tif (window->anim_mode) {\n\t\t\tint frame = yutani_time_since(yg, window->anim_start);\n\t\t\tif (frame >= yutani_animation_lengths[window->anim_mode]) {\n\t\t\t\t/* XXX handle animation-end things like cleanup of closing windows */\n\t\t\t\tif (yutani_is_closing_animation[window->anim_mode]) {\n\t\t\t\t\tlist_insert(yg->windows_to_remove, window);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tif (yutani_is_minimizing_animation[window->anim_mode]) {\n\t\t\t\t\tlist_insert(yg->windows_to_minimize, window);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\twindow->anim_mode = 0;\n\t\t\t\twindow->anim_start = 0;\n\t\t\t\tapply_rotation(yg, window, m, window->rotation);\n\t\t\t} else {\n\t\t\t\tswitch (window->anim_mode) {\n\t\t\t\t\tcase YUTANI_EFFECT_SQUEEZE_OUT:\n\t\t\t\t\tcase YUTANI_EFFECT_FADE_OUT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tframe = yutani_animation_lengths[window->anim_mode] - frame;\n\t\t\t\t\t\t} /* fallthrough */\n\t\t\t\t\tcase YUTANI_EFFECT_SQUEEZE_IN:\n\t\t\t\t\tcase YUTANI_EFFECT_FADE_IN:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdouble time_diff = ((double)frame / (float)yutani_animation_lengths[window->anim_mode]);\n\n\t\t\t\t\t\t\tapply_rotation(yg, window, m, window->rotation);\n\n\t\t\t\t\t\t\tif (window->server_flags & YUTANI_WINDOW_FLAG_DIALOG_ANIMATION) {\n\t\t\t\t\t\t\t\tdouble x = time_diff;\n\t\t\t\t\t\t\t\tint t_y = (window->height * (1.0 -x)) / 2;\n\t\t\t\t\t\t\t\tgfx_matrix_translate(m, 0, t_y);\n\t\t\t\t\t\t\t\tgfx_matrix_scale(m, 1.0, x);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdouble x = 0.75 + time_diff * 0.25;\n\t\t\t\t\t\t\t\topacity *= time_diff;\n\t\t\t\t\t\t\t\tif (!(window->server_flags & YUTANI_WINDOW_FLAG_ALT_ANIMATION)) {\n\t\t\t\t\t\t\t\t\tint t_x = (window->width * (1.0 - x)) / 2;\n\t\t\t\t\t\t\t\t\tint t_y = (window->height * (1.0 - x)) / 2;\n\t\t\t\t\t\t\t\t\tgfx_matrix_translate(m, t_x, t_y);\n\t\t\t\t\t\t\t\t\tgfx_matrix_scale(m, x, x);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_EFFECT_MINIMIZE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tframe = yutani_animation_lengths[window->anim_mode] - frame;\n\t\t\t\t\t\t} /* fallthrough */\n\t\t\t\t\tcase YUTANI_EFFECT_UNMINIMIZE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tdouble time_diff = ((double)frame / (float)yutani_animation_lengths[window->anim_mode]);\n\t\t\t\t\t\t\topacity *= time_diff;\n\t\t\t\t\t\t\tdouble t_x = -(window->x - window->icon_x) * (1.0 - time_diff);\n\t\t\t\t\t\t\tdouble t_y = -(window->y - window->icon_y) * (1.0 - time_diff);\n\t\t\t\t\t\t\tdouble s_x = 1.0 + (((float)window->icon_w / (float)(window->width ?: 1.0)) - 1.0) * (1.0 - time_diff);\n\t\t\t\t\t\t\tdouble s_y = 1.0 + (((float)window->icon_h / (float)(window->height ?: 1.0)) - 1.0) * (1.0 - time_diff);\n\t\t\t\t\t\t\tgfx_matrix_translate(m, t_x, t_y);\n\t\t\t\t\t\t\tgfx_matrix_scale(m, s_x, s_y);\n\t\t\t\t\t\t\tapply_rotation(yg, window, m, window->rotation * time_diff);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tapply_rotation(yg, window, m, window->rotation);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tapply_rotation(yg, window, m, window->rotation);\n\t\t}\n#ifdef ENABLE_BLUR_BEHIND\n\t\tif (window->server_flags & YUTANI_WINDOW_FLAG_BLUR_BEHIND) {\n\t\t\textern void draw_sprite_transform_blur(gfx_context_t * ctx, gfx_context_t * blur_ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha, uint8_t threshold);\n\t\t\tdraw_sprite_transform_blur(yg->backend_ctx, blur_ctx, &_win_sprite, m, opacity, window->alpha_threshold);\n\t\t} else\n#endif\n\t\tif (matrix_is_translation(m)) {\n\t\t\tdraw_sprite_alpha(yg->backend_ctx, &_win_sprite, m[0][2], m[1][2], opacity);\n\t\t} else {\n\t\t\tdraw_sprite_transform(yg->backend_ctx, &_win_sprite, m, opacity);\n\t\t}\n\t} else if (window->opacity != 255) {\n\t\tdraw_sprite_alpha(yg->backend_ctx, &_win_sprite, window->x, window->y, opacity);\n\t} else {\n\t\tdraw_sprite(yg->backend_ctx, &_win_sprite, window->x, window->y);\n\t}\n\n#if YUTANI_DEBUG_WINDOW_BOUNDS\n\tif (yg->debug_bounds) {\n\t\tint32_t t_x, t_y;\n\t\tint32_t s_x, s_y;\n\t\tint32_t r_x, r_y;\n\t\tint32_t q_x, q_y;\n\n\t\tyutani_window_to_device(window, 0, 0, &t_x, &t_y);\n\t\tyutani_window_to_device(window, window->width, window->height, &s_x, &s_y);\n\t\tyutani_window_to_device(window, 0, window->height, &r_x, &r_y);\n\t\tyutani_window_to_device(window, window->width, 0, &q_x, &q_y);\n\n\t\tuint32_t x = alpha_blend(rgba(0,0,0,0), yutani_color_for_wid(window->wid), rgb(178,0,0));\n\n\t\tstruct TT_Contour * contour = tt_contour_start(t_x,t_y);\n\t\tcontour = tt_contour_line_to(contour, r_x,r_y);\n\t\tcontour = tt_contour_line_to(contour, s_x,s_y);\n\t\tcontour = tt_contour_line_to(contour, q_x,q_y);\n\t\tstruct TT_Shape * shape = tt_contour_finish(contour);\n\t\tfree(contour);\n\t\ttt_path_paint(yg->backend_ctx, shape, x);\n\t\tfree(shape);\n\t}\n#endif\n\n\treturn 0;\n}\n\n/**\n * VirtualBox Seamless desktop driver.\n *\n * Sends rectangles describing all the non-background windows\n * to the VirtualBox Guest Additions driver for use with the\n * seamless desktop mode.\n */\nstatic void yutani_post_vbox_rects(yutani_globals_t * yg) {\n\tif (yg->vbox_rects <= 0) return;\n\n\tchar tmp[4096];\n\tuint32_t * count = (uint32_t *)tmp;\n\t*count = 0;\n\n\tstruct Rect {\n\t\tint32_t x;\n\t\tint32_t y;\n\t\tint32_t xe;\n\t\tint32_t ye;\n\t} __attribute__((packed));\n\n\n\tstruct Rect * rects = (struct Rect *)(tmp+sizeof(int32_t));\n\n#define DO_WINDOW(win) if (win && !win->hidden && !win->minimized && *count < 255 ) { \\\n\trects->x = (win)->x; \\\n\trects->y = (win)->y; \\\n\trects->xe = (win)->x + (win)->width; \\\n\trects->ye = (win)->y + (win)->height; \\\n\trects++; \\\n\t(*count)++; \\\n}\n\n\t/* Add top window if it exists */\n\tDO_WINDOW(yg->top_z);\n\n\t/* Add regular windows */\n\tforeach (node, yg->mid_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tDO_WINDOW(w);\n\t}\n\n\t/* Add overlay windows */\n\tforeach (node, yg->overlay_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tDO_WINDOW(w);\n\t}\n\n\t/* Add menu windows */\n\tforeach (node, yg->menu_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tDO_WINDOW(w);\n\t}\n\n\t/*\n\t * If there were no windows, show the whole desktop\n\t * so we can see, eg., the login screen.\n\t */\n\tif (*count == 0) {\n\t\t*count = 1;\n\t\trects->x = 0;\n\t\trects->y = 0;\n\t\trects->xe = yg->width;\n\t\trects->ye = yg->height;\n\t}\n\n\t/* Post rectangle data to driver */\n\twrite(yg->vbox_rects, tmp, sizeof(tmp));\n}\n\n/**\n * Blit all windows into the given context.\n *\n * This is called for rendering and for screenshots.\n */\nstatic void yutani_blit_windows(yutani_globals_t * yg) {\n\tif (!yg->bottom_z || yg->bottom_z->anim_mode) {\n\t\tdraw_fill(yg->backend_ctx, rgb(0,0,0));\n\t}\n\tif (yg->bottom_z) yutani_blit_window(yg, yg->bottom_z, yg->bottom_z->x, yg->bottom_z->y);\n\tforeach (node, yg->mid_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w) yutani_blit_window(yg, w, w->x, w->y);\n\t}\n\tforeach (node, yg->overlay_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w) yutani_blit_window(yg, w, w->x, w->y);\n\t}\n\tforeach (node, yg->menu_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w) yutani_blit_window(yg, w, w->x, w->y);\n\t}\n\tif (yg->top_z) yutani_blit_window(yg, yg->top_z, yg->top_z->x, yg->top_z->y);\n}\n\n/**\n * Take a screenshot\n */\nstatic void yutani_screenshot(yutani_globals_t * yg) {\n\tint task = yg->screenshot_frame;\n\tyg->screenshot_frame = 0;\n\n\t/* raw screenshots */\n\n\tchar fname[1024];\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tstrftime(fname,1024,\"/tmp/screenshot_%F_%H_%M_%S.tga\",timeinfo);\n\n\tFILE * f = fopen(fname, \"w\");\n\tif (!f) {\n\t\tTRACE(\"Error opening output file for screenshot.\");\n\t\treturn;\n\t}\n\n\tuint32_t * buffer = NULL;\n\tint width, height;\n\tint alpha;\n\n\tif (task == YUTANI_SCREENSHOT_FULL) {\n\t\tbuffer = (void *)yg->backend_ctx->backbuffer;\n\t\twidth = yg->width;\n\t\theight = yg->height;\n\t\talpha = 0;\n\t} else if (task == YUTANI_SCREENSHOT_WINDOW) {\n\t\tyutani_server_window_t * window = yg->focused_window;\n\t\tbuffer = (void *)window->buffer;\n\t\twidth = window->width;\n\t\theight = window->height;\n\t\talpha = 1;\n\t}\n\n\tif (buffer) {\n\n\t\tstruct {\n\t\t\tuint8_t id_length;\n\t\t\tuint8_t color_map_type;\n\t\t\tuint8_t image_type;\n\n\t\t\tuint16_t color_map_first_entry;\n\t\t\tuint16_t color_map_length;\n\t\t\tuint8_t color_map_entry_size;\n\n\t\t\tuint16_t x_origin;\n\t\t\tuint16_t y_origin;\n\t\t\tuint16_t width;\n\t\t\tuint16_t height;\n\t\t\tuint8_t  depth;\n\t\t\tuint8_t  descriptor;\n\t\t} __attribute__((packed)) header = {\n\t\t\t0, /* No image ID field */\n\t\t\t0, /* No color map */\n\t\t\t2, /* Uncompressed truecolor */\n\t\t\t0, 0, 0, /* No color map */\n\t\t\t0, 0, /* Don't care about origin */\n\t\t\twidth, height, alpha ? 32 : 24,\n\t\t\talpha ? 8 : 0,\n\t\t};\n\t\tfwrite(&header, 1, sizeof(header), f);\n\n\t\tfor (int y = height-1; y>=0; y--) {\n\t\t\tfor (int x = 0; x < width; ++x) {\n\t\t\t\tuint8_t buf[4] = {\n\t\t\t\t\t_BLU(buffer[y * width + x]),\n\t\t\t\t\t_GRE(buffer[y * width + x]),\n\t\t\t\t\t_RED(buffer[y * width + x]),\n\t\t\t\t\t_ALP(buffer[y * width + x]),\n\t\t\t\t};\n\t\t\t\tfwrite(buf, 1, alpha ? 4 : 3, f);\n\t\t\t}\n\t\t}\n\t}\n\tfclose(f);\n\n\n\tFILE * toast = fopen(\"/dev/pex/toast\", \"w\");\n\tfprintf(toast, \"{\\\"icon\\\": \\\"%s\\\", \\\"body\\\": \\\"Screenshot taken.\\\"}\", fname);\n\tfclose(toast);\n\n\t/* Blorp */\n\tsystem(\"play /usr/share/ttk/blorp.wav &\");\n}\n\nstatic gfx_context_t * init_graphics_with_store(gfx_context_t * base, char * store) {\n\tgfx_context_t * out = malloc(sizeof(gfx_context_t));\n\tout->clips = NULL;\n\tout->width = base->width;\n\tout->height = base->height;\n\tout->stride = base->stride;\n\tout->depth = base->depth;\n\tout->size = base->size;\n\tout->buffer = store;\n\tout->backbuffer = out->buffer;\n\treturn out;\n}\n\nstatic void resize_display(yutani_globals_t * yg) {\n\tTRACE(\"Resizing display.\");\n\n\tif (!yutani_options.nested) {\n\t\treinit_graphics_fullscreen(yg->backend_ctx);\n\t} else {\n\t\treinit_graphics_yutani(yg->backend_ctx, yg->host_window);\n\t\tyutani_window_resize_done(yg->host_context, yg->host_window);\n\t}\n\n#ifdef ENABLE_BLUR_BEHIND\n\tfree(blur_ctx);\n\tblur_texture = realloc(blur_texture, yg->backend_ctx->stride * yg->backend_ctx->height);\n\tblur_ctx = init_graphics_with_store(yg->backend_ctx, blur_texture);\n\tclip_ctx->width = yg->backend_ctx->width;\n\tclip_ctx->height = yg->backend_ctx->height;\n\n\t/* reinitialize extended clip context or we won't be drawing enough later... */\n\tif (clip_ctx->clips && clip_ctx->clips_size) {\n\t\tfree(clip_ctx->clips);\n\t\tclip_ctx->clips_size = 0;\n\t\tclip_ctx->clips = NULL;\n\t}\n#endif\n\n\tTRACE(\"graphics context resized...\");\n\tyg->width = yg->backend_ctx->width;\n\tyg->height = yg->backend_ctx->height;\n\tyg->backend_framebuffer = yg->backend_ctx->backbuffer;\n\n\tTRACE(\"Marking...\");\n\tyg->resize_on_next = 0;\n\tmark_screen(yg, 0, 0, yg->width, yg->height);\n\n\tTRACE(\"Sending welcome messages...\");\n\tyutani_msg_buildx_welcome_alloc(response);\n\tyutani_msg_buildx_welcome(response, yg->width, yg->height);\n\tpex_broadcast(yg->server, response->size, (char *)response);\n\tTRACE(\"Done.\");\n}\n\n/**\n * Redraw all windows, as well as the mouse cursor.\n *\n * This is the main redraw function.\n */\nstatic void redraw_windows(yutani_globals_t * yg) {\n\tint has_updates = 0;\n\n\t/* We keep our own temporary mouse coordinates as they may change while we're drawing. */\n\tint tmp_mouse_x = yg->mouse_x;\n\tint tmp_mouse_y = yg->mouse_y;\n\n\tif (yg->resize_on_next) {\n\t\tresize_display(yg);\n\t}\n\n\tif (yg->resizing_window &&\n\t\tyg->mouse_state == YUTANI_MOUSE_STATE_NORMAL &&\n\t\tyg->resize_release_time &&\n\t\tyutani_time_since(yg, yg->resize_release_time) >= 500) {\n\n\t\tyutani_server_window_t * resizing = yg->resizing_window;\n\t\tmark_window(yg, resizing);\n\t\tyg->resize_release_time = 0;\n\t\tyg->resizing_window = NULL;\n\t\tyg->mouse_window = NULL;\n\t\tmark_window(yg, resizing);\n\t}\n\n\tgfx_clear_clip(yg->backend_ctx);\n#ifdef ENABLE_BLUR_BEHIND\n\tgfx_clear_clip(clip_ctx);\n#endif\n\n\t/* If the mouse has moved, that counts as two damage regions */\n\tif ((yg->last_mouse_x != tmp_mouse_x) || (yg->last_mouse_y != tmp_mouse_y)) {\n\t\thas_updates = 2;\n\t\tgfx_add_clip(yg->backend_ctx, yg->last_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->last_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n\t\tgfx_add_clip(yg->backend_ctx, tmp_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, tmp_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n#ifdef ENABLE_BLUR_BEHIND\n\t\tgfx_add_clip(clip_ctx, yg->last_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X - BLUR_CLIP_MAX, yg->last_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y - BLUR_CLIP_MAX, MOUSE_WIDTH + BLUR_CLIP_MAX * 2, MOUSE_HEIGHT + BLUR_CLIP_MAX * 2);\n\t\tgfx_add_clip(clip_ctx, tmp_mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X - BLUR_CLIP_MAX, tmp_mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y - BLUR_CLIP_MAX, MOUSE_WIDTH + BLUR_CLIP_MAX * 2, MOUSE_HEIGHT + BLUR_CLIP_MAX * 2);\n#endif\n\t}\n\n\tyg->last_mouse_x = tmp_mouse_x;\n\tyg->last_mouse_y = tmp_mouse_y;\n\n\tif (yg->bottom_z && yg->bottom_z->anim_mode) mark_window(yg, yg->bottom_z);\n\tif (yg->top_z && yg->top_z->anim_mode) mark_window(yg, yg->top_z);\n\tforeach (node, yg->mid_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w && w->anim_mode) {\n\t\t\tif (w->anim_mode == YUTANI_EFFECT_MINIMIZE || w->anim_mode == YUTANI_EFFECT_UNMINIMIZE) {\n\t\t\t\tyutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));\n\t\t\t\trect->x = 0;\n\t\t\t\trect->y = 0;\n\t\t\t\trect->width = yg->width;\n\t\t\t\trect->height = yg->height;\n\t\t\t\tlist_insert(yg->update_list, rect);\n\t\t\t} else {\n\t\t\t\tmark_window(yg, w);\n\t\t\t}\n\t\t}\n\t}\n\tforeach (node, yg->overlay_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w && w->anim_mode) mark_window(yg, w);\n\t}\n\tforeach (node, yg->menu_zs) {\n\t\tyutani_server_window_t * w = node->value;\n\t\tif (w && w->anim_mode) mark_window(yg, w);\n\t}\n\n\t/* Calculate damage regions from currently queued updates */\n\twhile (yg->update_list->length) {\n\t\tnode_t * win = list_dequeue(yg->update_list);\n\t\tyutani_damage_rect_t * rect = (void *)win->value;\n\n\t\t/* We add a clip region for each window in the update queue */\n\t\thas_updates = 1;\n\t\tgfx_add_clip(yg->backend_ctx, rect->x, rect->y, rect->width, rect->height);\n#ifdef ENABLE_BLUR_BEHIND\n\t\tgfx_add_clip(clip_ctx, rect->x - BLUR_CLIP_MAX, rect->y - BLUR_CLIP_MAX, rect->width + BLUR_CLIP_MAX * 2, rect->height + BLUR_CLIP_MAX * 2);\n#endif\n\t\tfree(rect);\n\t\tfree(win);\n\t}\n\n\t/* Render */\n\tif (has_updates) {\n\n#ifdef ENABLE_BLUR_BEHIND\n\t\t/* Extend clips */\n\t\tchar * oclip = yg->backend_ctx->clips;\n\t\tyg->backend_ctx->clips = clip_ctx->clips;\n#endif\n\n\t\t/*\n\t\t * In theory, we should restrict this to windows within the clip region,\n\t\t * but calculating that may be more trouble than it's worth;\n\t\t * we also need to render windows in stacking order...\n\t\t */\n\t\tyutani_blit_windows(yg);\n\n#ifdef ENABLE_BLUR_BEHIND\n\t\t/* Restore clip context */\n\t\tyg->backend_ctx->clips = oclip;\n#endif\n\n\t\t/* Send VirtualBox rects */\n\t\tyutani_post_vbox_rects(yg);\n\n#if YUTANI_DEBUG_WINDOW_SHAPES\n#define WINDOW_SHAPE_VIEWER_SIZE 20\n\t\t/*\n\t\t * Debugging window shapes: draw a box around the mouse cursor\n\t\t * showing which window is at the top and will accept mouse events.\n\t\t */\n\t\tif (yg->debug_shapes) {\n\t\t\tint _ly = max(0,tmp_mouse_y/MOUSE_SCALE - WINDOW_SHAPE_VIEWER_SIZE);\n\t\t\tint _hy = min(yg->height,tmp_mouse_y/MOUSE_SCALE + WINDOW_SHAPE_VIEWER_SIZE);\n\t\t\tint _lx = max(0,tmp_mouse_x/MOUSE_SCALE - 20);\n\t\t\tint _hx = min(yg->width,tmp_mouse_x/MOUSE_SCALE + WINDOW_SHAPE_VIEWER_SIZE);\n\t\t\tfor (int y = _ly; y < _hy; ++y) {\n\t\t\t\tfor (int x = _lx; x < _hx; ++x) {\n\t\t\t\t\tyutani_server_window_t * w = top_at(yg, x, y);\n\t\t\t\t\tif (w) { GFX(yg->backend_ctx, x, y) = yutani_color_for_wid(w->wid); }\n\t\t\t\t}\n\t\t\t}\n\t\t}\n#endif\n\n\t\tif (yutani_options.nested) {\n\t\t\tflip(yg->backend_ctx);\n\t\t\t/*\n\t\t\t * We should be able to flip only the places we need to flip, but\n\t\t\t * instead we're going to flip the whole thing.\n\t\t\t *\n\t\t\t * TODO: Do a better job of this.\n\t\t\t */\n\t\t\tyutani_flip(yg->host_context, yg->host_window);\n\t\t\tyutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\t\t\tif (yg->mouse_state == YUTANI_MOUSE_STATE_MOVING) {\n\t\t\t\tyutani_window_show_mouse(yg->host_context, yg->host_window, YUTANI_CURSOR_TYPE_DRAG);\n\t\t\t} else if (!tmp_window || tmp_window->show_mouse) {\n\t\t\t\tyutani_window_show_mouse(yg->host_context, yg->host_window, tmp_window ? tmp_window->show_mouse : 1);\n\t\t\t}\n\t\t} else {\n\n\t\t\t/*\n\t\t\t * Draw the cursor.\n\t\t\t * We may also want to draw other compositor elements, like effects, but those\n\t\t\t * can also go in the stack order of the windows.\n\t\t\t */\n\t\t\tyutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\t\t\tif (!tmp_window || tmp_window->show_mouse) {\n\t\t\t\tdraw_cursor(yg, tmp_mouse_x, tmp_mouse_y, tmp_window ? tmp_window->show_mouse : 1);\n\t\t\t}\n\n\t\t\t/*\n\t\t\t * Flip the updated areas. This minimizes writes to video memory,\n\t\t\t * which is very important on real hardware where these writes are slow.\n\t\t\t */\n\t\t\tif (yg->backend_ctx->size == 0) {\n\t\t\t\textern void gfx_flip_24bit(gfx_context_t * ctx);\n\t\t\t\tgfx_flip_24bit(yg->backend_ctx);\n\t\t\t} else {\n\t\t\t\tflip(yg->backend_ctx);\n\t\t\t}\n\t\t}\n\n\t\tforeach (node, yg->windows) {\n\t\t\tyutani_server_window_t * w = node->value;\n\t\t\tif (w->z == YUTANI_ZORDER_MAX && w != yg->top_z) {\n\t\t\t\tif (yutani_is_closing_animation[w->anim_mode]) {\n\t\t\t\t\tlist_insert(yg->windows_to_remove, w);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/*\n\t\t * If any windows were marked for removal,\n\t\t * then remove them.\n\t\t */\n\t\twhile (yg->windows_to_remove->tail) {\n\t\t\tnode_t * node = list_pop(yg->windows_to_remove);\n\t\t\twindow_actually_close(yg, node->value);\n\t\t\tfree(node);\n\t\t}\n\n\t\twhile (yg->windows_to_minimize->tail) {\n\t\t\tnode_t * node = list_pop(yg->windows_to_minimize);\n\t\t\twindow_finish_minimize(yg, node->value);\n\t\t\tfree(node);\n\n\t\t}\n\n\t}\n\n\tif (yg->screenshot_frame) {\n\t\tyutani_screenshot(yg);\n\t}\n}\n\n/**\n * Initialize clipping regions.\n */\nvoid yutani_clip_init(yutani_globals_t * yg) {\n\tyg->update_list = list_create();\n}\n\n/**\n * Mark a region within a window as damaged.\n *\n * If the window is rotated, we calculate the minimum rectangle that covers\n * the whole region specified and then mark that.\n */\nstatic void mark_window_relative(yutani_globals_t * yg, yutani_server_window_t * window, int32_t x, int32_t y, int32_t width, int32_t height) {\n\tif (window->hidden || window->minimized) return;\n\tyutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t));\n\tyutani_server_window_t fake_window;\n\n\tif (window == yg->resizing_window) {\n\t\tfake_window.width  = yg->resizing_init_w;\n\t\tfake_window.height = yg->resizing_init_h;\n\t\tfake_window.x = window->x;\n\t\tfake_window.y = window->y;\n\t\tfake_window.rotation = window->rotation;\n\n\t\tdouble x_scale = (double)yg->resizing_w / (double)yg->resizing_window->width;\n\t\tdouble y_scale = (double)yg->resizing_h / (double)yg->resizing_window->height;\n\n\t\tx *= x_scale;\n\t\tx += yg->resizing_offset_x - 1;\n\n\t\ty *= y_scale;\n\t\ty += yg->resizing_offset_y - 1;\n\n\t\twidth *= x_scale;\n\t\theight *= y_scale;\n\n\t\twidth += 2;\n\t\theight += 2;\n\n\t\twindow = &fake_window;\n\t}\n\n\tif (window->rotation == 0) {\n\t\trect->x = window->x + x;\n\t\trect->y = window->y + y;\n\t\trect->width = width;\n\t\trect->height = height;\n\t} else {\n\t\tint32_t ul_x, ul_y;\n\t\tint32_t ll_x, ll_y;\n\t\tint32_t ur_x, ur_y;\n\t\tint32_t lr_x, lr_y;\n\n\t\tyutani_window_to_device(window, x, y, &ul_x, &ul_y);\n\t\tyutani_window_to_device(window, x, y + height, &ll_x, &ll_y);\n\t\tyutani_window_to_device(window, x + width, y, &ur_x, &ur_y);\n\t\tyutani_window_to_device(window, x + width, y + height, &lr_x, &lr_y);\n\n\t\t/* Calculate bounds */\n\n\t\tint32_t left_bound = min(min(ul_x, ll_x), min(ur_x, lr_x));\n\t\tint32_t top_bound  = min(min(ul_y, ll_y), min(ur_y, lr_y));\n\n\t\tint32_t right_bound  = max(max(ul_x+1, ll_x+1), max(ur_x+1, lr_x+1));\n\t\tint32_t bottom_bound = max(max(ul_y+1, ll_y+1), max(ur_y+1, lr_y+1));\n\n\t\trect->x = left_bound;\n\t\trect->y = top_bound;\n\t\trect->width = right_bound - left_bound;\n\t\trect->height = bottom_bound - top_bound;\n\t}\n\n\tlist_insert(yg->update_list, rect);\n}\n\n/**\n * (Convenience function) Mark a whole a window as damaged.\n */\nstatic void mark_window(yutani_globals_t * yg, yutani_server_window_t * window) {\n\tmark_window_relative(yg, window, 0, 0, window->width, window->height);\n}\n\n/**\n * Set a window as closed. It will be removed after rendering has completed.\n */\nstatic void window_mark_for_close(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tif (w->hidden || w->minimized) {\n\t\twindow_actually_close(yg, w);\n\t} else {\n\t\tw->anim_mode = yutani_pick_animation(w->server_flags, 1);\n\t\tw->anim_start = yutani_current_time(yg);\n\t}\n}\n\n/**\n * Remove a window from its owner's child set.\n */\nstatic void window_remove_from_client(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tlist_t * client_list = hashmap_get(yg->clients_to_windows, (void *)(uintptr_t)w->owner);\n\tif (client_list) {\n\t\tnode_t * n = list_find(client_list, w);\n\t\tif (n) {\n\t\t\tlist_delete(client_list, n);\n\t\t\tfree(n);\n\t\t}\n\t}\n}\n\nstatic void window_finish_minimize(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tif (w->minimized) return;\n\tw->minimized = 1;\n\tw->anim_mode = 0;\n\tw->anim_start = 0;\n\n\tunorder_window(yg,w);\n\tif (w == yg->focused_window) {\n\t\tyg->focused_window = NULL;\n\t\tif (yg->menu_zs->tail && yg->menu_zs->tail->value) {\n\t\t\tset_focused_window(yg, yg->menu_zs->tail->value);\n\t\t} else if (yg->mid_zs->tail && yg->mid_zs->tail->value) {\n\t\t\tset_focused_window(yg, yg->mid_zs->tail->value);\n\t\t} else {\n\t\t\tset_focused_window(yg, yg->bottom_z);\n\t\t}\n\t}\n\n\tlist_insert(yg->minimized_zs, w);\n\n\tnotify_subscribers(yg);\n}\n\n/**\n * Actually remove a window and free the associated resources.\n */\nstatic void window_actually_close(yutani_globals_t * yg, yutani_server_window_t * w) {\n\t/* Remove from the wid -> window mapping */\n\thashmap_remove(yg->wids_to_windows, (void *)(uintptr_t)w->wid);\n\n\t/* Remove from the general list of windows. */\n\tlist_remove(yg->windows, list_index_of(yg->windows, w));\n\n\tif (w->minimized) {\n\t\tnode_t * n = list_find(yg->minimized_zs, w);\n\t\tif (n) {\n\t\t\tlist_delete(yg->minimized_zs, n);\n\t\t\tfree(n);\n\t\t}\n\t}\n\n\t/* Unstack the window */\n\tunorder_window(yg, w);\n\n\t/* Mark the region where the window was */\n\tmark_window(yg, w);\n\n\t/* And if it was focused, unfocus it. */\n\tif (w == yg->focused_window) {\n\t\t/* find the top z-ordered window */\n\t\tyg->focused_window = NULL;\n\t\tif (yg->menu_zs->tail && yg->menu_zs->tail->value) {\n\t\t\tset_focused_window(yg, yg->menu_zs->tail->value);\n\t\t} else if (yg->mid_zs->tail && yg->mid_zs->tail->value) {\n\t\t\tset_focused_window(yg, yg->mid_zs->tail->value);\n\t\t} else {\n\t\t\tset_focused_window(yg, yg->bottom_z);\n\t\t}\n\t}\n\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, w->bufid);\n\n\t\t/*\n\t\t * Normally we would acquire a lock before doing this, but the render\n\t\t * thread holds that lock already and we are only called from the\n\t\t * render thread, so we don't bother.\n\t\t */\n\t\tshm_release(key);\n\t}\n\n\t/* Notify subscribers that there are changes to windows */\n\tnotify_subscribers(yg);\n}\n\n/**\n * Generate flags for client advertisements.\n *\n * Currently, we only have one flag (focused).\n */\nstatic uint32_t ad_flags(yutani_globals_t * yg, yutani_server_window_t * win) {\n\tuint32_t flags = win->client_flags;\n\tif (win == yg->focused_window || (yg->focused_window && yg->focused_window->parent == win->wid)) {\n\t\tflags |= 1;\n\t}\n\tif (win->minimized) {\n\t\tflags |= 2;\n\t}\n\treturn flags;\n}\n\n/**\n * Send a result for a window query.\n */\nstatic void yutani_query_result(yutani_globals_t * yg, uintptr_t dest, yutani_server_window_t * win) {\n\tif (win && win->client_length) {\n\t\tyutani_msg_buildx_window_advertise_alloc(response, win->client_length);\n\t\tyutani_msg_buildx_window_advertise(response, win->wid, ad_flags(yg, win), win->client_icon, win->bufid, win->width, win->height, win->client_length, win->client_strings);\n\t\tpex_send(yg->server, dest, response->size, (char *)response);\n\t}\n}\n\n/**\n * Send a notice to all subscribed clients that windows have updated.\n */\nstatic void notify_subscribers(yutani_globals_t * yg) {\n\tyutani_msg_buildx_notify_alloc(response);\n\tyutani_msg_buildx_notify(response);\n\tlist_t * remove = NULL;\n\tforeach(node, yg->window_subscribers) {\n\t\tuintptr_t subscriber = (uintptr_t)node->value;\n\t\tif (!hashmap_has(yg->clients_to_windows, (void *)subscriber)) {\n\t\t\tif (!remove) {\n\t\t\t\tremove = list_create();\n\t\t\t}\n\t\t\tlist_insert(remove, node);\n\t\t} else {\n\t\t\tpex_send(yg->server, subscriber, response->size, (char *)response);\n\t\t}\n\t}\n\tif (remove) {\n\t\twhile (remove->length) {\n\t\t\tnode_t * n = list_pop(remove);\n\t\t\tlist_delete(yg->window_subscribers, n->value);\n\t\t\tfree(n);\n\t\t}\n\t\tfree(remove);\n\t}\n}\n\nstatic void window_move(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y) {\n\tmark_window(yg, window);\n\twindow->x = x;\n\twindow->y = y;\n\tmark_window(yg, window);\n\n\tyutani_msg_buildx_window_move_alloc(response);\n\tyutani_msg_buildx_window_move(response, window->wid, x, y);\n\tpex_send(yg->server, window->owner, response->size, (char *)response);\n}\n\n/**\n * Move and resize a window to fit a particular tiling pattern.\n *\n * x and y are 0-based\n * width_div and height_div are the number of cells in each dimension\n */\nstatic void window_tile(yutani_globals_t * yg, yutani_server_window_t * window,  int width_div, int height_div, int x, int y) {\n\tint panel_h = 0;\n\tyutani_server_window_t * panel = yg->top_z;\n\tif (panel) {\n\t\tpanel_h = panel->height;\n\t\tif (panel->y < 1) {\n\t\t\tpanel_h += panel->y; /* We can move the panel up to \"hide\" it. */\n\t\t}\n\t}\n\n\tif (!window->tiled) {\n\t\twindow->untiled_width = window->width;\n\t\twindow->untiled_height = window->height;\n\t\twindow->untiled_left = window->x;\n\t\twindow->untiled_top = window->y;\n\t\twindow->tiled = 1;\n\t}\n\n\tint w = yg->width / width_div;\n\tint h = (yg->height - panel_h) / height_div;\n\tint _x = w * x;\n\tint _y = panel_h + h * y;\n\tif (x == width_div - 1) {\n\t\tw = yg->width - w * x;\n\t}\n\tif (y == height_div - 1) {\n\t\th = (yg->height - panel_h) - h * y;\n\t}\n\n\tint tile = YUTANI_RESIZE_TILED;\n\n\t/* If not left most */\n\tif (x > 0) {\n\t\t_x -= 1;\n\t\tw++;\n\t\ttile &= ~YUTANI_RESIZE_TILE_LEFT;\n\t}\n\n\t/* If not right most */\n\tif (x < width_div-1) {\n\t\ttile &= ~YUTANI_RESIZE_TILE_RIGHT;\n\t}\n\n\t/* If not top most */\n\tif (y > 0) {\n\t\t_y -= 1;\n\t\th++;\n\t\ttile &= ~YUTANI_RESIZE_TILE_UP;\n\t}\n\n\t/* If not bottom most */\n\tif (y < height_div-1) {\n\t\ttile &= ~YUTANI_RESIZE_TILE_DOWN;\n\t}\n\n\twindow_move(yg, window, _x, _y);\n\tyutani_msg_buildx_window_resize_alloc(response);\n\tyutani_msg_buildx_window_resize(response, YUTANI_MSG_RESIZE_OFFER, window->wid, w, h, 0, tile);\n\tpex_send(yg->server, window->owner, response->size, (char *)response);\n}\n\n/**\n * Take a previously tiled window and \"untile\" it, eg. restore its original size.\n */\nstatic void window_untile(yutani_globals_t * yg, yutani_server_window_t * window) {\n\twindow->tiled = 0;\n\n\tyutani_msg_buildx_window_resize_alloc(response);\n\tyutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, window->wid, window->untiled_width, window->untiled_height, 0, 0);\n\tpex_send(yg->server, window->owner, response->size, (char *)response);\n}\n\nstatic void window_reveal(yutani_globals_t * yg, yutani_server_window_t * window) {\n\tif (!window->hidden) return;\n\n\twindow->hidden = 0;\n\twindow->anim_mode = yutani_pick_animation(window->server_flags, 0);\n\twindow->anim_start = yutani_current_time(yg);\n}\n\nstatic void window_unminimize(yutani_globals_t * yg, yutani_server_window_t * window) {\n\tif (!window->minimized) return;\n\n\tnode_t * n = list_find(yg->minimized_zs, window);\n\tif (n) {\n\t\tlist_delete(yg->minimized_zs, n);\n\t\tfree(n);\n\t}\n\n\tlist_insert(yg->mid_zs, window);\n\twindow->z = 1;\n\n\twindow->minimized = 0;\n\twindow->anim_mode = YUTANI_EFFECT_UNMINIMIZE;\n\twindow->anim_start = yutani_current_time(yg);\n}\n\nstatic void window_minimize(yutani_globals_t * yg, yutani_server_window_t * window) {\n\tif (!window->client_length) return; /* Windows must be advertised to be minimized. */\n\twindow->anim_mode = YUTANI_EFFECT_MINIMIZE;\n\twindow->anim_start = yutani_current_time(yg);\n}\n\n/**\n * Process a key event.\n *\n * These are mostly compositor shortcuts and bindings.\n * We also process key bindings for other applications.\n */\nstatic void handle_key_event(yutani_globals_t * yg, struct yutani_msg_key_event * ke) {\n\tyg->active_modifiers = ke->event.modifiers;\n\tyutani_server_window_t * focused = get_focused(yg);\n\tif (focused) {\n#if 1\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'z')) {\n\t\t\tmark_window(yg,focused);\n\t\t\tfocused->rotation -= 5;\n\t\t\tmark_window(yg,focused);\n\t\t\treturn;\n\t\t}\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'x')) {\n\t\t\tmark_window(yg,focused);\n\t\t\tfocused->rotation += 5;\n\t\t\tmark_window(yg,focused);\n\t\t\treturn;\n\t\t}\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'c')) {\n\t\t\tmark_window(yg,focused);\n\t\t\tfocused->rotation = 0;\n\t\t\tmark_window(yg,focused);\n\t\t\treturn;\n\t\t}\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.keycode == 'a')) {\n\t\t\twindow_minimize(yg,focused);\n\t\t\treturn;\n\t\t}\n#endif\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t\t(ke->event.keycode == KEY_F10)) {\n\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\tif (focused->tiled) {\n\t\t\t\t\twindow_untile(yg, focused);\n\t\t\t\t\twindow_move(yg, focused, focused->untiled_left, focused->untiled_top);\n\t\t\t\t} else {\n\t\t\t\t\twindow_tile(yg, focused, 1, 1, 0, 0);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t\t(ke->event.keycode == KEY_F4)) {\n\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\tyutani_msg_buildx_window_close_alloc(response);\n\t\t\t\tyutani_msg_buildx_window_close(response, focused->wid);\n\t\t\t\tpex_send(yg->server, focused->owner, response->size, (char *)response);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n#if YUTANI_DEBUG_WINDOW_SHAPES\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'n')) {\n\t\t\tyg->debug_shapes = (1-yg->debug_shapes);\n\t\t\treturn;\n\t\t}\n#endif\n#if YUTANI_DEBUG_WINDOW_BOUNDS\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'b')) {\n\t\t\tyg->debug_bounds = (1-yg->debug_bounds);\n\t\t\treturn;\n\t\t}\n#endif\n#ifdef ENABLE_BLUR_BEHIND\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t(ke->event.keycode == 'v')) {\n\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\tfocused->server_flags ^= YUTANI_WINDOW_FLAG_BLUR_BEHIND;\n\t\t\t\tmark_window(yg, focused);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n#endif\n\t\t/* Screenshot key */\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.keycode == KEY_PRINT_SCREEN)) {\n\t\t\tif (ke->event.modifiers & (KEY_MOD_LEFT_SHIFT | KEY_MOD_RIGHT_SHIFT)) {\n\t\t\t\tyg->screenshot_frame = YUTANI_SCREENSHOT_WINDOW;\n\t\t\t} else {\n\t\t\t\tyg->screenshot_frame = YUTANI_SCREENSHOT_FULL;\n\t\t\t}\n\t\t}\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.keycode == KEY_ESCAPE) &&\n\t\t\t(yg->mouse_state == YUTANI_MOUSE_STATE_MOVING)) {\n\t\t\tmouse_stop_drag(yg);\n\t\t\treturn;\n\t\t}\n\t\t/*\n\t\t * Tiling hooks.\n\t\t * These are based on the compiz grid plugin.\n\t\t */\n\t\tif ((ke->event.action == KEY_ACTION_DOWN) &&\n\t\t\t(ke->event.modifiers & KEY_MOD_LEFT_SUPER)) {\n\t\t\tif ((ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t\t(ke->event.keycode == KEY_ARROW_LEFT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 2, 0, 0);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.modifiers & KEY_MOD_LEFT_SHIFT) &&\n\t\t\t\t(ke->event.keycode == KEY_ARROW_RIGHT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 2, 1, 0);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&\n\t\t\t\t(ke->event.keycode == KEY_ARROW_LEFT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 2, 0, 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&\n\t\t\t\t(ke->event.keycode == KEY_ARROW_RIGHT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 2, 1, 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.keycode == KEY_ARROW_LEFT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 1, 0, 0);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.keycode == KEY_ARROW_RIGHT)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 2, 1, 1, 0);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.keycode == KEY_ARROW_UP)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 1, 2, 0, 0);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ((ke->event.keycode == KEY_ARROW_DOWN)) {\n\t\t\t\tif (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) {\n\t\t\t\t\twindow_tile(yg, focused, 1, 2, 0, 1);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/*\n\t * External bindings registered by clients.\n\t */\n\tuint32_t key_code = ((ke->event.modifiers << 24) | (ke->event.keycode));\n\tif (hashmap_has(yg->key_binds, (void*)(uintptr_t)key_code)) {\n\t\tstruct key_bind * bind = hashmap_get(yg->key_binds, (void*)(uintptr_t)key_code);\n\n\t\tyutani_msg_buildx_key_event_alloc(response);\n\t\tyutani_msg_buildx_key_event(response,focused ? focused->wid : UINT32_MAX, &ke->event, &ke->state);\n\t\tpex_send(yg->server, bind->owner, response->size, (char *)response);\n\n\t\tif (bind->response == YUTANI_BIND_STEAL) {\n\t\t\t/* If this keybinding was registered as \"steal\", we'll stop here. */\n\t\t\treturn;\n\t\t}\n\t}\n\n\t/* Finally, send the key to the focused client. */\n\tif (focused) {\n\n\t\tyutani_msg_buildx_key_event_alloc(response);\n\t\tyutani_msg_buildx_key_event(response,focused->wid, &ke->event, &ke->state);\n\t\tpex_send(yg->server, focused->owner, response->size, (char *)response);\n\n\t}\n}\n\n/**\n * Register a new keybinding.\n *\n * req - bind message\n * owner - client to assign the binding to\n */\nstatic void add_key_bind(yutani_globals_t * yg, struct yutani_msg_key_bind * req, uintptr_t owner) {\n\tuint32_t key_code = (((uint8_t)req->modifiers << 24) | ((uint32_t)req->key & 0xFFFFFF));\n\tstruct key_bind * bind = hashmap_get(yg->key_binds, (void*)(uintptr_t)key_code);\n\n\tif (!bind) {\n\t\tbind = malloc(sizeof(struct key_bind));\n\n\t\tbind->owner = owner;\n\t\tbind->response = req->response;\n\n\t\thashmap_set(yg->key_binds, (void*)(uintptr_t)key_code, bind);\n\t} else {\n\t\tbind->owner = owner;\n\t\tbind->response = req->response;\n\t}\n}\n\nstatic void adjust_window_opacity(yutani_globals_t * yg, int direction) {\n\tyutani_server_window_t * window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\n\tif (window && window->z != YUTANI_ZORDER_BOTTOM) {\n\t\twindow->opacity += direction;\n\t\tif (window->opacity < 0) {\n\t\t\twindow->opacity = 0;\n\t\t}\n\t\tif (window->opacity > 255) {\n\t\t\twindow->opacity = 255;\n\t\t}\n\t\tmark_window(yg, window);\n\t}\n\n}\n\nstatic void mouse_stop_drag(yutani_globals_t * yg) {\n\tyg->mouse_window = NULL;\n\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\tmark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n}\n\nstatic void mouse_start_drag(yutani_globals_t * yg, yutani_server_window_t * w) {\n\tif (yg->mouse_state == YUTANI_MOUSE_STATE_RESIZING || yg->mouse_state == YUTANI_MOUSE_STATE_ROTATING) return; /* Refuse */\n\tset_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\tif (!w) {\n\t\tyg->mouse_window = get_focused(yg);\n\t} else {\n\t\tyg->mouse_window = w;\n\t}\n\tif (yg->mouse_window) {\n\t\tif (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP\n\t\t    || yg->mouse_window->server_flags & YUTANI_WINDOW_FLAG_DISALLOW_DRAG) {\n\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\tyg->mouse_window = NULL;\n\t\t} else {\n\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_MOVING;\n\t\t\tyg->mouse_init_x = yg->mouse_x;\n\t\t\tyg->mouse_init_y = yg->mouse_y;\n\t\t\tyg->mouse_win_x  = yg->mouse_window->x;\n\t\t\tyg->mouse_win_y  = yg->mouse_window->y;\n\t\t\tyg->mouse_drag_button = yg->last_mouse_buttons;\n\t\t\tmark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n\t\t\tmake_top(yg, yg->mouse_window);\n\t\t}\n\t}\n}\n\nstatic void mouse_start_rotate(yutani_globals_t * yg) {\n\tset_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\tyg->mouse_window = get_focused(yg);\n\tif (yg->mouse_window) {\n\t\tif (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP) {\n\t\t\t/* Prevent rotating panel and wallpaper */\n\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\tyg->mouse_window = NULL;\n\t\t\treturn;\n\t\t}\n\t\tyg->mouse_state = YUTANI_MOUSE_STATE_ROTATING;\n\t\tyg->mouse_init_x = yg->mouse_x;\n\t\tyg->mouse_init_y = yg->mouse_y;\n\t\tint32_t x_diff = yg->mouse_x / MOUSE_SCALE - (yg->mouse_window->x + yg->mouse_window->width / 2);\n\t\tint32_t y_diff = yg->mouse_y / MOUSE_SCALE - (yg->mouse_window->y + yg->mouse_window->height / 2);\n\t\tint new_r = atan2(x_diff, y_diff) * 180.0 / (-M_PI);\n\t\tyg->mouse_init_r = yg->mouse_window->rotation - new_r;\n\t\tmake_top(yg, yg->mouse_window);\n\t}\n}\n\nstatic void mouse_start_resize(yutani_globals_t * yg, yutani_scale_direction_t direction) {\n\tset_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\tyg->mouse_window = get_focused(yg);\n\tif (yg->mouse_window) {\n\t\tif (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP\n\t\t    || yg->mouse_window->server_flags & YUTANI_WINDOW_FLAG_DISALLOW_RESIZE) {\n\t\t\t/* Prevent resizing panel and wallpaper */\n\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\tyg->mouse_window = NULL;\n\t\t\tyg->resizing_window = NULL;\n\t\t} else {\n\t\t\tTRACE(\"resize starting for wid=%d\", yg->mouse_window -> wid);\n\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_RESIZING;\n\t\t\tyg->mouse_init_x = yg->mouse_x;\n\t\t\tyg->mouse_init_y = yg->mouse_y;\n\t\t\tyg->mouse_win_x  = yg->mouse_window->x;\n\t\t\tyg->mouse_win_y  = yg->mouse_window->y;\n\t\t\tyg->resizing_window = yg->mouse_window;\n\t\t\tyg->resizing_w = yg->mouse_window->width;\n\t\t\tyg->resizing_h = yg->mouse_window->height;\n\t\t\tyg->resizing_offset_x = 0;\n\t\t\tyg->resizing_offset_y = 0;\n\t\t\tyg->resizing_init_w = yg->mouse_window->width;\n\t\t\tyg->resizing_init_h = yg->mouse_window->height;\n\n\t\t\tif (direction == SCALE_AUTO) {\n\t\t\t\t/* Determine the best direction to scale in based on simple 9-cell system. */\n\t\t\t\tint32_t x, y;\n\t\t\t\tyutani_device_to_window(yg->resizing_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y);\n\n\t\t\t\tint h_d = 0;\n\t\t\t\tint v_d = 0;\n\n\t\t\t\tif (y <= yg->resizing_h / 3) {\n\t\t\t\t\tv_d = -1;\n\t\t\t\t} else if (y >= (yg->resizing_h / 3) * 2) {\n\t\t\t\t\tv_d = 1;\n\t\t\t\t}\n\t\t\t\tif (x <= yg->resizing_w / 3) {\n\t\t\t\t\th_d = -1;\n\t\t\t\t} else if (x >= (yg->resizing_w / 3) * 2) {\n\t\t\t\t\th_d = 1;\n\t\t\t\t}\n\n\t\t\t\t/* Fall back */\n\t\t\t\tif (h_d ==  0 && v_d ==  0) direction = SCALE_DOWN_RIGHT;\n\n\t\t\t\telse if (h_d ==  1 && v_d ==  1) direction = SCALE_DOWN_RIGHT;\n\t\t\t\telse if (h_d ==  1 && v_d == -1) direction = SCALE_UP_RIGHT;\n\t\t\t\telse if (h_d == -1 && v_d ==  1) direction = SCALE_DOWN_LEFT;\n\t\t\t\telse if (h_d == -1 && v_d == -1) direction = SCALE_UP_LEFT;\n\n\t\t\t\telse if (h_d ==  1 && v_d ==  0) direction = SCALE_RIGHT;\n\t\t\t\telse if (h_d == -1 && v_d ==  0) direction = SCALE_LEFT;\n\t\t\t\telse if (h_d ==  0 && v_d ==  1) direction = SCALE_DOWN;\n\t\t\t\telse if (h_d ==  0 && v_d == -1) direction = SCALE_UP;\n\t\t\t}\n\n\t\t\tyg->resizing_direction = direction;\n\t\t\tmake_top(yg, yg->mouse_window);\n\t\t\tmark_window(yg, yg->resizing_window);\n\t\t}\n\t}\n}\n\nstatic void handle_mouse_event(yutani_globals_t * yg, struct yutani_msg_mouse_event * me)  {\n\tif (me->type == YUTANI_MOUSE_EVENT_TYPE_RELATIVE) {\n\t\tyg->mouse_x += me->event.x_difference YUTANI_INCOMING_MOUSE_SCALE;\n\t\tyg->mouse_y -= me->event.y_difference YUTANI_INCOMING_MOUSE_SCALE;\n\t} else if (me->type == YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE) {\n\t\tyg->mouse_x = me->event.x_difference * MOUSE_SCALE;\n\t\tyg->mouse_y = me->event.y_difference * MOUSE_SCALE;\n\t}\n\n\tif (yg->mouse_x < 0) yg->mouse_x = 0;\n\tif (yg->mouse_y < 0) yg->mouse_y = 0;\n\tif (yg->mouse_x > (int)(yg->width) * MOUSE_SCALE) yg->mouse_x = (yg->width) * MOUSE_SCALE;\n\tif (yg->mouse_y > (int)(yg->height) * MOUSE_SCALE) yg->mouse_y = (yg->height) * MOUSE_SCALE;\n\n\tswitch (yg->mouse_state) {\n\t\tcase YUTANI_MOUSE_STATE_NORMAL:\n\t\t\t{\n\t\t\t\tif ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) &&\n\t\t\t\t\t\t(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tmouse_start_drag(yg, NULL);\n\t\t\t\t} else if ((me->event.buttons & YUTANI_MOUSE_SCROLL_UP) &&\n\t\t\t\t\t\t(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tadjust_window_opacity(yg, 8);\n\t\t\t\t} else if ((me->event.buttons & YUTANI_MOUSE_SCROLL_DOWN) &&\n\t\t\t\t\t\t(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tadjust_window_opacity(yg, -8);\n\t\t\t\t} else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT) &&\n\t\t\t\t\t\t(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tmouse_start_rotate(yg);\n\t\t\t\t} else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_MIDDLE) &&\n\t\t\t\t\t\t(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tyg->resizing_button = YUTANI_MOUSE_BUTTON_MIDDLE;\n\t\t\t\t\tmouse_start_resize(yg, SCALE_AUTO);\n\t\t\t\t} else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) &&\n\t\t\t\t\t\t!(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) {\n\t\t\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING;\n\t\t\t\t\tset_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\t\t\t\t\tyg->mouse_window = get_focused(yg);\n\t\t\t\t\tyg->mouse_moved = 0;\n\t\t\t\t\tyg->mouse_drag_button = YUTANI_MOUSE_BUTTON_LEFT;\n\t\t\t\t\tif (yg->mouse_window) {\n\t\t\t\t\t\tyutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);\n\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_DOWN, yg->active_modifiers);\n\t\t\t\t\t\tyg->mouse_click_x_orig = yg->mouse_click_x;\n\t\t\t\t\t\tyg->mouse_click_y_orig = yg->mouse_click_y;\n\t\t\t\t\t\tpex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tyg->mouse_window = get_focused(yg);\n\t\t\t\t\tyutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE);\n\t\t\t\t\tif (yg->mouse_window && !(me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) {\n\t\t\t\t\t\tint32_t x, y;\n\t\t\t\t\t\tyutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y);\n\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_MOVE, yg->active_modifiers);\n\t\t\t\t\t\tpex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);\n\t\t\t\t\t}\n\t\t\t\t\tif (tmp_window) {\n\t\t\t\t\t\tint32_t x, y;\n\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\tif (tmp_window != yg->old_hover_window) {\n\t\t\t\t\t\t\tyutani_device_to_window(tmp_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y);\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response, tmp_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_ENTER, yg->active_modifiers);\n\t\t\t\t\t\t\tpex_send(yg->server, tmp_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t\tif (yg->old_hover_window) {\n\t\t\t\t\t\t\t\tyutani_device_to_window(yg->old_hover_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response, yg->old_hover_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_LEAVE, yg->active_modifiers);\n\t\t\t\t\t\t\t\tpex_send(yg->server, yg->old_hover_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tyg->old_hover_window = tmp_window;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (tmp_window != yg->mouse_window || (me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) {\n\t\t\t\t\t\t\tyutani_device_to_window(tmp_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y);\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response, tmp_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_MOVE, yg->active_modifiers);\n\t\t\t\t\t\t\tpex_send(yg->server, tmp_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MOUSE_STATE_MOVING:\n\t\t\t{\n\t\t\t\tint button_down = (me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT);\n\t\t\t\tint drag_stop = yg->mouse_drag_button != 0 ? (!button_down) : (button_down);\n\t\t\t\tif (drag_stop) {\n\t\t\t\t\tmouse_stop_drag(yg);\n\t\t\t\t} else {\n\t\t\t\t\tif (yg->mouse_y / MOUSE_SCALE < 10) {\n\t\t\t\t\t\tif (!yg->mouse_window->tiled) {\n\t\t\t\t\t\t\twindow_tile(yg, yg->mouse_window, 1, 1, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (yg->mouse_x / MOUSE_SCALE < 10) {\n\t\t\t\t\t\tif (!yg->mouse_window->tiled) {\n\t\t\t\t\t\t\twindow_tile(yg, yg->mouse_window, 2, 1, 0, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else if (yg->mouse_x / MOUSE_SCALE >= ((int)yg->width - 10)) {\n\t\t\t\t\t\tif (!yg->mouse_window->tiled) {\n\t\t\t\t\t\t\twindow_tile(yg, yg->mouse_window, 2, 1, 1, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (yg->mouse_window->tiled) {\n\t\t\t\t\t\tif ((abs(yg->mouse_x - yg->mouse_init_x) > UNTILE_SENSITIVITY) || (abs(yg->mouse_y - yg->mouse_init_y) > UNTILE_SENSITIVITY)) {\n\t\t\t\t\t\t\t/* Untile it */\n\t\t\t\t\t\t\twindow_untile(yg,yg->mouse_window);\n\t\t\t\t\t\t\t/* Position the window such that it's representative of where it was, percentage-wise, in the untiled window */\n\t\t\t\t\t\t\tfloat percent_x = (float)(yg->mouse_x / MOUSE_SCALE - yg->mouse_window->x) / (float)yg->mouse_window->width;\n\t\t\t\t\t\t\tfloat percent_y = (float)(yg->mouse_y / MOUSE_SCALE - yg->mouse_window->y) / (float)yg->mouse_window->height;\n\t\t\t\t\t\t\twindow_move(yg, yg->mouse_window,\n\t\t\t\t\t\t\t            yg->mouse_x / MOUSE_SCALE - yg->mouse_window->untiled_width * percent_x,\n\t\t\t\t\t\t\t            yg->mouse_y / MOUSE_SCALE - yg->mouse_window->untiled_height * percent_y);\n\t\t\t\t\t\t\t/* reset init_x / init_y */\n\t\t\t\t\t\t\tyg->mouse_init_x = yg->mouse_x;\n\t\t\t\t\t\t\tyg->mouse_init_y = yg->mouse_y;\n\t\t\t\t\t\t\tyg->mouse_win_x  = yg->mouse_window->x;\n\t\t\t\t\t\t\tyg->mouse_win_y  = yg->mouse_window->y;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tint x, y;\n\t\t\t\t\t\tx = yg->mouse_win_x + (yg->mouse_x - yg->mouse_init_x) / MOUSE_SCALE;\n\t\t\t\t\t\ty = yg->mouse_win_y + (yg->mouse_y - yg->mouse_init_y) / MOUSE_SCALE;\n\t\t\t\t\t\twindow_move(yg, yg->mouse_window, x, y);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MOUSE_STATE_ROTATING:\n\t\t\t{\n\t\t\t\tif (!(me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) {\n\t\t\t\t\tyg->mouse_window = NULL;\n\t\t\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\t\t\tmark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n\t\t\t\t} else if (yg->mouse_window) {\n\t\t\t\t\t/* Calculate rotation and make relative to initial rotation */\n\t\t\t\t\tint32_t x_diff = yg->mouse_x / MOUSE_SCALE - (yg->mouse_window->x + yg->mouse_window->width / 2);\n\t\t\t\t\tint32_t y_diff = yg->mouse_y / MOUSE_SCALE - (yg->mouse_window->y + yg->mouse_window->height / 2);\n\t\t\t\t\tint new_r = atan2(x_diff, y_diff) * 180.0 / (-M_PI);\n\t\t\t\t\tmark_window(yg, yg->mouse_window);\n\t\t\t\t\t/* Normalize to -179~180 range */\n\t\t\t\t\tint nr = (new_r + yg->mouse_init_r + 360) % 360;\n\t\t\t\t\tyg->mouse_window->rotation = nr > 180 ? nr - 360 : nr;\n\t\t\t\t\tmark_window(yg, yg->mouse_window);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MOUSE_STATE_DRAGGING:\n\t\t\t{\n\t\t\t\tif (!(me->event.buttons & yg->mouse_drag_button)) {\n\t\t\t\t\t/* Mouse released */\n\t\t\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\t\t\tint32_t old_x = yg->mouse_click_x_orig;\n\t\t\t\t\tint32_t old_y = yg->mouse_click_y_orig;\n\t\t\t\t\tif (yg->mouse_window) {\n\t\t\t\t\t\tyutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);\n\t\t\t\t\t\tif (!yg->mouse_moved) {\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_CLICK, yg->active_modifiers);\n\t\t\t\t\t\t\tpex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, old_x, old_y, me->event.buttons, YUTANI_MOUSE_EVENT_RAISE, yg->active_modifiers);\n\t\t\t\t\t\t\tpex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING;\n\t\t\t\t\tyg->mouse_moved = 1;\n\t\t\t\t\tint32_t old_x = yg->mouse_click_x;\n\t\t\t\t\tint32_t old_y = yg->mouse_click_y;\n\t\t\t\t\tif (yg->mouse_window) {\n\t\t\t\t\t\tyutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y);\n\t\t\t\t\t\tif (old_x != yg->mouse_click_x || old_y != yg->mouse_click_y) {\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event_alloc(response);\n\t\t\t\t\t\t\tyutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, yg->mouse_click_x, yg->mouse_click_y, old_x, old_y, me->event.buttons, YUTANI_MOUSE_EVENT_DRAG, yg->active_modifiers);\n\t\t\t\t\t\t\tpex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MOUSE_STATE_RESIZING:\n\t\t\t{\n\n\t\t\t\tint32_t relative_x, relative_y;\n\t\t\t\tint32_t relative_init_x, relative_init_y;\n\n\t\t\t\tyutani_server_window_t fake_window = {\n\t\t\t\t\t.width = yg->resizing_init_w,\n\t\t\t\t\t.height = yg->resizing_init_h,\n\t\t\t\t\t.x = yg->resizing_window->x,\n\t\t\t\t\t.y = yg->resizing_window->y,\n\t\t\t\t\t.rotation = yg->resizing_window->rotation,\n\t\t\t\t};\n\n\t\t\t\tyutani_device_to_window(&fake_window, yg->mouse_init_x / MOUSE_SCALE, yg->mouse_init_y / MOUSE_SCALE, &relative_init_x, &relative_init_y);\n\t\t\t\tyutani_device_to_window(&fake_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &relative_x, &relative_y);\n\n\t\t\t\tint width_diff  = (relative_x - relative_init_x);\n\t\t\t\tint height_diff = (relative_y - relative_init_y);\n\n\t\t\t\tmark_window(yg, yg->resizing_window);\n\n\t\t\t\tif (yg->resizing_direction == SCALE_UP || yg->resizing_direction == SCALE_DOWN) {\n\t\t\t\t\twidth_diff = 0;\n\t\t\t\t\tyg->resizing_offset_x = 0;\n\t\t\t\t}\n\n\t\t\t\tif (yg->resizing_direction == SCALE_LEFT || yg->resizing_direction == SCALE_RIGHT) {\n\t\t\t\t\theight_diff = 0;\n\t\t\t\t\tyg->resizing_offset_y = 0;\n\t\t\t\t}\n\n\t\t\t\tif (yg->resizing_direction == SCALE_LEFT ||\n\t\t\t\t    yg->resizing_direction == SCALE_UP_LEFT ||\n\t\t\t\t    yg->resizing_direction == SCALE_DOWN_LEFT) {\n\t\t\t\t\tyg->resizing_offset_x = width_diff;\n\t\t\t\t\twidth_diff = -width_diff;\n\t\t\t\t} else if (yg->resizing_direction == SCALE_RIGHT ||\n\t\t\t\t           yg->resizing_direction == SCALE_UP_RIGHT ||\n\t\t\t\t           yg->resizing_direction == SCALE_DOWN_RIGHT) {\n\t\t\t\t\tyg->resizing_offset_x = 0;\n\t\t\t\t}\n\n\t\t\t\tif (yg->resizing_direction == SCALE_UP ||\n\t\t\t\t    yg->resizing_direction == SCALE_UP_LEFT ||\n\t\t\t\t    yg->resizing_direction == SCALE_UP_RIGHT) {\n\t\t\t\t\tyg->resizing_offset_y = height_diff;\n\t\t\t\t\theight_diff = -height_diff;\n\t\t\t\t} else if (yg->resizing_direction == SCALE_DOWN ||\n\t\t\t\t           yg->resizing_direction == SCALE_DOWN_LEFT ||\n\t\t\t\t           yg->resizing_direction == SCALE_DOWN_RIGHT) {\n\t\t\t\t\tyg->resizing_offset_y = 0;\n\t\t\t\t}\n\n\t\t\t\tyg->resizing_w = yg->resizing_init_w + width_diff;\n\t\t\t\tyg->resizing_h = yg->resizing_init_h + height_diff;\n\n\t\t\t\t/* Enforce logical boundaries */\n\t\t\t\tif (yg->resizing_w < 1) {\n\t\t\t\t\tyg->resizing_w = 1;\n\t\t\t\t}\n\t\t\t\tif (yg->resizing_h < 1) {\n\t\t\t\t\tyg->resizing_h = 1;\n\t\t\t\t}\n\t\t\t\tif (yg->resizing_offset_x > yg->resizing_init_w) {\n\t\t\t\t\tyg->resizing_offset_x = yg->resizing_init_w;\n\t\t\t\t}\n\t\t\t\tif (yg->resizing_offset_y > yg->resizing_init_h) {\n\t\t\t\t\tyg->resizing_offset_y = yg->resizing_init_h;\n\t\t\t\t}\n\n\t\t\t\tmark_window(yg, yg->resizing_window);\n\n\t\t\t\tif (!yg->resize_release_time ||\n\t\t\t\t    !(me->event.buttons & yg->resizing_button) ||\n\t\t\t\t    (yg->resize_release_time && yutani_time_since(yg, yg->resize_release_time) >= 100)) {\n\t\t\t\t\tyg->resize_release_time = yutani_current_time(yg);\n\t\t\t\t\tyutani_msg_buildx_window_resize_alloc(response);\n\t\t\t\t\tyutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, yg->resizing_window->wid, yg->resizing_w, yg->resizing_h, 0, yg->resizing_window->tiled);\n\t\t\t\t\tpex_send(yg->server, yg->resizing_window->owner, response->size, (char *)response);\n\t\t\t\t}\n\n\t\t\t\tif (!(me->event.buttons & yg->resizing_button)) {\n\t\t\t\t\tyg->mouse_state = YUTANI_MOUSE_STATE_NORMAL;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/* XXX ? */\n\t\t\tbreak;\n\t}\n}\n\nstatic yutani_globals_t * _static_yg;\nstatic void yutani_display_resize_handle(int signum) {\n\t(void)signum;\n\tTRACE(\"Display change request, one moment.\");\n\t_static_yg->resize_on_next = 1;\n\tsignal(SIGWINEVENT, yutani_display_resize_handle);\n}\n\n#define FONT_PATH \"/usr/share/fonts/\"\n#define FONT(a,b) {a, FONT_PATH b}\n\nstruct font_def {\n\tchar * identifier;\n\tchar * path;\n};\n\n/**\n * TODO: This should be configurable...\n */\nstatic struct font_def fonts[] = {\n\tFONT(\"sans-serif\",            \"truetype/dejavu/DejaVuSans.ttf\"),\n\tFONT(\"sans-serif.bold\",       \"truetype/dejavu/DejaVuSans-Bold.ttf\"),\n\tFONT(\"sans-serif.italic\",     \"truetype/dejavu/DejaVuSans-Oblique.ttf\"),\n\tFONT(\"sans-serif.bolditalic\", \"truetype/dejavu/DejaVuSans-BoldOblique.ttf\"),\n\tFONT(\"monospace\",             \"truetype/dejavu/DejaVuSansMono.ttf\"),\n\tFONT(\"monospace.bold\",        \"truetype/dejavu/DejaVuSansMono-Bold.ttf\"),\n\tFONT(\"monospace.italic\",      \"truetype/dejavu/DejaVuSansMono-Oblique.ttf\"),\n\tFONT(\"monospace.bolditalic\",  \"truetype/dejavu/DejaVuSansMono-BoldOblique.ttf\"),\n\t{NULL, NULL}\n};\n\nstatic char * precache_shmfont(char * ident, char * name) {\n\tFILE * f = fopen(name, \"r\");\n\tif (!f) return NULL;\n\tsize_t s = 0;\n\tfseek(f, 0, SEEK_END);\n\ts = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\n\tsize_t shm_size = s;\n\tchar * font = shm_obtain(ident, &shm_size);\n\tassert((shm_size >= s) && \"shm_obtain returned too little memory to load a font into!\");\n\n\tfread(font, s, 1, f);\n\n\tfclose(f);\n\treturn font;\n}\n\nstatic void load_fonts(yutani_globals_t * yg) {\n\tint i = 0;\n\twhile (fonts[i].identifier) {\n\t\tchar tmp[100];\n\t\tsprintf(tmp, \"sys.%s.fonts.%s\", yg->server_ident, fonts[i].identifier);\n\t\tTRACE(\"Loading font %s -> %s\", fonts[i].path, tmp);\n\t\tif (!precache_shmfont(tmp, fonts[i].path)) {\n\t\t\tTRACE(\"  ... failed.\");\n\t\t}\n\t\t++i;\n\t}\n}\n\n/**\n * main\n */\nint main(int argc, char * argv[]) {\n\n\tint argx = 0;\n\tint results = parse_args(argc, argv, &argx);\n\tif (results) return results;\n\n\tyutani_globals_t * yg = malloc(sizeof(yutani_globals_t));\n\tmemset(yg, 0x00, sizeof(yutani_globals_t));\n\n\tif (yutani_options.nested) {\n\t\tyg->host_context = yutani_init();\n\t\tyg->host_window = yutani_window_create(yg->host_context, yutani_options.nest_width, yutani_options.nest_height);\n\t\tyutani_window_move(yg->host_context, yg->host_window, 50, 50);\n\t\tyutani_window_advertise_icon(yg->host_context, yg->host_window, \"Compositor\", \"compositor\");\n\t\tyg->backend_ctx = init_graphics_yutani_double_buffer(yg->host_window);\n\t} else {\n\t\tchar * d = getenv(\"DISPLAY\");\n\t\tif (d && *d) {\n\t\t\tfprintf(stderr, \"DISPLAY is already set but not running nested. This is probably wrong.\\n\");\n\t\t\treturn 1;\n\t\t}\n\t\t_static_yg = yg;\n\t\tsignal(SIGWINEVENT, yutani_display_resize_handle);\n\t\tyg->backend_ctx = init_graphics_fullscreen_double_buffer();\n\t}\n\n#ifdef ENABLE_BLUR_BEHIND\n\tblur_texture = realloc(blur_texture, yg->backend_ctx->stride * yg->backend_ctx->height);\n\tblur_ctx = init_graphics_with_store(yg->backend_ctx, blur_texture);\n\tclip_ctx = calloc(1, sizeof(gfx_context_t));\n\tclip_ctx->width = yg->backend_ctx->width;\n\tclip_ctx->height = yg->backend_ctx->height;\n#endif\n\n\tif (!yg->backend_ctx) {\n\t\tfree(yg);\n\t\tTRACE(\"Failed to open framebuffer, bailing.\");\n\t\treturn 1;\n\t}\n\n\t{\n\t\tstruct timeval t;\n\t\tgettimeofday(&t, NULL);\n\t\tyg->start_time = t.tv_sec;\n\t\tyg->start_subtime = t.tv_usec;\n\t}\n\n\tyg->width = yg->backend_ctx->width;\n\tyg->height = yg->backend_ctx->height;\n\n\tdraw_fill(yg->backend_ctx, rgb(0,0,0));\n\tflip(yg->backend_ctx);\n\n\tyg->backend_framebuffer = yg->backend_ctx->backbuffer;\n\n\tif (yutani_options.nested) {\n\t\tchar * name = malloc(sizeof(char) * 64);\n\t\tsprintf(name, \"compositor-nest-%d\", getpid());\n\t\tyg->server_ident = name;\n\t} else {\n\t\t/* XXX check if this already exists? */\n\t\tyg->server_ident = \"compositor\";\n\t}\n\tsetenv(\"DISPLAY\", yg->server_ident, 1);\n\n\tFILE * server = pex_bind(yg->server_ident);\n\tTRACE(\"pex bound? %d\", server);\n\tyg->server = server;\n\n\tload_fonts(yg);\n\n\tTRACE(\"Loading sprites...\");\n#define MOUSE_DIR \"/usr/share/cursor/\"\n\tload_sprite(&yg->mouse_sprite, MOUSE_DIR \"normal.png\");\n\tload_sprite(&yg->mouse_sprite_drag, MOUSE_DIR \"grab.png\");\n\tload_sprite(&yg->mouse_sprite_resize_v, MOUSE_DIR \"resize-vertical.png\");\n\tload_sprite(&yg->mouse_sprite_resize_h, MOUSE_DIR \"resize-horizontal.png\");\n\tload_sprite(&yg->mouse_sprite_resize_da, MOUSE_DIR \"resize-uldr.png\");\n\tload_sprite(&yg->mouse_sprite_resize_db, MOUSE_DIR \"resize-dlur.png\");\n\tload_sprite(&yg->mouse_sprite_point, MOUSE_DIR \"point.png\");\n\tload_sprite(&yg->mouse_sprite_ibeam, MOUSE_DIR \"ibeam.png\");\n\tTRACE(\"Done.\");\n\n\tTRACE(\"Initializing variables...\");\n\tyg->last_mouse_x = 0;\n\tyg->last_mouse_y = 0;\n\tyg->mouse_x = yg->width * MOUSE_SCALE / 2;\n\tyg->mouse_y = yg->height * MOUSE_SCALE / 2;\n\n\tyg->windows = list_create();\n\tyg->wids_to_windows = hashmap_create_int(10);\n\tyg->key_binds = hashmap_create_int(10);\n\tyg->clients_to_windows = hashmap_create_int(10);\n\tyg->mid_zs = list_create();\n\tyg->menu_zs = list_create();\n\tyg->overlay_zs = list_create();\n\tyg->windows_to_remove = list_create();\n\tyg->windows_to_minimize = list_create();\n\tyg->minimized_zs = list_create();\n\n\tyg->window_subscribers = list_create();\n\n\tyg->last_mouse_buttons = 0;\n\tyg->resize_release_time = 0;\n\tTRACE(\"Done.\");\n\n\tyutani_clip_init(yg);\n\n\tif (!fork()) {\n\t\tif (argx < argc) {\n\t\t\tTRACE(\"Starting alternate startup app: %s\", argv[argx]);\n\t\t\texecvp(argv[argx], &argv[argx]);\n\t\t} else {\n\t\t\tTRACE(\"Starting application\");\n\t\t\tchar * args[] = {\"/bin/glogin\", NULL};\n\t\t\texecvp(args[0], args);\n\t\t\tTRACE(\"Failed to start app?\");\n\t\t}\n\t}\n\n\tint fds[4];\n\tint mfd = -1;\n\tint kfd = -1;\n\tint amfd = -1;\n\tint vmmouse = 0;\n\tmouse_device_packet_t packet;\n\tkey_event_t event;\n\tkey_event_state_t state = {0};\n\n\tfds[0] = fileno(server);\n\n\tif (yutani_options.nested) {\n\t\tfds[1] = fileno(yg->host_context->sock);\n\t} else {\n\t\tmfd = open(\"/dev/mouse\", O_RDONLY);\n\t\tkfd = open(\"/dev/kbd\", O_RDONLY);\n\t\tamfd = open(\"/dev/absmouse\", O_RDONLY);\n\t\tif (amfd < 0) {\n\t\t\tamfd = open(\"/dev/vmmouse\", O_RDONLY);\n\t\t\tvmmouse = 1;\n\t\t}\n\t\tyg->vbox_rects = open(\"/dev/vboxrects\", O_WRONLY);\n\t\tyg->vbox_pointer = open(\"/dev/vboxpointer\", O_WRONLY);\n\n\t\tfds[1] = mfd;\n\t\tfds[2] = kfd;\n\t\tfds[3] = amfd;\n\t}\n\n\tuint64_t last_redraw = 0;\n\n\twhile (1) {\n\n\t\tunsigned long frameTime = yutani_time_since(yg, last_redraw);\n\t\tif (frameTime > 15) {\n\t\t\tredraw_windows(yg);\n\t\t\tlast_redraw = yutani_current_time(yg);\n\t\t\tframeTime = 0;\n\t\t}\n\n\t\tif (yutani_options.nested) {\n\t\t\tint index = fswait2(2, fds, 16 - frameTime);\n\n\t\t\tif (index == 1) {\n\t\t\t\tyutani_msg_t * m = yutani_poll(yg->host_context);\n\t\t\t\tif (m) {\n\t\t\t\t\tswitch (m->type) {\n\t\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\t\tyutani_msg_buildx_key_event_alloc(m_);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_key_event(m_, 0, &ke->event, &ke->state);\n\t\t\t\t\t\t\t\thandle_key_event(yg, (struct yutani_msg_key_event *)m_->data);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\t\t\tmouse_device_packet_t packet;\n\n\t\t\t\t\t\t\t\tpacket.buttons = me->buttons;\n\t\t\t\t\t\t\t\tpacket.x_difference = me->new_x;\n\t\t\t\t\t\t\t\tpacket.y_difference = me->new_y;\n\n\t\t\t\t\t\t\t\tyg->last_mouse_buttons = packet.buttons;\n\n\t\t\t\t\t\t\t\tyutani_msg_buildx_mouse_event_alloc(m_);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_mouse_event(m_, 0, &packet, YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE);\n\t\t\t\t\t\t\t\thandle_mouse_event(yg, (struct yutani_msg_mouse_event *)m_->data);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\t\t\tTRACE(\"Resize request from host compositor for size %dx%d\", wr->width, wr->height);\n\t\t\t\t\t\t\t\tyutani_window_resize_accept(yg->host_context, yg->host_window, wr->width, wr->height);\n\t\t\t\t\t\t\t\tyg->resize_on_next = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tTRACE(\"Host session ended. Should exit.\");\n\t\t\t\t\t\t\t\tyutani_msg_buildx_session_end_alloc(response);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_session_end(response);\n\t\t\t\t\t\t\t\tpex_broadcast(server, response->size, (char *)response);\n\t\t\t\t\t\t\t\tyg->server = NULL;\n\t\t\t\t\t\t\t\texit(0);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tcontinue;\n\t\t\t} else if (index > 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\tint index = fswait2(amfd == -1 ? 3 : 4, fds, 16 - frameTime);\n\n\t\t\tif (index == 2) {\n\t\t\t\tunsigned char buf[1];\n\t\t\t\tint r = read(kfd, buf, 1);\n\t\t\t\tif (r > 0) {\n\t\t\t\t\tif (kbd_scancode(&state, buf[0], &event)) {\n\t\t\t\t\t\tyutani_msg_buildx_key_event_alloc(m);\n\t\t\t\t\t\tyutani_msg_buildx_key_event(m,0, &event, &state);\n\t\t\t\t\t\thandle_key_event(yg, (struct yutani_msg_key_event *)m->data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (index == 1) {\n\t\t\t\tint r = read(mfd, (char *)&packet, sizeof(mouse_device_packet_t));\n\t\t\t\tif (r > 0) {\n\t\t\t\t\tyg->last_mouse_buttons = packet.buttons;\n\t\t\t\t\tyutani_msg_buildx_mouse_event_alloc(m);\n\t\t\t\t\tyutani_msg_buildx_mouse_event(m,0, &packet, YUTANI_MOUSE_EVENT_TYPE_RELATIVE);\n\t\t\t\t\thandle_mouse_event(yg, (struct yutani_msg_mouse_event *)m->data);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (amfd != -1 && index == 3) {\n\t\t\t\tint r = read(amfd, (char *)&packet, sizeof(mouse_device_packet_t));\n\t\t\t\tif (r > 0) {\n\t\t\t\t\tif (!vmmouse) {\n\t\t\t\t\t\tpacket.buttons = yg->last_mouse_buttons & 0xF;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tyg->last_mouse_buttons = packet.buttons;\n\t\t\t\t\t}\n\t\t\t\t\tyutani_msg_buildx_mouse_event_alloc(m);\n\t\t\t\t\tyutani_msg_buildx_mouse_event(m,0, &packet, YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE);\n\t\t\t\t\thandle_mouse_event(yg, (struct yutani_msg_mouse_event *)m->data);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t} else if (index > 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tpex_packet_t * p = calloc(PACKET_SIZE, 1);\n\t\tpex_listen(server, p);\n\n\t\tyutani_msg_t * m = (yutani_msg_t *)p->data;\n\n\t\tif (p->size == 0) {\n\t\t\t/* Connection closed for client */\n\t\t\tTRACE(\"Connection closed for client  %x\", p->source);\n\n\t\t\tlist_t * client_list = hashmap_get(yg->clients_to_windows, (void *)p->source);\n\t\t\tif (client_list) {\n\t\t\t\tforeach(node, client_list) {\n\t\t\t\t\tyutani_server_window_t * win = node->value;\n\t\t\t\t\tTRACE(\"Killing window %d\", win->wid);\n\t\t\t\t\twindow_mark_for_close(yg, win);\n\t\t\t\t}\n\t\t\t\thashmap_remove(yg->clients_to_windows, (void *)p->source);\n\t\t\t\tlist_free(client_list);\n\t\t\t\tfree(client_list);\n\t\t\t}\n\n\t\t\tif (hashmap_is_empty(yg->clients_to_windows)) {\n\t\t\t\tTRACE(\"Last compositor client disconnected, exiting.\");\n\t\t\t\tyg->server = NULL;\n\t\t\t\texit(0);\n\t\t\t}\n\n\t\t\tfree(p);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (m->magic != YUTANI_MSG__MAGIC) {\n\t\t\tTRACE(\"Message has bad magic. (Should eject client, but will instead skip this message.) 0x%x\", m->magic);\n\t\t\tfree(p);\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch(m->type) {\n\t\t\tcase YUTANI_MSG_HELLO:\n\t\t\t\t{\n\t\t\t\t\tTRACE(\"And hello to you, %p!\", p->source);\n\t\t\t\t\tlist_t * client_list = hashmap_get(yg->clients_to_windows, (void *)p->source);\n\t\t\t\t\tif (!client_list) {\n\t\t\t\t\t\tTRACE(\"Client is new: %p\", p->source);\n\t\t\t\t\t\tclient_list = list_create();\n\t\t\t\t\t\thashmap_set(yg->clients_to_windows, (void *)p->source, client_list);\n\t\t\t\t\t}\n\t\t\t\t\tyutani_msg_buildx_welcome_alloc(response);\n\t\t\t\t\tyutani_msg_buildx_welcome(response,yg->width, yg->height);\n\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_NEW:\n\t\t\tcase YUTANI_MSG_WINDOW_NEW_FLAGS:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_new_flags * wn = (void *)m->data;\n\t\t\t\t\tTRACE(\"Client %p requested a new window (%dx%d).\", p->source, wn->width, wn->height);\n\t\t\t\t\tyutani_server_window_t * w = server_window_create(yg, wn->width, wn->height, p->source, m->type != YUTANI_MSG_WINDOW_NEW ? wn->flags : 0);\n\n\t\t\t\t\tif (w->server_flags & YUTANI_WINDOW_FLAG_PARENT_WID) {\n\t\t\t\t\t\tTRACE(\"Set parent wid of %d to %d\\n\", w->wid, wn->parent_wid);\n\t\t\t\t\t\tw->parent = wn->parent_wid;\n\t\t\t\t\t}\n\n\t\t\t\t\tyutani_msg_buildx_window_init_alloc(response);\n\t\t\t\t\tyutani_msg_buildx_window_init(response,w->wid, w->width, w->height, w->bufid);\n\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\n\t\t\t\t\tif (!(w->server_flags & YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS)) {\n\t\t\t\t\t\tset_focused_window(yg, w);\n\t\t\t\t\t}\n\n\t\t\t\t\tnotify_subscribers(yg);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_FLIP:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_flip * wf = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wf->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\twindow_reveal(yg, w);\n\t\t\t\t\t\tmark_window(yg, w);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_FLIP_REGION:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_flip_region * wf = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wf->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\twindow_reveal(yg, w);\n\t\t\t\t\t\tmark_window_relative(yg, w, wf->x, wf->y, wf->width, wf->height);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t{\n\t\t\t\t\t/* XXX Verify this is from a valid device client */\n\t\t\t\t\tstruct yutani_msg_key_event * ke = (void *)m->data;\n\t\t\t\t\thandle_key_event(yg, ke);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_MOUSE_EVENT:\n\t\t\t\t{\n\t\t\t\t\t/* XXX Verify this is from a valid device client */\n\t\t\t\t\tstruct yutani_msg_mouse_event * me = (void *)m->data;\n\t\t\t\t\thandle_mouse_event(yg, me);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_MOVE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_move * wm = (void *)m->data;\n\t\t\t\t\t//TRACE(\"%08x wanted to move window %d to %d, %d\", p->source, wm->wid, (int)wm->x, (int)wm->y);\n\t\t\t\t\tif (wm->x > (int)yg->width + 100 || wm->x < -(int)yg->width || wm->y > (int)yg->height + 100 || wm->y < -(int)yg->height) {\n\t\t\t\t\t\tTRACE(\"Refusing to move window to these coordinates.\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tyutani_server_window_t * win = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid);\n\t\t\t\t\tif (win) {\n\t\t\t\t\t\twindow_move(yg, win, wm->x, wm->y);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tTRACE(\"%08x wanted to move window %d, but I can't find it?\", p->source, wm->wid);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_MOVE_RELATIVE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_move_relative * wm = (void *)m->data;\n\n\t\t\t\t\tyutani_server_window_t * movee = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid_to_move);\n\t\t\t\t\tyutani_server_window_t * base  = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid_base);\n\n\t\t\t\t\tif (!movee || !base) break;\n\n\t\t\t\t\t/* Map coordinate to new origin location */\n\t\t\t\t\tint32_t nx, ny;\n\t\t\t\t\tyutani_window_to_device(base, wm->x + movee->width / 2, wm->y + movee->height / 2, &nx, &ny);\n\t\t\t\t\twindow_move(yg, movee, nx - movee->width / 2, ny - movee->height / 2);\n\n\t\t\t\t\t/* Match window rotation to base window */\n\t\t\t\t\tmovee->rotation = base->rotation;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_SET_PARENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_set_parent * wsp = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * movee = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wsp->wid);\n\t\t\t\t\tif (!movee) break;\n\t\t\t\t\t/* XXX Should we disallow parenting to a window owned by another client?\n\t\t\t\t\t *     Validate that the window even exists?\n\t\t\t\t\t *     For now, just accept any wid, whatever. */\n\t\t\t\t\tmovee->parent = wsp->parent_wid;\n\t\t\t\t\tnotify_subscribers(yg);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_close * wc = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wc->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\twindow_mark_for_close(yg, w);\n\t\t\t\t\t\twindow_remove_from_client(yg, w);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_STACK:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_stack * ws = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)ws->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\treorder_window(yg, w, ws->z);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_RESIZE_REQUEST:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tyutani_msg_buildx_window_resize_alloc(response);\n\t\t\t\t\t\tyutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0, w->tiled);\n\t\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tyutani_msg_buildx_window_resize_alloc(response);\n\t\t\t\t\t\tyutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0, w->tiled);\n\t\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_RESIZE_ACCEPT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tuint32_t newbufid = server_window_resize(yg, w, wr->width, wr->height);\n\t\t\t\t\t\tyutani_msg_buildx_window_resize_alloc(response);\n\t\t\t\t\t\tyutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_BUFID, w->wid, wr->width, wr->height, newbufid, 0);\n\t\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_RESIZE_DONE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tserver_window_resize_finish(yg, w, wr->width, wr->height);\n\t\t\t\t\t\tnotify_subscribers(yg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_QUERY_WINDOWS:\n\t\t\t\t{\n\t\t\t\t\tforeach (node, yg->minimized_zs) {\n\t\t\t\t\t\tyutani_query_result(yg, p->source, node->value);\n\t\t\t\t\t}\n\t\t\t\t\tyutani_query_result(yg, p->source, yg->bottom_z);\n\t\t\t\t\tforeach (node, yg->mid_zs) {\n\t\t\t\t\t\tyutani_query_result(yg, p->source, node->value);\n\t\t\t\t\t}\n\t\t\t\t\t/* Exclude menus, overlay windows, top and bottom. */\n\t\t\t\t\tyutani_query_result(yg, p->source, yg->top_z);\n\t\t\t\t\tyutani_msg_buildx_window_advertise_alloc(response, 0);\n\t\t\t\t\tyutani_msg_buildx_window_advertise(response,0, 0, 0, 0, 0, 0, 0, NULL);\n\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_SUBSCRIBE:\n\t\t\t\t{\n\t\t\t\t\tforeach(node, yg->window_subscribers) {\n\t\t\t\t\t\tif ((uintptr_t)node->value == p->source) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlist_insert(yg->window_subscribers, (void*)p->source);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_UNSUBSCRIBE:\n\t\t\t\t{\n\t\t\t\t\tnode_t * node = list_find(yg->window_subscribers, (void*)p->source);\n\t\t\t\t\tif (node) {\n\t\t\t\t\t\tlist_delete(yg->window_subscribers, node);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_ADVERTISE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_advertise * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tif (w->client_strings) free(w->client_strings);\n\n\t\t\t\t\t\tw->client_icon    = wa->icon;\n\t\t\t\t\t\tw->client_flags   = wa->flags;\n\t\t\t\t\t\tw->client_length  = wa->size;\n\t\t\t\t\t\tw->client_strings = malloc(wa->size);\n\t\t\t\t\t\tmemcpy(w->client_strings, wa->strings, wa->size);\n\n\t\t\t\t\t\tnotify_subscribers(yg);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t{\n\t\t\t\t\tyutani_msg_buildx_session_end_alloc(response);\n\t\t\t\t\tyutani_msg_buildx_session_end(response);\n\t\t\t\t\tpex_broadcast(server, response->size, (char *)response);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_FOCUS:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_focus * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tset_focused_window(yg, w);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_KEY_BIND:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_key_bind * wa = (void *)m->data;\n\t\t\t\t\tadd_key_bind(yg, wa, p->source);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_DRAG_START:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_drag_start * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\t/* Start dragging */\n\t\t\t\t\t\tmouse_start_drag(yg, w);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_UPDATE_SHAPE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_update_shape * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\t/* Set shape parameter */\n\t\t\t\t\t\tserver_window_update_shape(yg, w, wa->set_shape);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_WARP_MOUSE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_warp_mouse * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tif (yg->focused_window == w) {\n\t\t\t\t\t\t\tint32_t x, y;\n\t\t\t\t\t\t\tyutani_window_to_device(w, wa->x, wa->y, &x, &y);\n\n\t\t\t\t\t\t\tstruct yutani_msg_mouse_event me;\n\t\t\t\t\t\t\tme.event.x_difference = x;\n\t\t\t\t\t\t\tme.event.y_difference = y;\n\t\t\t\t\t\t\tme.event.buttons = yg->last_mouse_buttons;\n\t\t\t\t\t\t\tme.type = YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE;\n\t\t\t\t\t\t\tme.wid = wa->wid;\n\n\t\t\t\t\t\t\thandle_mouse_event(yg, &me);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_SHOW_MOUSE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_show_mouse * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tif (wa->show_mouse == -1) {\n\t\t\t\t\t\t\tw->show_mouse = w->default_mouse;\n\t\t\t\t\t\t} else if (wa->show_mouse < 2) {\n\t\t\t\t\t\t\tw->default_mouse = wa->show_mouse;\n\t\t\t\t\t\t\tw->show_mouse = wa->show_mouse;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tw->show_mouse = wa->show_mouse;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (yg->focused_window == w) {\n\t\t\t\t\t\t\tmark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_RESIZE_START:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize_start * wa = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tif (yg->focused_window == w && !yg->resizing_window) {\n\t\t\t\t\t\t\tyg->resizing_window = w;\n\t\t\t\t\t\t\tyg->resizing_button = YUTANI_MOUSE_BUTTON_LEFT; /* XXX Uh, what if we used something else */\n\t\t\t\t\t\t\tmouse_start_resize(yg, wa->direction);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_SPECIAL_REQUEST:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_special_request * sr = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)sr->wid);\n\t\t\t\t\tswitch (sr->request) {\n\t\t\t\t\t\tcase YUTANI_SPECIAL_REQUEST_MAXIMIZE:\n\t\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\t\tif (w->tiled) {\n\t\t\t\t\t\t\t\t\twindow_untile(yg,w);\n\t\t\t\t\t\t\t\t\twindow_move(yg,w,w->untiled_left,w->untiled_top);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\twindow_tile(yg, w, 1, 1, 0, 0);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE:\n\t\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\t\tyutani_msg_buildx_window_close_alloc(response);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_window_close(response, w->wid);\n\t\t\t\t\t\t\t\tpex_send(yg->server, w->owner, response->size, (char *)response);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_SPECIAL_REQUEST_MINIMIZE:\n\t\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\t\twindow_minimize(yg,w);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_SPECIAL_REQUEST_CLIPBOARD:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tyutani_msg_buildx_clipboard_alloc(response, yg->clipboard_size);\n\t\t\t\t\t\t\t\tyutani_msg_buildx_clipboard(response, yg->clipboard);\n\t\t\t\t\t\t\t\tpex_send(server, p->source, response->size, (char *)response);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tTRACE(\"Unknown special request type: 0x%x\", sr->request);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_CLIPBOARD:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_clipboard * cb = (void *)m->data;\n\t\t\t\t\tyg->clipboard_size = min(cb->size, 511);\n\t\t\t\t\tmemcpy(yg->clipboard, cb->content, yg->clipboard_size);\n\t\t\t\t\tyg->clipboard[yg->clipboard_size] = '\\0';\n\t\t\t\t\tTRACE(\"Copied text to clipbard (size=%d)\", yg->clipboard_size);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_PANEL_SIZE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_panel_size * ps = (void *)m->data;\n\t\t\t\t\tyutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)ps->wid);\n\t\t\t\t\tif (w) {\n\t\t\t\t\t\tw->icon_x = ps->x;\n\t\t\t\t\t\tw->icon_y = ps->y;\n\t\t\t\t\t\tw->icon_w = ps->w;\n\t\t\t\t\t\tw->icon_h = ps->h;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t{\n\t\t\t\t\tTRACE(\"Unknown type: 0x%8x\", m->type);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t\tfree(p);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/cp.c",
    "content": "/**\n * @brief cp - Copy files\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <dirent.h>\n#include <unistd.h>\n#include <errno.h>\n\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n\n#define CHUNK_SIZE 4096\n\n#ifndef IS_MV\n#define APP_NAME \"cp\"\nstatic int recursive = 0;\n#endif\nstatic int symlinks = 0;\nstatic int copy_thing(char * tmp, char * tmp2);\n\nstatic int copy_link(char * source, char * dest, int mode, int uid, int gid) {\n\t//fprintf(stderr, \"need to copy link %s to %s\\n\", source, dest);\n\tchar tmp[1024];\n\treadlink(source, tmp, 1024);\n\tsymlink(tmp, dest);\n\tchmod(dest, mode);\n\tchown(dest, uid, gid);\n\n\treturn 0;\n}\n\nstatic int copy_file(char * source, char * dest, int mode,int uid, int gid) {\n\t//fprintf(stderr, \"need to copy file %s to %s %x\\n\", source, dest, mode);\n\n\tint s_fd = open(source, O_RDONLY);\n\tif (s_fd < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", source, strerror(errno));\n\t\treturn 1;\n\t}\n\n\tint d_fd = open(dest, O_WRONLY | O_CREAT, mode);\n\tif (d_fd < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", dest, strerror(errno));\n\t\treturn 1;\n\t}\n\n\tssize_t length;\n\tlength = lseek(s_fd, 0, SEEK_END);\n\tif (length < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", source, strerror(errno));\n\t\treturn 1;\n\t}\n\n\tlseek(s_fd, 0, SEEK_SET);\n\n\t//fprintf(stderr, \"%d bytes to copy\\n\", length);\n\n\tchar buf[CHUNK_SIZE];\n\n\twhile (length > 0) {\n\t\tssize_t r = read(s_fd, buf, length < CHUNK_SIZE ? length : CHUNK_SIZE);\n\t\tif (r < 0) {\n\t\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", source, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\t//fprintf(stderr, \"copying %d bytes from %s to %s\\n\", r, source, dest);\n\t\tssize_t w = write(d_fd, buf, r);\n\t\tif (w < 0) {\n\t\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", dest, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\tlength -= r; /* Actually should be -w, but let's not get into that now... this should probably use stdio anyway */\n\t\t//fprintf(stderr, \"%d bytes remaining\\n\", length);\n\t}\n\n\tclose(s_fd);\n\tclose(d_fd);\n\n\tif (chown(dest, uid, gid) < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", dest, strerror(errno));\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int copy_directory(char * source, char * dest, int mode, int uid, int gid) {\n\tDIR * dirp = opendir(source);\n\tif (dirp == NULL) {\n\t\tfprintf(stderr, \"Failed to copy directory %s\\n\", source);\n\t\treturn 1;\n\t}\n\n\t//fprintf(stderr, \"Creating %s\\n\", dest);\n\tif (!strcmp(dest, \"/\")) {\n\t\tdest = \"\";\n\t} else {\n\t\tmkdir(dest, mode);\n\t}\n\n\tint ret = 0;\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (!strcmp(ent->d_name,\".\") || !strcmp(ent->d_name,\"..\")) {\n\t\t\t//fprintf(stderr, \"Skipping %s\\n\", ent->d_name);\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\t//fprintf(stderr, \"not skipping %s/%s → %s/%s\\n\", source, ent->d_name, dest, ent->d_name);\n\t\tchar tmp[strlen(source)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp, \"%s/%s\", source, ent->d_name);\n\t\tchar tmp2[strlen(dest)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp2, \"%s/%s\", dest, ent->d_name);\n\t\t//fprintf(stderr,\"%s → %s\\n\", tmp, tmp2);\n\t\tret |= copy_thing(tmp,tmp2);\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tchown(dest, uid, gid);\n\n\treturn ret;\n}\n\nstatic int copy_thing(char * tmp, char * tmp2) {\n\tstruct stat statbuf;\n\tint ret = symlinks ? lstat(tmp, &statbuf) : stat(tmp, &statbuf);\n\tif (ret < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", tmp, strerror(errno));\n\t\treturn 1;\n\t}\n\tif (S_ISLNK(statbuf.st_mode)) {\n\t\treturn copy_link(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t} else if (S_ISDIR(statbuf.st_mode)) {\n\t\tif (!recursive) {\n\t\t\tfprintf(stderr, APP_NAME \": %s: omitting directory\\n\", tmp);\n\t\t\treturn 1;\n\t\t}\n\t\treturn copy_directory(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t} else if (S_ISREG(statbuf.st_mode)) {\n\t\treturn copy_file(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t} else {\n\t\tfprintf(stderr, APP_NAME \": %s is not any of the required file types?\\n\", tmp);\n\t\treturn 1;\n\t}\n}\n\n#ifndef IS_MV\nstatic int copy_top_level(char **argv, int argc, int optind) {\n\tchar * destination = argv[argc-1];\n\tdestination = *destination ? destination : \".\";\n\n\tint ret = 0;\n\n\tstruct stat statbuf;\n\tstat((destination), &statbuf);\n\tif (S_ISDIR(statbuf.st_mode)) {\n\t\twhile (optind < argc - 1) {\n\t\t\tchar * source = strrchr(argv[optind], '/');\n\t\t\tif (!source) source = argv[optind];\n\t\t\tchar output[4096];\n\t\t\tsprintf(output, \"%s/%s\", destination, source);\n\t\t\tret |= copy_thing(argv[optind], output);\n\t\t\toptind++;\n\t\t}\n\t} else {\n\t\tif (optind < argc - 2) {\n\t\t\tfprintf(stderr, APP_NAME \": target '%s' is not a directory\\n\", destination);\n\t\t\treturn 1;\n\t\t}\n\t\tret |= copy_thing(argv[optind], destination);\n\t}\n\n\treturn ret;\n}\n\nint main(int argc, char ** argv) {\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"RrP\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'R':\n\t\t\tcase 'r':\n\t\t\t\trecursive = 1;\n\t\t\t\tsymlinks = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'P':\n\t\t\t\tsymlinks = 0;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"cp: unrecognized option '%c'\\n\", opt);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind < argc - 1) {\n\t\treturn copy_top_level(argv, argc, optind);\n\t} else {\n\t\tfprintf(stderr, \"cp: not enough arguments\\n\");\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n#endif\n"
  },
  {
    "path": "apps/cpu-name.krk",
    "content": "#!/bin/kuroko\n\nimport fileio\n\nlet lines\nlet cpus = {0: {}}\nlet current = 0\n\nwith fileio.open('/proc/cpuinfo','r') as f:\n    lines = f.readlines()\n\nfor line in lines:\n    if line == '\\n':\n        current++\n        cpus[current] = {}\n        continue\n    line = line.strip()\n    if ': ' in line:\n        let key, value = line.split(': ')\n        cpus[current][key] = value\n\nif cpus and 'Model name' in cpus[0]:\n    print(cpus[0]['Model name'])\nelse if cpus and 'PartNum' in cpus[0]:\n    # ARM\n    let manuf = {\n        0x00: 'Masked',\n        0x41: 'ARM',\n        0x61: 'Apple',\n    }\n    let parts = {\n        0x000: 'by hypervisor',\n\n        0xD02: 'Cortex-A34',\n        0xD03: 'Cortex-A53',\n        0xD04: 'Cortex-A35',\n        0xD05: 'Cortex-A55',\n        0xD07: 'Cortex-A57',\n        0xD08: 'Cortex-A72',\n        0xD09: 'Cortex-A73',\n\n        # Apple stuff\n        0x022: 'M1', # Icestorm core\n        0x023: 'M1', # Firestorm core\n    }\n    print(\n        manuf.get(int(cpus[0]['Implementer'],0),cpus[0]['Implementer']),\n        parts.get(int(cpus[0]['PartNum'],0),cpus[0]['PartNum'])\n    )\n"
  },
  {
    "path": "apps/cpuwidget.c",
    "content": "/**\n * @brief System monitor tool\n *\n * Displays CPU usage, memory usage, and network usage, with nice\n * curvy anti-aliased graphs that scroll smoothly.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <time.h>\n#include <sched.h>\n#include <math.h>\n\n#include <net/if.h>\n\n#include <sys/ioctl.h>\n#include <sys/times.h>\n#include <sys/fswait.h>\n#include <sys/sysfunc.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/text.h>\n#include <toaru/menu.h>\n\nstatic int left, top, width, height;\n\nstatic struct menu_bar menu_bar = {0};\nstatic struct menu_bar_entries menu_entries[] = {\n\t{\"File\", \"file\"},\n\t{\"Help\", \"help\"},\n\t{NULL, NULL},\n};\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx_base;\n\nstatic gfx_context_t * ctx_cpu;\nstatic gfx_context_t * ctx_mem;\nstatic gfx_context_t * ctx_net;\n\nstatic int left_pad = 0;\nstatic int h_pad = 0;\nstatic int top_pad = 19;\nstatic int bottom_pad = 34;\nstatic int graph_height;\n\nstatic struct TT_Font * tt_thin = NULL;\nstatic struct TT_Font * tt_bold = NULL;\n\nuint32_t hsv_to_rgb(float h, float s, float v) {\n\tfloat c  = v * s;\n\tfloat hp = fmod(h, 2 * M_PI);\n\tfloat x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0));\n\tfloat m = v - c;\n\tfloat rp, gp, bp;\n\tif (hp <= 1.0472)      { rp = c; gp = x; bp = 0; }\n\telse if (hp <= 2.0944) { rp = x; gp = c; bp = 0; }\n\telse if (hp <= 3.1416) { rp = 0; gp = c; bp = x; }\n\telse if (hp <= 4.1888) { rp = 0; gp = x; bp = c; }\n\telse if (hp <= 5.2360) { rp = x; gp = 0; bp = c; }\n\telse                   { rp = c; gp = 0; bp = x; }\n\treturn rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255);\n}\n\n\nstatic int should_exit = 0;\nstatic clock_t last_redraw = 0;\nstatic int cpu_count = 1;\n\nstatic void get_cpu_info(int cpus[]) {\n\tFILE * f = fopen(\"/proc/idle\",\"r\");\n\tchar buf[4096];\n\tfread(buf, 4096, 1, f);\n\n\tchar * buffer = buf;\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\t/* pid */\n\t\tchar * a = strchr(buffer, ':');\n\t\ta++;\n\t\tcpus[i] = strtoul(a, &a, 10);\n\t\tcpus[i] += strtoul(a, &a, 10);\n\t\tcpus[i] += strtoul(a, &a, 10);\n\t\tcpus[i] += strtoul(a, &a, 10);\n\t\tcpus[i] /= 4;\n\n\t\tif (cpus[i] < 0) cpus[i] = 0;\n\t\tif (cpus[i] > 1000) cpus[i] = 1000;\n\t\tbuffer = strchr(a, '\\n');\n\t}\n\n\tfclose(f);\n}\n\nstatic uint32_t colors[32];\nstatic uint32_t if_colors[32];\n\n#define EASE_WIDTH 8\n\nstatic void plot_graph(gfx_context_t * ctx, size_t scale, long samples[100], uint32_t color, float shift) {\n\tfloat unit_width = (float)ctx->width / 99.0;\n\tfloat factor[EASE_WIDTH];\n\tfor (int k = 0; k < EASE_WIDTH; ++k) {\n\t\tfactor[k] = (cos(M_PI * ((float)k / (float)(EASE_WIDTH-1))) + 1.0) / 2.0;\n\t}\n\n\tstruct TT_Contour * contour = NULL;\n\tsize_t first = 1;\n\tfor (int j = 1; j < 100; ++j) {\n\t\tif (samples[j-1] == -1) {\n\t\t\tfirst++;\n\t\t\tcontinue;\n\t\t}\n\t\tfloat start = (float)ctx->width * (float)(j - 1) / 99.0 + shift;\n\n\t\tsize_t old = samples[j-1];\n\t\tsize_t new = samples[j];\n\n\t\tif (old > scale) old = scale;\n\t\tif (new > scale) new = scale;\n\n\t\tfloat nsamples[EASE_WIDTH];\n\t\tfor (int k = 0; k < EASE_WIDTH; ++k) {\n\t\t\tfloat value = old * factor[k] + new * (1.0 - factor[k]);\n\t\t\tnsamples[k] =  ((scale - value) * ((float)ctx->height - 1) / (float)scale);\n\t\t}\n\n\t\tif (!contour) {\n\t\t\tcontour = tt_contour_start(start, nsamples[0]);\n\t\t}\n\n\t\tfor (int k = 1; k < EASE_WIDTH; ++k) {\n\t\t\tcontour = tt_contour_line_to(contour, start + unit_width * ((float)k / (float)(EASE_WIDTH-1)), nsamples[k]);\n\t\t}\n\t}\n\n\tif (!contour) return;\n\n\tstruct TT_Shape * stroke = tt_contour_stroke_shape(contour, 0.5);\n\ttt_path_paint(ctx, stroke, color);\n\tfree(stroke);\n\n\tcontour = tt_contour_line_to(contour, ctx->width + shift, ctx->height);\n\tcontour = tt_contour_line_to(contour, (float)ctx->width * (float)(first - 1) / 99.0 + shift, ctx->height);\n\n\tstruct TT_Shape * shape = tt_contour_finish(contour);\n\n\tuint32_t c = premultiply(rgba(_RED(color),_GRE(color),_BLU(color),_ALP(color) * 0.25));\n\ttt_path_paint(ctx, shape, c);\n\tfree(shape);\n\tfree(contour);\n}\n\nstatic long cpu_samples[32][100];\n\nstatic void draw_lines(gfx_context_t * ctx) {\n\tfloat unit_width = (float)ctx->width / 99.0;\n\tfor (int i = 1; i < 10; i++) {\n\t\tstruct TT_Contour * line = tt_contour_start((int)(unit_width * 10.0 * i) + 0.5, 0);\n\t\tline = tt_contour_line_to(line, (int)(unit_width * 10.0 * i) + 0.5, ctx->height);\n\t\tstruct TT_Shape * shape = tt_contour_stroke_shape(line, 0.5);\n\t\tfree(line);\n\t\ttt_path_paint(ctx, shape, rgb(150,150,150));\n\t\tfree(shape);\n\t}\n}\n\nstatic void draw_cpu_graphs(gfx_context_t * ctx, float shift) {\n\tdraw_fill(ctx, rgb(0xF8,0xF8,0xF8));\n\tdraw_lines(ctx);\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\tplot_graph(ctx, 1000, cpu_samples[i], colors[i], shift);\n\t}\n}\n\nstatic void next_cpu(gfx_context_t * ctx) {\n\tint cpus_new[32];\n\tget_cpu_info(cpus_new);\n\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\tmemmove(&cpu_samples[i][0], &cpu_samples[i][1], 99 * sizeof(long));\n\t\tcpu_samples[i][99] = 1000-cpus_new[i];\n\t}\n\n\tdraw_cpu_graphs(ctx, 0.0);\n}\n\nstatic void get_mem_info(int * total, int * used) {\n\tFILE * f = fopen(\"/proc/meminfo\", \"r\");\n\tif (!f) return;\n\tint free;\n\tchar buf[1024] = {0};\n\tfgets(buf, 1024, f);\n\n\tchar * a, * b;\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\t*total = atoi(a);\n\tfgets(buf, 1024, f);\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\tfree = atoi(a);\n\t*used = *total - free;\n\n\tfclose(f);\n}\n\nstatic long mem_samples[100];\nstatic long mem_total;\nstatic void draw_mem_graphs(gfx_context_t * ctx, float shift) {\n\tdraw_fill(ctx, rgb(0xF8,0xF8,0xF8));\n\tdraw_lines(ctx);\n\tplot_graph(ctx, mem_total, mem_samples, rgb(250,110,240), shift);\n}\n\nstatic void next_mem(gfx_context_t * ctx) {\n\tstatic int total = 0;\n\tstatic int old_use = 0;\n\tint mem_use = 0;\n\tget_mem_info(&total, &mem_use);\n\n\tif (!old_use) {\n\t\told_use = mem_use;\n\t\treturn;\n\t}\n\n\tmemmove(&mem_samples[0], &mem_samples[1], 99 * sizeof(long));\n\tmem_total = total;\n\tmem_samples[99] = mem_use;\n\tdraw_mem_graphs(ctx, 0.0);\n\n\told_use = mem_use;\n}\n\nstatic char ifnames[32][256];\n\nstatic int count_interfaces(void) {\n\tint count = 0;\n\tDIR * d = opendir(\"/dev/net\");\n\tif (!d) {\n\t\treturn 0;\n\t}\n\n\tstruct dirent * ent;\n\twhile ((ent = readdir(d))) {\n\t\tif (ent->d_name[0] == '.') continue;\n\t\tsnprintf(ifnames[count>>1], 255, ent->d_name);\n\t\tcount += 2;\n\t}\n\n\tclosedir(d);\n\treturn count;\n}\n\nstatic void refresh_interfaces(size_t ifs[32]) {\n\tint ind = 0;\n\n\tDIR * d = opendir(\"/dev/net\");\n\tif (!d) return;\n\n\tstruct dirent * ent;\n\twhile ((ent = readdir(d))) {\n\t\tif (ent->d_name[0] == '.') continue;\n\t\tchar if_path[1024];\n\t\tsnprintf(if_path, 1023, \"/dev/net/%s\", ent->d_name);\n\t\tint netdev = open(if_path, O_RDONLY);\n\t\tif (netdev < 0) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tnetif_counters_t counts;\n\t\tif (!ioctl(netdev, SIOCGIFCOUNTS, &counts)) {\n\t\t\tifs[ind + 0] = counts.rx_bytes;\n\t\t\tifs[ind + 1] = counts.tx_bytes;\n\t\t\tind += 2;\n\t\t}\n\n\t\tclose(netdev);\n\t}\n\n\tclosedir(d);\n}\n\nstatic long net_samples[32][100];\nstatic size_t net_scale = 300 * 1024;\n\nstatic void redraw_net_scale(void);\n\nstatic int if_count = -1;\n\nstatic void draw_net_graphs(gfx_context_t * ctx, float shift) {\n\tdraw_fill(ctx, rgb(0xF8,0xF8,0xF8));\n\tdraw_lines(ctx);\n\tfor (int i = 0; i < if_count; ++i) {\n\t\tplot_graph(ctx, net_scale, net_samples[i], if_colors[i], shift);\n\t}\n}\n\nstatic void next_net(gfx_context_t * ctx) {\n\tstatic size_t old_ifs[32];\n\tstatic clock_t ticks_last = 0;\n\tsize_t new_ifs[32];\n\n\tif (!ticks_last) {\n\t\tticks_last = times(NULL);\n\t\trefresh_interfaces(old_ifs);\n\t\treturn;\n\t}\n\n\tclock_t ticks_now = times(NULL);\n\trefresh_interfaces(new_ifs);\n\n\tlong max = 0;\n\tfor (int i = 0; i < if_count; ++i) {\n\t\tfor (int j = 0; j < 99; ++j) {\n\t\t\tnet_samples[i][j] = net_samples[i][j+1];\n\t\t\tif (net_samples[i][j] != -1) {\n\t\t\t\tif (net_samples[i][j] > max) {\n\t\t\t\t\tmax = net_samples[i][j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* Kilobits... */\n\t\tsize_t use = (new_ifs[i] - old_ifs[i]) * 8 / 1024;\n\t\tuse *= CLOCKS_PER_SEC;\n\t\tuse /= (ticks_now - ticks_last);\n\n\t\tnet_samples[i][99] = use;\n\n\t\tif ((long)use > max) {\n\t\t\tmax = use;\n\t\t}\n\t}\n\n\tsize_t scale = max ? max : (300 * 1024);\n\tif (scale != net_scale) {\n\t\tnet_scale = scale;\n\t\tredraw_net_scale();\n\t}\n\n\tdraw_net_graphs(ctx, 0.0);\n\n\tmemcpy(old_ifs, new_ifs, sizeof(new_ifs));\n\tticks_last = ticks_now;\n}\n\nstatic void draw_legend_element(int which, int count, int index, uint32_t color, char * label) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\n\t/* Available display width */\n\tint legend_width = ctx_base->width - bounds.width - 40;\n\tif (legend_width <= 0) return;\n\n\t/* Calculate graph offset from the usual rule */\n\tint y = MENU_BAR_HEIGHT + bounds.top_height + (which + 1) * (top_pad + graph_height) + which * bottom_pad + 4;\n\n\t/* Space to give to each unit. */\n\tint unit_width = legend_width / count;\n\n\t/* Left offset of this unit */\n\tint unit_x = unit_width * index + bounds.left_width + 10;\n\n\t/* First draw blob */\n\tdraw_rounded_rectangle(ctx_base,\n\t\tunit_x, y, 20, 20, 5, color);\n\n\tif (unit_width > 22) {\n\t\tchar * label_cropped = tt_ellipsify(label, 12, tt_thin, unit_width - 22, NULL);\n\t\ttt_draw_string(ctx_base, tt_thin, 22 + unit_x, y + 14, label_cropped, rgb(0,0,0));\n\t}\n\n}\n\nstatic void draw_legend_cpu(void) {\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\tchar _cpu_name[] = \"CPU    \";\n\t\tsprintf(_cpu_name, \"CPU %d\", i+1);\n\t\tdraw_legend_element(0, cpu_count, i, colors[i], _cpu_name);\n\t}\n}\n\nstatic void draw_legend_mem(void) {\n\tdraw_legend_element(1, 1, 0, rgb(250,110,240), \"Memory Usage\");\n}\n\nstatic void draw_legend_net(void) {\n\tfor (int i = 0; i < if_count; ++i) {\n\t\tchar _net_name[300];\n\t\tsprintf(_net_name, \"%s (%s)\", (i & 1) ? \"TX\" : \"RX\", ifnames[i>>1]);\n\t\tdraw_legend_element(2, if_count, i, if_colors[i], _net_name);\n\t}\n}\n\nstatic int poll_tick = 0;\nstatic void redraw_graphs(void) {\n\tfloat shift = -(float)(poll_tick + 1) / (float)(EASE_WIDTH-1) * ctx_cpu->width / 100.0;\n\tdraw_cpu_graphs(ctx_cpu, shift);\n\tdraw_mem_graphs(ctx_mem, shift);\n\tdraw_net_graphs(ctx_net, shift);\n}\n\nstatic void refresh(clock_t ticks) {\n\n\tif (poll_tick == (EASE_WIDTH-2)) {\n\t\tnext_cpu(ctx_cpu);\n\t\tnext_mem(ctx_mem);\n\t\tnext_net(ctx_net);\n\t\tpoll_tick = 0;\n\t} else {\n\t\tredraw_graphs();\n\t\tpoll_tick++;\n\t}\n\n\tflip(ctx_base);\n\tyutani_flip(yctx, wina);\n\n\tlast_redraw = ticks;\n}\n\nstatic void redraw_net_scale(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\ttt_set_size(tt_thin, 10);\n\n\tchar net_max[100];\n\tsnprintf(net_max, 100, \"%0.2fmbps\", (double)net_scale / 1024.0);\n\tint swidth = tt_string_width(tt_thin, net_max) + 2;\n\tdraw_rectangle(ctx_base, bounds.left_width + width - swidth, MENU_BAR_HEIGHT + bounds.top_height + 2 * (top_pad + bottom_pad + graph_height), swidth, 20, rgb(204,204,204));\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - swidth, MENU_BAR_HEIGHT + bounds.top_height + 2 * (top_pad + bottom_pad + graph_height) + 17, net_max, rgb(0,0,0));\n}\n\nvoid render_base(void) {\n\trender_decorations(wina, ctx_base, \"System Monitor\");\n\tmenu_bar_render(&menu_bar, ctx_base);\n}\n\nstatic void redraw_window_callback(struct menu_bar * self) {\n\t(void)self;\n\trender_base();\n\tflip(ctx_base);\n\tyutani_flip(yctx,wina);\n}\n\nstatic void initial_stuff(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\tgraph_height = (height - top_pad * 3 - bottom_pad * 3) / 3;\n\n\tmenu_bar.x = bounds.left_width;\n\tmenu_bar.y = bounds.top_height;\n\tmenu_bar.width = ctx_base->width - bounds.width;\n\tmenu_bar.window = wina;\n\n\tdraw_fill(ctx_base, rgb(204,204,204));\n\n\tctx_cpu = init_graphics_subregion(ctx_base, bounds.left_width + left_pad, MENU_BAR_HEIGHT + bounds.top_height + top_pad, width - h_pad, graph_height);\n\tctx_mem = init_graphics_subregion(ctx_base, bounds.left_width + left_pad, MENU_BAR_HEIGHT + bounds.top_height + 2 * top_pad + graph_height + bottom_pad, width - h_pad, graph_height);\n\tctx_net = init_graphics_subregion(ctx_base, bounds.left_width + left_pad, MENU_BAR_HEIGHT + bounds.top_height + 3 * top_pad + 2 * graph_height + 2 * bottom_pad, width - h_pad, graph_height);\n\n\tdraw_fill(ctx_cpu, rgb(0xF8,0xF8,0xF8));\n\tdraw_fill(ctx_mem, rgb(0xF8,0xF8,0xF8));\n\tdraw_fill(ctx_net, rgb(0xF8,0xF8,0xF8));\n\n\ttt_set_size(tt_bold, 13);\n\ttt_draw_string(ctx_base, tt_bold, bounds.left_width + 3, MENU_BAR_HEIGHT + bounds.top_height + 14, \"CPU\", rgb(0,0,0));\n\ttt_draw_string(ctx_base, tt_bold, bounds.left_width + 3, MENU_BAR_HEIGHT + bounds.top_height + (top_pad + bottom_pad + graph_height) + 14, \"Memory\", rgb(0,0,0));\n\ttt_draw_string(ctx_base, tt_bold, bounds.left_width + 3, MENU_BAR_HEIGHT + bounds.top_height + 2 * (top_pad + bottom_pad + graph_height) + 14, \"Network\", rgb(0,0,0));\n\n\ttt_set_size(tt_thin, 10);\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 30, MENU_BAR_HEIGHT + bounds.top_height + 17, \"100%\", rgb(0,0,0));\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 30, MENU_BAR_HEIGHT + bounds.top_height + (top_pad + bottom_pad + graph_height) + 17, \"100%\", rgb(0,0,0));\n\n\tchar net_max[100];\n\tsnprintf(net_max, 100, \"%0.2fmbps\", (double)net_scale / 1024.0);\n\tint swidth = tt_string_width(tt_thin, net_max) + 2;\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - swidth, MENU_BAR_HEIGHT + bounds.top_height + 2 * (top_pad + bottom_pad + graph_height) + 17, net_max, rgb(0,0,0));\n\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 25, MENU_BAR_HEIGHT + bounds.top_height + top_pad + graph_height + 13, \"0%\", rgb(0,0,0));\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 25, MENU_BAR_HEIGHT + bounds.top_height + 2 * (top_pad + graph_height) + bottom_pad + 13, \"0%\", rgb(0,0,0));\n\ttt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 40, MENU_BAR_HEIGHT + bounds.top_height + 3 * (top_pad + graph_height) + 2 * bottom_pad + 13, \"0mbps\", rgb(0,0,0));\n\n\trender_base();\n\n\tdraw_legend_cpu();\n\tdraw_legend_mem();\n\tdraw_legend_net();\n}\n\nvoid resize_finish(int w, int h) {\n\n\tif (w < 300) w = 300;\n\tif (h < 300) h = 300;\n\n\tfree(ctx_cpu);\n\tfree(ctx_mem);\n\tfree(ctx_net);\n\n\tyutani_window_resize_accept(yctx, wina, w, h);\n\treinit_graphics_yutani(ctx_base, wina);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\n\twidth  = w - bounds.left_width - bounds.right_width;\n\theight = h - MENU_BAR_HEIGHT - bounds.top_height - bounds.bottom_height;\n\n\tinitial_stuff();\n\tredraw_graphs();\n\n\tflip(ctx_base);\n\n\tyutani_window_resize_done(yctx, wina);\n}\n\nstatic void _menu_action_exit(struct MenuEntry * entry) {\n\texit(0);\n}\n\nstatic void _menu_action_help(struct MenuEntry * entry) {\n\tsystem(\"help-browser systemmonitor.trt &\");\n\trender_base();\n}\n\nstatic void _menu_action_about(struct MenuEntry * entry) {\n\t/* Show About dialog */\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About System Monitor\\\" /usr/share/icons/48/system-monitor.png \\\"System Monitor\\\" \\\"© 2021-2023 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)wina->x + (int)wina->width / 2, (int)wina->y + (int)wina->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\trender_base();\n}\n\n\nint main (int argc, char ** argv) {\n\tleft   = 100;\n\ttop    = 100;\n\twidth  = 640;\n\theight = 480;\n\tcpu_count = sysfunc(TOARU_SYS_FUNC_NPROC, NULL);\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tsrand(time(NULL));\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\tcolors[i] = hsv_to_rgb((float)i / (float)cpu_count * 6.24, 0.9, 0.9);\n\t\tfor (int j = 0; j < 100; ++j) {\n\t\t\tcpu_samples[i][j] = -1;\n\t\t}\n\t}\n\n\tinit_decorations();\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\twina = yutani_window_create(yctx, width + bounds.width, height + bounds.height + MENU_BAR_HEIGHT);\n\tyutani_window_move(yctx, wina, left, top);\n\tyutani_window_advertise_icon(yctx, wina, \"System Monitor\", \"system-monitor\");\n\n\tctx_base = init_graphics_yutani_double_buffer(wina);\n\n\tmenu_bar.entries = menu_entries;\n\tmenu_bar.redraw_callback = redraw_window_callback;\n\tmenu_bar.set = menu_set_create();\n\n\tstruct MenuList * m = menu_create(); /* File */\n\tmenu_insert(m, menu_create_normal(\"exit\",NULL,\"Exit\", _menu_action_exit));\n\tmenu_set_insert(menu_bar.set, \"file\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(\"help\",NULL,\"Contents\",_menu_action_help));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"star\",NULL,\"About System Monitor\",_menu_action_about));\n\tmenu_set_insert(menu_bar.set, \"help\", m);\n\n\ttt_thin = tt_font_from_shm(\"sans-serif\");\n\ttt_bold = tt_font_from_shm(\"sans-serif.bold\");\n\n\tif_count = count_interfaces();\n\tfor (int i = 0; i < if_count; ++i) {\n\t\tif_colors[i] = hsv_to_rgb((float)i / (float)(if_count)* 6.24 + 0.2, 0.9, 0.9);\n\t\tfor (int j = 0; j < 100; ++j) {\n\t\t\tnet_samples[i][j] = -1;\n\t\t}\n\t}\n\n\tfor (int i = 0; i < 100; ++i) {\n\t\tmem_samples[i] = -1;\n\t}\n\n\tinitial_stuff();\n\trefresh(times(NULL));\n\n\twhile (!should_exit) {\n\t\tint fds[1] = {fileno(yctx->sock)};\n\t\tint index = fswait2(1,fds,20);\n\t\tif (index == 0) {\n\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\twhile (m) {\n\t\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\t\t/* just decorations should be fine */\n\t\t\t\t\trender_base();\n\t\t\t\t\tflip(ctx_base);\n\t\t\t\t\tyutani_flip(yctx, wina);\n\t\t\t\t}\n\t\t\t\tswitch (m->type) {\n\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\tsched_yield();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\t\tif (win == wina) {\n\t\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\t\trender_base();\n\t\t\t\t\t\t\t\tflip(ctx_base);\n\t\t\t\t\t\t\t\tyutani_flip(yctx, wina);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid);\n\t\t\t\t\t\t\tif (win == wina) {\n\t\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\t\tdecor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y);\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tmenu_bar_mouse_event(yctx, wina, &menu_bar, me, me->new_x, me->new_y);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t}\n\t\t}\n\t\tclock_t ticks = times(NULL);\n\t\tif (ticks > last_redraw + CLOCKS_PER_SEC/12) {\n\t\t\trefresh(ticks);\n\t\t}\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/crc32.c",
    "content": "/**\n * crc32 - Simple CRC32 calculator for verifying file integrity.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <errno.h>\n#include <string.h>\n\nstatic unsigned int crctab[256] = {\n\t0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,\n\t0xe963a535, 0x9e6495a3,\t0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,\n\t0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,\n\t0xf3b97148, 0x84be41de,\t0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,\n\t0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,\t0x14015c4f, 0x63066cd9,\n\t0xfa0f3d63, 0x8d080df5,\t0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,\n\t0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,\t0x35b5a8fa, 0x42b2986c,\n\t0xdbbbc9d6, 0xacbcf940,\t0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,\n\t0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,\n\t0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,\n\t0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,\t0x76dc4190, 0x01db7106,\n\t0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,\n\t0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,\n\t0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,\n\t0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,\n\t0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,\n\t0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,\n\t0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,\n\t0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,\n\t0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,\n\t0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,\n\t0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,\n\t0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,\n\t0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,\n\t0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,\n\t0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,\n\t0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,\n\t0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,\n\t0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,\n\t0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,\n\t0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,\n\t0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,\n\t0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,\n\t0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,\n\t0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,\n\t0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,\n\t0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,\n\t0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,\n\t0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,\n\t0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,\n\t0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,\n\t0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,\n\t0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d\n};\n\n#define RBUF_SIZE 10240\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"usage: %s FILE\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tFILE * f = fopen(argv[1], \"r\");\n\tif (!f) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[1], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tchar buf[RBUF_SIZE];\n\tunsigned int crc32 = 0xffffffff;\n\twhile (!feof(f)) {\n\t\tsize_t r = fread(buf, 1, RBUF_SIZE, f);\n\t\tif (r == 0 && ferror(f)) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[1], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\tfor (size_t i = 0; i < r; ++i) {\n\t\t\tint ind = (crc32 ^ buf[i]) & 0xFF;\n\t\t\tcrc32 = (crc32 >> 8) ^ (crctab[ind]);\n\t\t}\n\t}\n\tcrc32 ^= 0xffffffff;\n\n\tfprintf(stdout, \"%8x\\n\", (unsigned int)crc32);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/cursor-off.c",
    "content": "/**\n * @brief cursor-off - Disables the VGA text mode cursor.\n *\n * This is an old tool that calls a special system call\n * to change the VGA text-mode cursor position. The VGA\n * terminal renders its own cursor in software, so we\n * try to move the hardware cursor off screen so it doesn't\n * interfere with the rest of the terminal and look weird.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2018 K. Lange\n */\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/sysfunc.h>\n\nint main(int argc, char * argv[]) {\n\tint fd = open(\"/dev/port\", O_RDWR);\n\tif (fd < 0) return 1;\n\tpwrite(fd, (unsigned char[]){14}, 1, 0x3D4);\n\tpwrite(fd, (unsigned char[]){0xFF}, 1, 0x3D5);\n\tpwrite(fd, (unsigned char[]){15}, 1, 0x3D4);\n\tpwrite(fd, (unsigned char[]){0xFF}, 1, 0x3D5);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/date.c",
    "content": "/**\n * date - Print the current date and time.\n *\n * TODO: The traditional POSIX version of this tool is supposed\n *       to accept a format *and* allow you to set the time.\n *       We currently lack system calls for setting the time,\n *       but when we add those this should probably be updated.\n *\n *       At the very least, improving this to print the \"correct\"\n *       default format would be good.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/time.h>\n\nstatic void show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"%s - print the time and day\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-?] +FORMAT\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"    Note: This implementation is not currently capable of\\n\"\n\t\t\t\"          setting the system time.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0], argv[0]);\n}\n\nint digits(const char * s, int len) {\n\tfor (int i = 0; i < len; ++i) {\n\t\tif (s[i] < '0' || s[i] > '9') return 0;\n\t}\n\treturn 1;\n}\n\nint mmddhhmm(struct tm * tm, const char * str) {\n\tint month = (str[0]-'0') * 10 + (str[1]-'0');\n\tint day   = (str[2]-'0') * 10 + (str[3]-'0');\n\tint hour  = (str[4]-'0') * 10 + (str[5]-'0');\n\tint min   = (str[6]-'0') * 10 + (str[7]-'0');\n\n\tif (month < 1 || month > 12) return 0;\n\tif (day < 1 || day > 31) return 0;\n\tif (hour < 0 || hour > 23) return 0;\n\tif (min < 0 || min > 59) return 0;\n\n\ttm->tm_mon = month - 1;\n\ttm->tm_mday = day;\n\ttm->tm_hour = hour;\n\ttm->tm_min = min;\n\n\treturn 1;\n}\n\nint ddyy(struct tm * tm, const char * str) {\n\tint year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 + (str[2]-'0') * 10 + (str[3]-'0');\n\ttm->tm_year = year - 1900;\n\treturn 1;\n}\n\nint secs(struct tm * tm, const char * str) {\n\tint sec = (str[0]-'0') * 10 + (str[1]-'0');\n\tif (sec < 0 || sec > 59) return 0;\n\ttm->tm_sec = sec;\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tchar * format = \"%a %d %b %Y %T %Z\";\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tchar buf[BUFSIZ] = {0};\n\tint opt;\n\n\twhile ((opt = getopt(argc,argv,\"?\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc,argv);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\tif (optind < argc && *argv[optind] == '+') {\n\t\tformat = &argv[optind][1];\n\t} else if (optind < argc) {\n\n\t\tint len =strlen(argv[optind]);\n\n\t\tif (len == 8) {\n\t\t\tif (!digits(argv[optind], 8)) goto _invalid;\n\t\t\tif (!mmddhhmm(timeinfo, argv[optind])) goto _invalid;\n\t\t\tgoto set_time;\n\t\t} else if (len == 11) {\n\t\t\tif (argv[optind][8] != '.') goto _invalid;\n\t\t\tif (!digits(argv[optind], 8) || !digits(argv[optind]+9,2)) goto _invalid;\n\t\t\tif (!mmddhhmm(timeinfo, argv[optind])) goto _invalid;\n\t\t\tif (!secs(timeinfo, argv[optind]+9)) goto _invalid;\n\t\t\tgoto set_time;\n\t\t} else if (len == 12) {\n\t\t\tif (!digits(argv[optind], 12)) goto _invalid;\n\t\t\tif (!mmddhhmm(timeinfo, argv[optind])) goto _invalid;\n\t\t\tif (!ddyy(timeinfo, argv[optind]+8)) goto _invalid;\n\t\t\tgoto set_time;\n\t\t} else if (len == 15) {\n\t\t\tif (argv[optind][12] != '.') goto _invalid;\n\t\t\tif (!digits(argv[optind], 12) || !digits(argv[optind]+13,2)) goto _invalid;\n\t\t\tif (!mmddhhmm(timeinfo, argv[optind])) goto _invalid;\n\t\t\tif (!ddyy(timeinfo, argv[optind]+8)) goto _invalid;\n\t\t\tif (!secs(timeinfo, argv[optind]+13)) goto _invalid;\n\t\t\tgoto set_time;\n\t\t}\n_invalid:\n\t\tfprintf(stderr, \"date: only 'MMDDhhmm', 'MMDDhhmm.ss', 'MMDDhhmmCCYY' and 'MMDDhhmmCCYY.ss' are supported for setting time.\\n\");\n\t\treturn 1;\n\nset_time:\n\t\tnow.tv_usec = 0;\n\t\tnow.tv_sec = mktime(timeinfo);\n\t\treturn settimeofday(&now, NULL);\n\t}\n\n\tstrftime(buf,BUFSIZ,format,timeinfo);\n\tputs(buf);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/dbg.c",
    "content": "/**\n * @brief Debugger.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <signal.h>\n#include <ctype.h>\n#include <sys/ptrace.h>\n#include <sys/wait.h>\n#include <sys/signal.h>\n#include <sys/signal_defs.h>\n#include <sys/sysfunc.h>\n#include <sys/utsname.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n#include <syscall_nums.h>\n\n#include <toaru/rline.h>\n#include <toaru/hashmap.h>\n#include <kernel/elf.h>\n#include <sys/uregs.h>\n\nstatic char * last_command = NULL;\nstatic char * binary_path = NULL;\nstatic FILE * binary_obj = NULL;\nstatic pid_t  binary_pid = 0;\nstatic int    binary_is_child = 0;\n\nstatic void dump_regs(struct URegs * r) {\n\tfprintf(stdout, UREGS_FMT, UREGS_ARGS(r));\n\n}\n\n#define M(e) [e] = #e\nconst char * signal_names[256] = {\n\tM(SIGHUP),\n\tM(SIGINT),\n\tM(SIGQUIT),\n\tM(SIGILL),\n\tM(SIGTRAP),\n\tM(SIGABRT),\n\tM(SIGEMT),\n\tM(SIGFPE),\n\tM(SIGKILL),\n\tM(SIGBUS),\n\tM(SIGSEGV),\n\tM(SIGSYS),\n\tM(SIGPIPE),\n\tM(SIGALRM),\n\tM(SIGTERM),\n\tM(SIGUSR1),\n\tM(SIGUSR2),\n\tM(SIGCHLD),\n\tM(SIGPWR),\n\tM(SIGWINCH),\n\tM(SIGURG),\n\tM(SIGPOLL),\n\tM(SIGSTOP),\n\tM(SIGTSTP),\n\tM(SIGCONT),\n\tM(SIGTTIN),\n\tM(SIGTTOUT),\n\tM(SIGVTALRM),\n\tM(SIGPROF),\n\tM(SIGXCPU),\n\tM(SIGXFSZ),\n\tM(SIGWAITING),\n\tM(SIGDIAF),\n\tM(SIGHATE),\n\tM(SIGWINEVENT),\n\tM(SIGCAT),\n};\n\nstatic int data_read_bytes(pid_t pid, uintptr_t addr, char * buf, size_t size) {\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tif (ptrace(PTRACE_PEEKDATA, pid, (void*)addr++, &buf[i])) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int data_read_int(pid_t pid, uintptr_t addr) {\n\tint x;\n\tdata_read_bytes(pid, addr, (char*)&x, sizeof(int));\n\treturn x;\n}\n\nstatic uintptr_t data_read_ptr(pid_t pid, uintptr_t addr) {\n\tuintptr_t x;\n\tdata_read_bytes(pid, addr, (char*)&x, sizeof(uintptr_t));\n\treturn x;\n}\n\nstatic void string_arg(pid_t pid, uintptr_t ptr, size_t maxsize) {\n\tFILE * logfile = stdout;\n\n\tif (ptr == 0) {\n\t\tfprintf(logfile, \"NULL\");\n\t\treturn;\n\t}\n\n\tfprintf(logfile, \"\\\"\");\n\n\tsize_t size = 0;\n\tuint8_t buf = 0;\n\n\tdo {\n\t\tlong result = ptrace(PTRACE_PEEKDATA, pid, (void*)ptr, &buf);\n\t\tif (result != 0) break;\n\t\tif (!buf) {\n\t\t\tfprintf(logfile, \"\\\"\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (buf == '\\\\') fprintf(logfile, \"\\\\\\\\\");\n\t\telse if (buf == '\"') fprintf(logfile, \"\\\\\\\"\");\n\t\telse if (buf >= ' ' && buf < '~') fprintf(logfile, \"%c\", buf);\n\t\telse if (buf == '\\r') fprintf(logfile, \"\\\\r\");\n\t\telse if (buf == '\\n') fprintf(logfile, \"\\\\n\");\n\t\telse fprintf(logfile, \"\\\\x%02x\", buf);\n\n\t\tptr++;\n\t\tsize++;\n\t\tif (size > maxsize) break;\n\t} while (buf);\n\n\tfprintf(logfile, \"\\\"...\");\n}\n\nextern uintptr_t __ld_symbol_table(void);\nextern uintptr_t __ld_objects_table(void);\n\nstatic char * read_string(pid_t pid, uintptr_t ptr) {\n\tif (!ptr) return strdup(\"(null)\");\n\tsize_t len = 0;\n\tuint8_t buf = 0;\n\twhile ((data_read_bytes(pid, ptr + len, (char*)&buf, 1), buf)) len++;\n\n\tchar * out = malloc(len + 1);\n\tdata_read_bytes(pid, ptr, out, len+1);\n\treturn out;\n}\n\ntypedef struct elf_object {\n\tFILE * file;\n\tElf64_Header header;\n\tchar * dyn_string_table;\n\tsize_t dyn_string_table_size;\n\tElf64_Sym * dyn_symbol_table;\n\tsize_t dyn_symbol_table_size;\n\tElf64_Dyn * dynamic;\n\tElf64_Word * dyn_hash;\n\tvoid (*init)(void);\n\tvoid (**init_array)(void);\n\tsize_t init_array_size;\n\tuintptr_t base;\n\tlist_t * dependencies;\n\tint loaded;\n} elf_t;\n\nstatic int find_symbol(pid_t pid, uintptr_t addr_in, char ** name, uintptr_t *addr_out, char ** objname) {\n\n\tintptr_t  current_max = INTPTR_MAX;\n\tuintptr_t current_addr = (uintptr_t)NULL;\n\tuintptr_t current_xname = (uintptr_t)NULL;\n\tchar * current_name = NULL;\n\tchar * current_obj = NULL;\n\tuintptr_t best_base = 0;\n\n\t/* Can we cheat and peek at ld.so? */\n\tuintptr_t their_symbol_table  = data_read_ptr(pid, __ld_symbol_table());\n\n\tif (their_symbol_table) {\n\t\thashmap_t map;\n\t\tdata_read_bytes(pid, their_symbol_table, (char*)&map, sizeof(hashmap_t));\n\n\t\t/* Cool, now let's look at every entry... */\n\t\tfor (size_t i = 0; i < map.size; ++i) {\n\t\t\tuintptr_t ptr;\n\t\t\thashmap_entry_t entry;\n\t\t\tdata_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t));\n\t\t\tint j = 0;\n\t\t\twhile (ptr) {\n\t\t\t\tdata_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t));\n\t\t\t\tif (entry.value && addr_in >= (uintptr_t)entry.value) {\n\t\t\t\t\tintptr_t x = addr_in - (uintptr_t)entry.value;\n\t\t\t\t\tif (x < current_max) {\n\t\t\t\t\t\tcurrent_max = x;\n\t\t\t\t\t\tcurrent_addr = (uintptr_t)entry.value;\n\t\t\t\t\t\tcurrent_xname = (uintptr_t)entry.key;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tptr = (uintptr_t)entry.next;\n\t\t\t\tj++;\n\t\t\t}\n\t\t}\n\n\t\tif (current_xname) {\n\t\t\tcurrent_name = read_string(pid, current_xname);\n\t\t}\n\t}\n\n\tif (addr_in < 0x40000000) {\n\t\tcurrent_obj = strdup(\"ld.so\");\n\t}\n\n\t/* Figure out where this object is in the objects map */\n\tuintptr_t their_objects_table = data_read_ptr(pid, __ld_objects_table());\n\n\tif (!current_obj && their_objects_table) {\n\t\thashmap_t map;\n\t\tdata_read_bytes(pid, their_objects_table, (char*)&map, sizeof(hashmap_t));\n\n\t\tintptr_t  cmax = INTPTR_MAX;\n\t\tuintptr_t best_name = 0;\n\t\tfor (size_t i = 0; i < map.size; ++i) {\n\t\t\tuintptr_t ptr;\n\t\t\thashmap_entry_t entry;\n\t\t\tdata_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t));\n\t\t\twhile (ptr) {\n\t\t\t\tdata_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t));\n\t\t\t\tif (entry.value) {\n\t\t\t\t\telf_t obj;\n\t\t\t\t\tdata_read_bytes(pid, (uintptr_t)entry.value, (char*)&obj, sizeof(elf_t));\n\t\t\t\t\tif (addr_in >= obj.base) {\n\t\t\t\t\t\tintptr_t x = addr_in - (uintptr_t)obj.base;\n\t\t\t\t\t\tif (x < cmax) {\n\t\t\t\t\t\t\tcmax = x;\n\t\t\t\t\t\t\tbest_name = (uintptr_t)entry.key;\n\t\t\t\t\t\t\tbest_base = obj.base;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tptr = (uintptr_t)entry.next;\n\t\t\t}\n\t\t}\n\n\t\tif (best_name) {\n\t\t\tcurrent_obj = read_string(pid, best_name);\n\t\t}\n\t}\n\n\tFILE * f = binary_obj;\n\tif (current_obj) {\n\t\t/* Try to open that */\n\t\tstruct stat stat_buf;\n\t\tchar path[1024];\n\t\tsprintf(path, \"/lib/%s\", current_obj);\n\t\tif (stat(path, &stat_buf)) {\n\t\t\tsprintf(path, \"/usr/lib/%s\", current_obj);\n\t\t\tif (stat(path, &stat_buf)) goto _bail;\n\t\t}\n\n\t\tf = fopen(path, \"r\");\n\t} else {\n_bail:\n\t\tcurrent_obj = strdup(binary_path);\n\t\tbest_base = 0;\n\t}\n\n\tfseek(f, 0, SEEK_SET);\n\tElf64_Header header;\n\tfread(&header, sizeof(Elf64_Header), 1, f);\n\n\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\tfseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET);\n\t\tElf64_Shdr sectionHeader;\n\t\tfread(&sectionHeader, sizeof(Elf64_Shdr), 1, f);\n\t\tswitch (sectionHeader.sh_type) {\n\t\t\tcase SHT_SYMTAB:\n\t\t\tcase SHT_DYNSYM: {\n\t\t\t\t/* Try to get the actual one if possible */\n\t\t\t\tElf64_Sym * symtab = malloc(sectionHeader.sh_size);\n\n\t\t\t\tif (sectionHeader.sh_addr > 0x40000000) {\n\t\t\t\t\tdata_read_bytes(pid, sectionHeader.sh_addr, (char*)symtab, sectionHeader.sh_size);\n\t\t\t\t} else {\n\t\t\t\t\tfseek(f, sectionHeader.sh_offset, SEEK_SET);\n\t\t\t\t\tfread(symtab, sectionHeader.sh_size, 1, f);\n\t\t\t\t}\n\n\t\t\t\tElf64_Shdr shdr_strtab;\n\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);\n\t\t\t\tfread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\tchar * strtab = malloc(shdr_strtab.sh_size);\n\t\t\t\tfseek(f, shdr_strtab.sh_offset, SEEK_SET);\n\t\t\t\tfread(strtab, shdr_strtab.sh_size, 1, f);\n\n\t\t\t\tfor (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Sym); ++i) {\n\t\t\t\t\tif (!symtab[i].st_value) continue;\n\t\t\t\t\tif ((symtab[i].st_info & 0xF) == STT_SECTION) continue;\n\t\t\t\t\tif ((symtab[i].st_info & 0xF) == STT_NOTYPE) continue;\n\t\t\t\t\tif (addr_in >= ((uintptr_t)symtab[i].st_value + best_base)) {\n\t\t\t\t\t\tintptr_t x = addr_in - ((uintptr_t)symtab[i].st_value + best_base);\n\t\t\t\t\t\tif (x < current_max) {\n\t\t\t\t\t\t\tif (current_name) free(current_name);\n\t\t\t\t\t\t\tcurrent_max = x;\n\t\t\t\t\t\t\tcurrent_addr = symtab[i].st_value + best_base;\n\t\t\t\t\t\t\tcurrent_name = strdup(strtab + symtab[i].st_name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfree(strtab);\n\t\t\t\tfree(symtab);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t*addr_out = current_addr;\n\t*name = current_name;\n\t*objname = current_obj;\n\n\tif (current_name) return 1;\n\tif (f != binary_obj) fclose(f);\n\treturn 0;\n}\n\nstatic void show_libs(pid_t pid) {\n\thashmap_t map;\n\tuintptr_t their_objects_table = data_read_ptr(pid, __ld_objects_table());\n\tdata_read_bytes(pid, their_objects_table, (char*)&map, sizeof(hashmap_t));\n\tfor (size_t i = 0; i < map.size; ++i) {\n\t\tuintptr_t ptr;\n\t\thashmap_entry_t entry;\n\t\tdata_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t));\n\t\twhile (ptr) {\n\t\t\tdata_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t));\n\t\t\tif (entry.value) {\n\t\t\t\telf_t obj;\n\t\t\t\tdata_read_bytes(pid, (uintptr_t)entry.value, (char*)&obj, sizeof(elf_t));\n\t\t\t\tchar * s = read_string(pid, (uintptr_t)entry.key);\n\t\t\t\tfprintf(stderr, \"%s @ %#zx\\n\", s, (uintptr_t)obj.base);\n\t\t\t}\n\t\t\tptr = (uintptr_t)entry.next;\n\t\t}\n\t}\n}\n\nstatic void attempt_backtrace(pid_t pid, struct URegs * regs) {\n\n\t/* We already printed the top, now let's try to dig down */\n\tuintptr_t ip = uregs_ip(regs);\n\tuintptr_t bp = uregs_bp(regs);\n\tint depth = 0;\n\tint max_depth = 20;\n\n\twhile (bp && ip && depth < max_depth && ip < 0xFFFfff0000000000UL) {\n\t\tchar * name = NULL;\n\t\tchar * objname = NULL;\n\t\tuintptr_t addr = 0;\n\t\tif (find_symbol(pid, ip - 1, &name, &addr, &objname)) {\n\t\t\tfprintf(stderr, \"<0x%016zx> %s+%#zx in %s\\n\",\n\t\t\t\tip,\n\t\t\t\tname, ip - addr, objname);\n\t\t\tfree(name);\n\t\t\tfree(objname);\n\t\t}\n\n\t\tip = data_read_ptr(pid, bp + sizeof(uintptr_t));\n\t\tbp = data_read_ptr(pid, bp);\n\t\tdepth++;\n\t}\n}\n\nstatic int imatch(const char * a, const char * b) {\n\tdo {\n\t\tif (!*a && !*b) return 1;\n\t\tif (tolower(*a) != tolower(*b)) return 0;\n\t\ta++;\n\t\tb++;\n\t} while (1);\n}\n\nstatic int signal_from_string(const char * str) {\n\tif (isdigit(*str)) {\n\t\treturn strtoul(str,NULL,0);\n\t} else if (str[0] == 'S' && str[1] == 'I' && str[2] == 'G') {\n\t\tfor (int i = 0; i < 256; ++i) {\n\t\t\tif (signal_names[i] && imatch(signal_names[i], str)) return i;\n\t\t}\n\t\treturn -1;\n\t} else {\n\t\tfor (int i = 0; i < 256; ++i) {\n\t\t\tif (signal_names[i] && imatch(signal_names[i]+3, str)) return i;\n\t\t}\n\t\treturn -1;\n\t}\n\n\treturn -1;\n}\n\nstatic void show_commandline(pid_t pid, int status, struct URegs * regs) {\n\n\tfprintf(stderr, \"[Process %d, ip=%#zx]\\n\",\n\t\tpid, uregs_ip(regs));\n\n\t/* Try to figure out what symbol that is */\n\tchar * name = NULL;\n\tchar * objname = NULL;\n\tuintptr_t addr = 0;\n\tif (find_symbol(pid, uregs_ip(regs), &name, &addr, &objname)) {\n\t\tfprintf(stderr, \"     %s+%zx in %s\\n\",\n\t\t\tname, uregs_ip(regs) - addr, objname);\n\t\tfree(name);\n\t\tfree(objname);\n\t}\n\n\twhile (1) {\n\t\tchar buf[4096] = {0};\n\t\trline_exit_string = \"\";\n\t\trline_exp_set_prompts(\"(dbg) \", \"\", 6, 0);\n\t\trline_exp_set_syntax(\"dbg\");\n\t\trline_exp_set_tab_complete_func(NULL); /* TODO */\n\t\tif (rline(buf, 4096) == 0) goto _exitDebugger;\n\n\t\tchar *nl = strstr(buf, \"\\n\");\n\t\tif (nl) *nl = '\\0';\n\t\tif (!strlen(buf)) {\n\t\t\tif (last_command) {\n\t\t\t\tstrcpy(buf, last_command);\n\t\t\t} else {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\trline_history_insert(strdup(buf));\n\t\t\trline_scroll = 0;\n\t\t\tif (last_command) free(last_command);\n\t\t\tlast_command = strdup(buf);\n\t\t}\n\n\t\t/* Tokenize just the first command */\n\t\tchar * arg = NULL;\n\t\tchar * sp = strstr(buf, \" \");\n\t\tif (sp) {\n\t\t\t*sp = '\\0';\n\t\t\targ = sp + 1;\n\t\t}\n\n\t\tif (!strcmp(buf, \"show\")) {\n\t\t\tif (!arg) {\n\t\t\t\tfprintf(stderr, \"Things that can be shown:\\n\");\n\t\t\t\tfprintf(stderr, \"   regs\\n\");\n\t\t\t\tfprintf(stderr, \"   libs\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!strcmp(arg, \"regs\")) {\n\t\t\t\tdump_regs(regs);\n\t\t\t} else if (!strcmp(arg, \"libs\")) {\n\t\t\t\tshow_libs(pid);\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"Don't know how to show '%s'\\n\", arg);\n\t\t\t}\n\t\t} else if (!strcmp(buf, \"bt\") || !strcmp(buf, \"backtrace\")) {\n\t\t\tattempt_backtrace(pid, regs);\n\t\t} else if (!strcmp(buf, \"continue\") || !strcmp(buf,\"c\")) {\n\t\t\tint signum = WSTOPSIG(status);\n\t\t\tif (signum == SIGINT) signum = 0;\n\t\t\tptrace(PTRACE_CONT, pid, NULL, (void*)(uintptr_t)signum);\n\t\t\treturn;\n\t\t} else if (!strcmp(buf, \"signal\")) {\n\t\t\tif (!arg) {\n\t\t\t\tfprintf(stderr, \"'signal' needs an argument\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tint signum = signal_from_string(arg);\n\t\t\tif (signum == -1) {\n\t\t\t\tfprintf(stderr, \"'%s' is not a recognized signal\\n\", arg);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tptrace(PTRACE_CONT, pid, NULL, (void*)(uintptr_t)signum);\n\t\t\treturn;\n\t\t} else if (!strcmp(buf, \"step\") || !strcmp(buf,\"s\")) {\n\t\t\tint signum = WSTOPSIG(status);\n\t\t\tif (signum == SIGINT) signum = 0;\n\t\t\tptrace(PTRACE_SINGLESTEP, pid, NULL, (void*)(uintptr_t)signum);\n\t\t\treturn;\n\t\t} else if (!strcmp(buf, \"poke\")) {\n\t\t\tchar * addr = arg;\n\t\t\tchar * data = strstr(addr, \" \");\n\t\t\tif (!data) {\n\t\t\t\tfprintf(stderr, \"usage: poke addr byte\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t*data = '\\0'; data++;\n\n\t\t\tuintptr_t addr_ = strtoul(addr, NULL, 0);\n\t\t\tuintptr_t data_ = strtoul(data, NULL, 0);\n\n\t\t\tif (ptrace(PTRACE_POKEDATA, pid, (void*)addr_, (void*)&data_) != 0) {\n\t\t\t\tfprintf(stderr, \"poke: %s\\n\", strerror(errno));\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t} else if (!strcmp(buf, \"print\") || !strcmp(buf,\"p\")) {\n\t\t\tchar * fmt = arg;\n\t\t\tchar * sp = strstr(arg, \" \");\n\t\t\tif (!sp) {\n\t\t\t\tfprintf(stderr, \"usage: print fmt addr\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t*sp = '\\0'; sp++;\n\n\t\t\tuintptr_t addr = strtoul(sp,NULL,0);\n\n\t\t\t/* Parse any leading numbers */\n\t\t\tint count = 1;\n\n\t\t\tif (*fmt >= '1' && *fmt <= '9') {\n\t\t\t\tcount = (*fmt - '0');\n\t\t\t\tfmt++;\n\t\t\t\twhile (*fmt >= '0' && *fmt <= '9') {\n\t\t\t\t\tcount *= 10;\n\t\t\t\t\tcount += (*fmt - '0');\n\t\t\t\t\tfmt++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/* Parse the format */\n\t\t\tfor (int i = 0; i < count; ++i) {\n\t\t\t\tif (!strcmp(fmt, \"x\")) {\n\t\t\t\t\tuint8_t buf[1];\n\t\t\t\t\tdata_read_bytes(pid, addr, (char*)buf, 1);\n\t\t\t\t\tprintf(\"%02x\", buf[0]);\n\t\t\t\t\taddr += 1;\n\t\t\t\t} else if (!strcmp(fmt, \"i\")) {\n\t\t\t\t\tprintf(\"%d\", data_read_int(pid,addr));\n\t\t\t\t\taddr += sizeof(int);\n\t\t\t\t} else if (!strcmp(fmt, \"l\")) {\n\t\t\t\t\tprintf(\"%ld\", (intptr_t)data_read_ptr(pid,addr));\n\t\t\t\t\taddr += sizeof(long);\n\t\t\t\t} else if (!strcmp(fmt, \"p\")) {\n\t\t\t\t\tprintf(\"%#zx\", data_read_ptr(pid,addr));\n\t\t\t\t\taddr += sizeof(uintptr_t);\n\t\t\t\t} else if (!strcmp(fmt, \"s\")) {\n\t\t\t\t\tstring_arg(pid,addr,count == 1 ? 30 : count);\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tprintf(\"print: invalid format string\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (i + 1 < count) {\n\t\t\t\t\tprintf(\" \");\n\t\t\t\t}\n\t\t\t}\n\t\t\tprintf(\"\\n\");\n\t\t} else if (!strcmp(buf, \"help\")) {\n\t\t\tprintf(\"commands:\\n\"\n\t\t\t\t\"  show (regs, libs)\\n\"\n\t\t\t\t\"  backtrace\\n\"\n\t\t\t\t\"  continue\\n\"\n\t\t\t\t\"  signal signum\\n\"\n\t\t\t\t\"  step\\n\"\n\t\t\t\t\"  poke addr byte\\n\"\n\t\t\t\t\"  print fmt addr\\n\");\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tfprintf(stderr, \"dbg: unrecognized command '%s'\\n\", buf);\n\t\t\tcontinue;\n\t\t}\n\t}\n\n_exitDebugger:\n\tif (binary_is_child) {\n\t\tfprintf(stderr, \"Terminating child process '%d'.\\n\", pid);\n\t\tptrace(PTRACE_DETACH, pid, NULL, (void*)(uintptr_t)SIGKILL);\n\t}\n\texit(0);\n}\n\nstatic int usage(char * argv[]) {\n#define T_I \"\\033[3m\"\n#define T_O \"\\033[0m\"\n\tfprintf(stderr, \"usage: %s command...\\n\"\n\t\t\t\"  -h         \" T_I \"Show this help text.\" T_O \"\\n\",\n\t\t\targv[0]);\n\treturn 1;\n}\n\n#define DEFAULT_PATH \"/bin:/usr/bin\"\nstatic char * find_binary(const char * file) {\n\tif (file && (!strstr(file, \"/\"))) {\n\t\tchar * path = getenv(\"PATH\");\n\t\tif (!path) {\n\t\t\tpath = DEFAULT_PATH;\n\t\t}\n\t\tchar * xpath = strdup(path);\n\t\tchar * p, * last;\n\t\tfor ((p = strtok_r(xpath, \":\", &last)); p; p = strtok_r(NULL, \":\", &last)) {\n\t\t\tint r;\n\t\t\tstruct stat stat_buf;\n\t\t\tchar * exe = malloc(strlen(p) + strlen(file) + 2);\n\t\t\tstrcpy(exe, p);\n\t\t\tstrcat(exe, \"/\");\n\t\t\tstrcat(exe, file);\n\t\t\tr = stat(exe, &stat_buf);\n\t\t\tif (r != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!(stat_buf.st_mode & 0111)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfree(xpath);\n\t\t\treturn exe;\n\t\t}\n\t\tfree(xpath);\n\t\treturn NULL;\n\t} else if (file) {\n\t\treturn strdup(file);\n\t}\n\treturn NULL;\n}\n\nstatic char * sig_to_str(int signum) {\n\tstatic char _buf[100];\n\tif (signum >= 0 && signum <= 255) {\n\t\tchar * maybe = (char*)signal_names[signum];\n\t\tif (maybe) {\n\t\t\treturn maybe;\n\t\t}\n\t}\n\tsprintf(_buf, \"%d\", signum);\n\treturn _buf;\n}\n\nstatic void pass_sig(int sig) {\n\tkill(binary_pid, sig);\n\tsignal(SIGINT, pass_sig);\n}\n\nint main(int argc, char * argv[]) {\n\tpid_t target_pid = 0;\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"o:p:h\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'p':\n\t\t\t\ttarget_pid = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\treturn (usage(argv), 0);\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t}\n\t}\n\n\tif (optind == argc) {\n\t\treturn usage(argv);\n\t}\n\n\t/* TODO find argv[optind] */\n\t/* TODO load symbols from it, and from its dependencies... with offsets... from ld.so... */\n\n\tbinary_path = find_binary(argv[optind]);\n\n\tif (!binary_path) {\n\t\tfprintf(stderr, \"%s: %s: No such file or not an executable.\\n\",\n\t\t\targv[0], argv[optind]);\n\t\treturn 1;\n\t}\n\n\tbinary_obj = fopen(binary_path, \"r\");\n\n\tif (!binary_obj) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\",\n\t\t\targv[0], argv[optind], strerror(errno));\n\t\treturn 1;\n\t}\n\n\t/* Catch one potential mistake early... */\n\tstruct stat stat_buf;\n\tfstat(fileno(binary_obj), &stat_buf);\n\tif (stat_buf.st_mode & S_IFDIR) {\n\t\tfprintf(stderr, \"%s: %s: Is a directory\\n\", argv[0], binary_path);\n\t\treturn 1;\n\t}\n\n\t/* Attempt to load symbol information... */\n\n\tif (target_pid) {\n\t\tbinary_pid = target_pid;\n\t\tif (ptrace(PTRACE_ATTACH, binary_pid, NULL, NULL) < 0) {\n\t\t\tfprintf(stderr, \"%s: ptrace: %s\\n\", argv[0], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\tsignal(SIGINT, pass_sig);\n\t} else {\n\t\tbinary_is_child = 1;\n\t\tbinary_pid = fork();\n\t\tif (!binary_pid) {\n\t\t\tif (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) {\n\t\t\t\tfprintf(stderr, \"%s: ptrace: %s\\n\", argv[0], strerror(errno));\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\texecv(binary_path, &argv[optind]);\n\t\t\t/* If we got to this point at all... */\n\t\t\tfprintf(stderr, \"%s: %s: execv: %s\\n\", argv[0], binary_path, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\n\t\tsignal(SIGINT, SIG_IGN);\n\t}\n\n\twhile (1) {\n\t\tint status = 0;\n\t\tpid_t res = waitpid(binary_pid, &status, WSTOPPED);\n\n\t\tif (res == 0) continue;\n\n\t\tif (res < 0) {\n\t\t\tif (errno == EINTR) continue;\n\t\t\tfprintf(stderr, \"%s: waitpid: %s\\n\", argv[0], strerror(errno));\n\t\t} else {\n\t\t\tif (WIFSTOPPED(status)) {\n\t\t\t\tif (WSTOPSIG(status) == SIGTRAP) {\n\t\t\t\t\t/* Don't care about TRAP right now */\n\t\t\t\t\tint event = (status >> 16) & 0xFF;\n\t\t\t\t\tswitch (event) {\n\t\t\t\t\t\tcase PTRACE_EVENT_SINGLESTEP: {\n\t\t\t\t\t\t\t\tstruct URegs regs;\n\t\t\t\t\t\t\t\tptrace(PTRACE_GETREGS, res, NULL, &regs);\n\t\t\t\t\t\t\t\tshow_commandline(res, status, &regs);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t//ptrace(PTRACE_SIGNALS_ONLY_PLZ, p, NULL, NULL);\n\t\t\t\t\t\t\tptrace(PTRACE_CONT, res, NULL, NULL);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tprintf(\"Program received signal %s.\\n\", sig_to_str(WSTOPSIG(status)));\n\n\t\t\t\t\tstruct URegs regs;\n\t\t\t\t\tptrace(PTRACE_GETREGS, res, NULL, &regs);\n\n\t\t\t\t\tshow_commandline(res, status, &regs);\n\t\t\t\t}\n\t\t\t} else if (WIFSIGNALED(status)) {\n\t\t\t\tfprintf(stderr, \"Process %d was killed by %s.\\n\", res, signal_names[WTERMSIG(status)]);\n\t\t\t\treturn 0;\n\t\t\t} else if (WIFEXITED(status)) {\n\t\t\t\tfprintf(stderr, \"Process %d exited normally with status %d.\\n\", res, WEXITSTATUS(status));\n\t\t\t\treturn 0;\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"Unknown state?\\n\");\n\t\t\t}\n\t\t}\n\t}\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/demo.c",
    "content": "/**\n * @brief Simple executable that was used during initial testing of Misaka.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <syscall.h>\n#include <sys/time.h>\n#include <sys/utsname.h>\n\n#include <toaru/graphics.h>\n#include <toaru/text.h>\n\nstatic sprite_t wallpaper;\nstatic struct TT_Font * _tt_font_thin;\nstatic struct tm * timeinfo;\nstatic gfx_context_t * ctx;\n\nstatic void redraw(void) {\n\tdraw_sprite(ctx, &wallpaper, 0, 0);\n\tstruct utsname u;\n\tuname(&u);\n\n\tchar string[1024];\n\tsnprintf(string, 1024,\n\t\t\"ToaruOS %s %s %s\",\n\t\tu.release, u.version, u.machine);\n\ttt_draw_string_shadow(ctx, _tt_font_thin, string, 15, 30, 30, rgb(255,255,255), rgb(0,0,0), 4);\n\n\tstrftime(string,1024,\"%a %d %b %Y %T %Z\",timeinfo);\n\n\ttt_draw_string_shadow(ctx, _tt_font_thin, string, 15, 30, 60, rgb(255,255,255), rgb(0,0,0), 4);\n\n\tflip(ctx);\n}\n\nint main(int argc, char * argv[]) {\n\tfprintf(stderr, \"open() = %ld\\n\", syscall_open(\"/dev/null\", 0, 0));\n\tfprintf(stderr, \"open() = %ld\\n\", syscall_open(\"/dev/null\", 1, 0));\n\tfprintf(stderr, \"open() = %ld\\n\", syscall_open(\"/dev/null\", 1, 0));\n\n\tctx = init_graphics_fullscreen_double_buffer();\n\tdraw_fill(ctx, rgb(120,120,120));\n\tflip(ctx);\n\n\t_tt_font_thin = tt_font_from_file(\"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\");\n\tload_sprite(&wallpaper, \"/usr/share/wallpaper.jpg\");\n\n\tdraw_fill(ctx, rgb(0,0,0));\n\tflip(ctx);\n\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\n\tint forked = 0;\n\twhile (1) {\n\t\ttime_t last = now.tv_sec;\n\t\ttimeinfo = localtime(&last);\n\t\tredraw();\n\n\t\twhile (1) {\n\t\t\tgettimeofday(&now, NULL);\n\t\t\tif (now.tv_sec != last) break;\n\t\t}\n\n\t\tif (!forked) {\n\t\t\tforked = 1;\n\t\t\tsystem(\"uname -a\");\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/demo.krk",
    "content": "#!/bin/kuroko\nimport os, kuroko\n\nprint('Kuroko',kuroko.version, kuroko.buildenv, kuroko.builddate)\nprint('Running on:',os.uname())\n"
  },
  {
    "path": "apps/dhclient.c",
    "content": "/**\n * @brief DHCP client\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdlib.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <poll.h>\n#include <dirent.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <sys/time.h>\n#include <net/if.h>\n\nstruct ethernet_packet {\n\tuint8_t destination[6];\n\tuint8_t source[6];\n\tuint16_t type;\n\tuint8_t payload[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nstruct ipv4_packet {\n\tuint8_t  version_ihl;\n\tuint8_t  dscp_ecn;\n\tuint16_t length;\n\tuint16_t ident;\n\tuint16_t flags_fragment;\n\tuint8_t  ttl;\n\tuint8_t  protocol;\n\tuint16_t checksum;\n\tuint32_t source;\n\tuint32_t destination;\n\tuint8_t  payload[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct udp_packet {\n\tuint16_t source_port;\n\tuint16_t destination_port;\n\tuint16_t length;\n\tuint16_t checksum;\n\tuint8_t  payload[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct dhcp_packet {\n\tuint8_t op;\n\tuint8_t htype;\n\tuint8_t hlen;\n\tuint8_t hops;\n\n\tuint32_t xid;\n\n\tuint16_t secs;\n\tuint16_t flags;\n\n\tuint32_t ciaddr;\n\tuint32_t yiaddr;\n\tuint32_t siaddr;\n\tuint32_t giaddr;\n\n\tuint8_t  chaddr[16];\n\n\tuint8_t sname[64];\n\tuint8_t file[128];\n\n\tuint32_t magic;\n\n\tuint8_t  options[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct dns_packet {\n\tuint16_t qid;\n\tuint16_t flags;\n\tuint16_t questions;\n\tuint16_t answers;\n\tuint16_t authorities;\n\tuint16_t additional;\n\tuint8_t data[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct tcp_header {\n\tuint16_t source_port;\n\tuint16_t destination_port;\n\n\tuint32_t seq_number;\n\tuint32_t ack_number;\n\n\tuint16_t flags;\n\tuint16_t window_size;\n\tuint16_t checksum;\n\tuint16_t urgent;\n\n\tuint8_t  payload[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nstruct tcp_check_header {\n\tuint32_t source;\n\tuint32_t destination;\n\tuint8_t  zeros;\n\tuint8_t  protocol;\n\tuint16_t tcp_len;\n\tuint8_t  tcp_header[];\n};\n\n#define SOCK_STREAM 1\n#define SOCK_DGRAM 2\n\n// Note: Data offset is in upper 4 bits of flags field. Shift and subtract 5 since that is the min TCP size.\n//       If the value is more than 5, multiply by 4 because this field is specified in number of words\n#define TCP_OPTIONS_LENGTH(tcp) (((((tcp)->flags) >> 12) - 5) * 4)\n#define TCP_HEADER_LENGTH(tcp) ((((tcp)->flags) >> 12) * 4)\n#define TCP_HEADER_LENGTH_FLIPPED(tcp) (((htons((tcp)->flags)) >> 12) * 4)\n\n#define htonl(l)  ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24))\n#define htons(s)  ( (((s) & 0xFF) << 8) | (((s) & 0xFF00) >> 8) )\n#define ntohl(l)  htonl((l))\n#define ntohs(s)  htons((s))\n\n#define BROADCAST_MAC {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}\n#define IPV4_PROT_UDP 17\n#define IPV4_PROT_TCP 6\n#define DHCP_MAGIC 0x63825363\n\n#define TCP_FLAGS_FIN (1 << 0)\n#define TCP_FLAGS_SYN (1 << 1)\n#define TCP_FLAGS_RES (1 << 2)\n#define TCP_FLAGS_PSH (1 << 3)\n#define TCP_FLAGS_ACK (1 << 4)\n#define TCP_FLAGS_URG (1 << 5)\n#define TCP_FLAGS_ECE (1 << 6)\n#define TCP_FLAGS_CWR (1 << 7)\n#define TCP_FLAGS_NS  (1 << 8)\n#define DATA_OFFSET_5 (0x5 << 12)\n\n#define ETHERNET_TYPE_IPV4 0x0800\n#define ETHERNET_TYPE_ARP  0x0806\n\nstruct payload {\n\tstruct ethernet_packet eth_header;\n\tstruct ipv4_packet     ip_header;\n\tstruct udp_packet      udp_header;\n\tstruct dhcp_packet     dhcp_header;\n\tuint8_t payload[32];\n};\n\nstatic void ip_ntoa(const uint32_t src_addr, char * out) {\n\tsnprintf(out, 16, \"%d.%d.%d.%d\",\n\t\t(src_addr & 0xFF000000) >> 24,\n\t\t(src_addr & 0xFF0000) >> 16,\n\t\t(src_addr & 0xFF00) >> 8,\n\t\t(src_addr & 0xFF));\n}\n\nuint16_t calculate_ipv4_checksum(struct ipv4_packet * p) {\n\tuint32_t sum = 0;\n\tuint16_t * s = (uint16_t *)p;\n\n\t/* TODO: Checksums for options? */\n\tfor (int i = 0; i < 10; ++i) {\n\t\tsum += ntohs(s[i]);\n\t}\n\n\tif (sum > 0xFFFF) {\n\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t}\n\n\treturn ~(sum & 0xFFFF) & 0xFFFF;\n}\n\nuint8_t mac_addr[6];\nuint32_t xid = 0x1337;\n\nvoid fill(struct payload *it, size_t payload_size) {\n\n\tit->eth_header.source[0] = mac_addr[0];\n\tit->eth_header.source[1] = mac_addr[1];\n\tit->eth_header.source[2] = mac_addr[2];\n\tit->eth_header.source[3] = mac_addr[3];\n\tit->eth_header.source[4] = mac_addr[4];\n\tit->eth_header.source[5] = mac_addr[5];\n\n\tit->eth_header.destination[0] = 0xFF;\n\tit->eth_header.destination[1] = 0xFF;\n\tit->eth_header.destination[2] = 0xFF;\n\tit->eth_header.destination[3] = 0xFF;\n\tit->eth_header.destination[4] = 0xFF;\n\tit->eth_header.destination[5] = 0xFF;\n\n\tit->eth_header.type = htons(0x0800);\n\n\tit->ip_header.version_ihl = ((0x4 << 4) | (0x5 << 0));\n\tit->ip_header.dscp_ecn    = 0;\n\tit->ip_header.length      = htons(sizeof(struct ipv4_packet) + sizeof(struct udp_packet) + sizeof(struct dhcp_packet) + payload_size);\n\tit->ip_header.ident       = htons(1);\n\tit->ip_header.flags_fragment = 0;\n\tit->ip_header.ttl         = 0x40;\n\tit->ip_header.protocol    = IPV4_PROT_UDP;\n\tit->ip_header.checksum    = 0;\n\tit->ip_header.source      = htonl(0);\n\tit->ip_header.destination = htonl(0xFFFFFFFF);\n\n\tit->ip_header.checksum = htons(calculate_ipv4_checksum(&it->ip_header));\n\n\tit->udp_header.source_port = htons(68);\n\tit->udp_header.destination_port = htons(67);\n\tit->udp_header.length = htons(sizeof(struct udp_packet) + sizeof(struct dhcp_packet) + payload_size);\n\tit->udp_header.checksum = 0; /* uh */\n\n\tit->dhcp_header.op = 1;\n\tit->dhcp_header.htype = 1;\n\tit->dhcp_header.hlen = 6;\n\tit->dhcp_header.hops = 0;\n\tit->dhcp_header.xid = htonl(xid); /* transaction id... */\n\tit->dhcp_header.secs = 0;\n\tit->dhcp_header.flags = 0;\n\n\tit->dhcp_header.ciaddr = 0;\n\tit->dhcp_header.yiaddr = 0;\n\tit->dhcp_header.siaddr = 0;\n\tit->dhcp_header.giaddr = 0;\n\tit->dhcp_header.chaddr[0] = mac_addr[0];\n\tit->dhcp_header.chaddr[1] = mac_addr[1];\n\tit->dhcp_header.chaddr[2] = mac_addr[2];\n\tit->dhcp_header.chaddr[3] = mac_addr[3];\n\tit->dhcp_header.chaddr[4] = mac_addr[4];\n\tit->dhcp_header.chaddr[5] = mac_addr[5];\n\n\tit->dhcp_header.magic = htonl(DHCP_MAGIC);\n}\n\n\nstatic void time_diff(struct timeval *start, struct timeval *end, time_t *sec_diff, suseconds_t *usec_diff) {\n\t*sec_diff = end->tv_sec - start->tv_sec;\n\t*usec_diff = end->tv_usec - start->tv_usec;\n\tif (end->tv_usec < start->tv_usec) {\n\t\t*sec_diff -= 1;\n\t\t*usec_diff = (1000000 + end->tv_usec) - start->tv_usec;\n\t}\n}\n\nextern char * _argv_0;\n\nstatic int configure_interface(const char * if_name) {\n\t/* Open a raw socket. */\n\tint sock = socket(AF_RAW, SOCK_RAW, 0);\n\tif (sock < 0) {\n\t\tperror(_argv_0);\n\t\treturn 1;\n\t}\n\n\t/* Bind to this interface */\n\tif (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, if_name, strlen(if_name)+1)) {\n\t\tperror(_argv_0);\n\t\treturn 1;\n\t}\n\n\t/* Request the mac address */\n\tchar if_path[100];\n\tsnprintf(if_path, 100, \"/dev/net/%s\", if_name);\n\tint netdev = open(if_path, O_RDWR);\n\n\tif (netdev < 0) {\n\t\tperror(_argv_0);\n\t\treturn 1;\n\t}\n\n\tint res = ioctl(netdev, SIOCGIFHWADDR, &mac_addr);\n\n\tif (res == 1) return 0; /* Loopback, skip */\n\tif (res) {\n\t\tfprintf(stderr, \"%s: %s: could not get mac address\\n\", _argv_0, if_name);\n\t\treturn 1;\n\t}\n\n\txid = rand();\n\n\t/* Try to frob the whatsit */\n\t{\n\t\tstruct payload thething = {\n\t\t\t.payload = {53,1,1,55,2,3,6,255,0}\n\t\t};\n\n\t\tfill(&thething, 8);\n\n\t\tsend(sock, &thething, sizeof(struct payload), 0);\n\t}\n\n\tuint32_t yiaddr;\n\tint stage = 1;\n\n\tstruct timeval start, end;\n\ttime_t sec_diff;\n\tsuseconds_t usec_diff;\n\tgettimeofday(&start, NULL);\n\n\tstatic uint8_t eth_broadcast[6] = {255,255,255,255,255,255};\n\n\tdo {\n\t\tchar buf[4096] = {0};\n\n\t\tgettimeofday(&end, NULL);\n\t\ttime_diff(&start,&end,&sec_diff,&usec_diff);\n\t\tif (sec_diff > 2) {\n\t\t\tclose(netdev);\n\t\t\treturn 1;\n\t\t}\n\n\t\tstruct pollfd fds[1];\n\t\tfds[0].fd = sock;\n\t\tfds[0].events = POLLIN;\n\t\tint ret = poll(fds,1,200);\n\t\tif (ret == 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (ret < 0) {\n\t\t\tfprintf(stderr, \"poll: failed\\n\");\n\t\t\treturn 1;\n\t\t}\n\t\tssize_t rsize = recv(sock, &buf, 4096, 0);\n\n\t\tif (rsize <= 0) {\n\t\t\tfprintf(stderr, \"%s: %s: bad size? %zd\\n\", _argv_0, if_name, rsize);\n\t\t\tcontinue;\n\t\t}\n\n\t\tstruct payload * response = (void*)buf;\n\n\t\tif (memcmp(response->eth_header.destination,mac_addr,6) &&\n\t\t    memcmp(response->eth_header.destination,eth_broadcast,6)) {\n\t\t\t/* Not ours */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ntohs(response->udp_header.destination_port) != 68) {\n\t\t\t/* Not DHCP */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (ntohl(response->dhcp_header.xid) != xid) {\n\t\t\t/* Not our transaction */\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (stage == 1) {\n\t\t\tyiaddr = response->dhcp_header.yiaddr;\n\t\t\tstruct payload thething = {\n\t\t\t\t.payload = {53,1,3,50,4,\n\t\t\t\t\t(yiaddr) & 0xFF,\n\t\t\t\t\t(yiaddr >> 8) & 0xFF,\n\t\t\t\t\t(yiaddr >> 16) & 0xFF,\n\t\t\t\t\t(yiaddr >> 24) & 0xFF,\n\t\t\t\t\t55,2,3,6,255,0}\n\t\t\t};\n\t\t\tfill(&thething, 14);\n\t\t\tsend(sock, &thething, sizeof(struct payload), 0);\n\t\t\tstage = 2;\n\t\t\tgettimeofday(&start, NULL);\n\t\t} else if (stage == 2) {\n\t\t\tyiaddr = response->dhcp_header.yiaddr;\n\t\t\tchar yiaddr_ip[16];\n\t\t\tip_ntoa(ntohl(yiaddr), yiaddr_ip);\n\t\t\tif (!ioctl(netdev, SIOCSIFADDR, &yiaddr)) {\n\t\t\t\tprintf(\"%s: %s: configured for %s\\n\", _argv_0, if_name, yiaddr_ip);\n\t\t\t} else {\n\t\t\t\tperror(_argv_0);\n\t\t\t}\n\n\t\t\t/* See if we got a gateway and subnet out of it as well, those are cool... */\n\t\t\tuint8_t * opt = response->dhcp_header.options;\n\t\t\twhile (*opt && *opt != 255) {\n\t\t\t\tuint8_t opt_type = *opt++;\n\t\t\t\tuint8_t len = *opt++;\n\t\t\t\tif (opt_type == 1) {\n\t\t\t\t\t/* Subnet mask */\n\t\t\t\t\tuint32_t ip_data;\n\t\t\t\t\tmemcpy(&ip_data, opt, 4);\n\t\t\t\t\tchar addr[16];\n\t\t\t\t\tip_ntoa(ntohl(ip_data), addr);\n\t\t\t\t\tprintf(\"%s: %s: subnet mask %s\\n\", _argv_0, if_name, addr);\n\t\t\t\t\tioctl(netdev, SIOCSIFNETMASK, &ip_data);\n\t\t\t\t} else if (opt_type == 3) {\n\t\t\t\t\t/* Gateway address - add this to a route table? */\n\t\t\t\t\tuint32_t ip_data;\n\t\t\t\t\tmemcpy(&ip_data, opt, 4);\n\t\t\t\t\tchar addr[16];\n\t\t\t\t\tip_ntoa(ntohl(ip_data), addr);\n\t\t\t\t\tprintf(\"%s: %s: gateway %s\\n\", _argv_0, if_name, addr);\n\t\t\t\t\tioctl(netdev, SIOCSIFGATEWAY, &ip_data);\n\t\t\t\t} else if (opt_type == 6) {\n\t\t\t\t\t/* DNS server */\n\t\t\t\t\tuint32_t ip_data;\n\t\t\t\t\tmemcpy(&ip_data, opt, 4);\n\t\t\t\t\tchar addr[16];\n\t\t\t\t\tip_ntoa(ntohl(ip_data), addr);\n\t\t\t\t\tprintf(\"%s: %s: nameserver %s\\n\", _argv_0, if_name, addr);\n\t\t\t\t\tFILE * resolve = fopen(\"/etc/resolv.conf\",\"w\");\n\t\t\t\t\tif (!resolve) resolve = fopen(\"/var/resolv.conf\",\"w\");\n\t\t\t\t\tif (resolve) {\n\t\t\t\t\t\tfprintf(resolve, \"nameserver %s\\n\", addr);\n\t\t\t\t\t\tfclose(resolve);\n\t\t\t\t\t} /* else, read-only file system? */\n\t\t\t\t}\n\t\t\t\topt += len;\n\t\t\t}\n\n\t\t\tclose(netdev);\n\t\t\tclose(sock);\n\t\t\treturn 0;\n\t\t}\n\t} while (1);\n\n\treturn 1;\n}\n\nstatic int configure_interface_with_backoff(const char * if_name) {\n\tint sleep_times[] = {1,3,5,0};\n\n\tfor (int *time = sleep_times; *time; time++) {\n\t\tif (!configure_interface(if_name)) return 0;\n\t\tsleep(*time);\n\t}\n\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tint retval = 0;\n\n\tif (argc > 1) {\n\t\treturn configure_interface(argv[1]);\n\t} else {\n\t\t/* Read /dev/net for interfaces */\n\t\tDIR * d = opendir(\"/dev/net\");\n\t\tif (!d) {\n\t\t\tfprintf(stderr, \"%s: no network?\\n\", _argv_0);\n\t\t\treturn 1;\n\t\t}\n\n\t\tstruct dirent * ent;\n\t\twhile ((ent = readdir(d))) {\n\t\t\tif (ent->d_name[0] == '.') continue;\n\t\t\tif (configure_interface_with_backoff(ent->d_name)) {\n\t\t\t\tretval = 1;\n\t\t\t}\n\t\t}\n\n\t\tclosedir(d);\n\t}\n\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/dirname.c",
    "content": "/**\n * @brief dirname - print directory name from path string\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <libgen.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: expected argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tchar * c = dirname(argv[1]);\n\tfprintf(stdout, \"%s\\n\", c);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/drawlines.c",
    "content": "/**\n * @brief drawlines - Draw random lines into a GUI window\n *\n * The original compositor demo application, this dates all the\n * way back to the original pre-Yutani compositor. Opens a very\n * basic window (no decorations) and randomly fills it with\n * colorful lines.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n#include <time.h>\n#include <sched.h>\n#include <math.h>\n\n#include <sys/fswait.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\nstatic int left, top, width, height;\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx;\nstatic int should_exit = 0;\nstatic int thick = 0;\n\nstatic void draw(void) {\n\tif (thick) {\n\t\tdraw_line_aa(ctx, rand() % width, rand() % width, rand() % height, rand() % height, rgb(rand() % 255,rand() % 255,rand() % 255), (float)thick);\n\t} else {\n\t\tdraw_line(ctx, rand() % width, rand() % width, rand() % height, rand() % height, rgb(rand() % 255,rand() % 255,rand() % 255));\n\t}\n\tyutani_flip(yctx, wina);\n}\n\nstatic void show_usage(char * argv[]) {\n\tprintf(\n\t\t\t\"drawlines - graphical demo, draws lines randomly\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-t thickness]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -t     \\033[3mdraw with anti-aliasing and the specified thickness\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\n\nint main (int argc, char ** argv) {\n\tleft   = 100;\n\ttop    = 100;\n\twidth  = 500;\n\theight = 500;\n\n\tsrand(time(NULL));\n\n\tint c;\n\twhile ((c = getopt(argc, argv, \"t:?\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 't':\n\t\t\t\tthick = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\twina = yutani_window_create(yctx, width, height);\n\tyutani_window_move(yctx, wina, left, top);\n\tyutani_window_advertise_icon(yctx, wina, \"drawlines\", \"drawlines\");\n\n\tctx = init_graphics_yutani(wina);\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\twhile (!should_exit) {\n\t\tint fds[1] = {fileno(yctx->sock)};\n\t\tint index = fswait2(1,fds,20);\n\t\tif (index == 0) {\n\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\twhile (m) {\n\t\t\t\tswitch (m->type) {\n\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\tsched_yield();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\tyutani_window_drag_start(yctx, wina);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t}\n\t\t}\n\t\tdraw();\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/du.c",
    "content": "/**\n * @brief du - calculate file size usage\n *\n * TODO: Should use st_blocks, but we don't set that in the kernel yet?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <dirent.h>\n\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n\nstatic int show_total = 0;\nstatic int human = 0;\nstatic int all = 1;\nstatic int is_arg = 0;\n\nstatic uint64_t count_thing(char * tmp);\n\nstatic int print_human_readable_size(char * _out, size_t s) {\n\tif (s >= 1<<30) {\n\t\tsize_t t = s / (1 << 30);\n\t\treturn sprintf(_out, \"%d.%1dG\", (int)t, (int)(s - t * (1 << 30)) / ((1 << 30) / 10));\n\t} else if (s >= 1<<20) {\n\t\tsize_t t = s / (1 << 20);\n\t\treturn sprintf(_out, \"%d.%1dM\", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10));\n\t} else if (s >= 1<<10) {\n\t\tsize_t t = s / (1 << 10);\n\t\treturn sprintf(_out, \"%d.%1dK\", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10));\n\t} else {\n\t\treturn sprintf(_out, \"%d\", (int)s);\n\t}\n}\n\nstatic void print_size(uint64_t size, char * name) {\n\tchar sizes[30];\n\tif (!human) {\n\t\tsprintf(sizes, \"%-7llu\", size/1024LLU);\n\t} else {\n\t\tprint_human_readable_size(sizes, size);\n\t}\n\tif (strlen(name) > 2 && name[0] == '/' && name[1] == '/') {\n\t\tname = &name[1];\n\t}\n\tfprintf(stdout, \"%7s %s\\n\", sizes, name);\n}\n\nstatic uint64_t count_directory(char * source) {\n\tDIR * dirp = opendir(source);\n\tif (dirp == NULL) {\n\t\t//fprintf(stderr, \"could not open %s\\n\", source);\n\t\treturn 0;\n\t}\n\n\tint was_arg = is_arg;\n\tis_arg = 0;\n\n\tuint64_t total = 0;\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (!strcmp(ent->d_name,\".\") || !strcmp(ent->d_name,\"..\")) {\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\tchar tmp[strlen(source)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp, \"%s/%s\", source, ent->d_name);\n\t\ttotal += count_thing(tmp);\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tif (all || was_arg) {\n\t\tprint_size(total, source);\n\t}\n\n\treturn total;\n}\n\nstatic uint64_t count_thing(char * tmp) {\n\tstruct stat statbuf;\n\tlstat(tmp,&statbuf);\n\tif (S_ISDIR(statbuf.st_mode)) {\n\t\treturn count_directory(tmp);\n\t} else {\n\t\tif (is_arg) {\n\t\t\tprint_size(statbuf.st_size, tmp);\n\t\t}\n\t\treturn statbuf.st_size;\n\t}\n}\n\n\nint main(int argc, char * argv[]) {\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"hsc\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'h': /* human readable */\n\t\t\t\thuman = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tshow_total = 1;\n\t\t\t\tbreak;\n\t\t\tcase 's': /* summary */\n\t\t\t\tall = 0;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"%s: unrecognized option '%c'\\n\", argv[0], opt);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tint ret = 0;\n\tuint64_t total = 0;\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tis_arg = 1;\n\t\ttotal += count_thing(argv[i]);\n\t}\n\n\tif (show_total) {\n\t\tprint_size(total, \"total\");\n\t}\n\n\treturn ret;\n}\n\n"
  },
  {
    "path": "apps/echo.c",
    "content": "/**\n * @brief echo - Print arguments to stdout.\n *\n * Prints arguments to stdout, possibly interpreting escape\n * sequences in the arguments.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n\nvoid show_usage(char * argv[]) {\n\tprintf(\n\t\t\t\"echo - print arguments\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-ne] ARG...\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -n     \\033[3mdo not output a new line at the end\\033[0m\\n\"\n\t\t\t\" -e     \\033[3mprocess escape sequences\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nint main(int argc, char ** argv) {\n\tint use_newline     = 1;\n\tint process_escapes = 0;\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"enh?\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\tcase 'h':\n\t\t\t\tshow_usage(argv);\n\t\t\t\treturn 1;\n\t\t\tcase 'n':\n\t\t\t\tuse_newline = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'e':\n\t\t\t\tprocess_escapes = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tif (process_escapes) {\n\t\t\tchar * c = argv[i];\n\t\t\twhile (*c) {\n\t\t\t\tif (*c == '\\\\') {\n\t\t\t\t\tc++;\n\t\t\t\t\tswitch (*c) {\n\t\t\t\t\t\tcase '\\\\':\n\t\t\t\t\t\t\tputchar('\\\\');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'a':\n\t\t\t\t\t\t\tputchar('\\a');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'b':\n\t\t\t\t\t\t\tputchar('\\b');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'c':\n\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\tputchar('\\033');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\tputchar('\\f');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'n':\n\t\t\t\t\t\t\tputchar('\\n');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 't':\n\t\t\t\t\t\t\tputchar('\\t');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'v':\n\t\t\t\t\t\t\tputchar('\\v');\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase '0':\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tint i = 0;\n\t\t\t\t\t\t\t\tif (!isdigit(*(c+1)) || *(c+1) > '7') {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tc++;\n\t\t\t\t\t\t\t\ti = *c - '0';\n\t\t\t\t\t\t\t\tif (isdigit(*(c+1)) && *(c+1) <= '7') {\n\t\t\t\t\t\t\t\t\tc++;\n\t\t\t\t\t\t\t\t\ti = (i << 3) | (*c - '0');\n\t\t\t\t\t\t\t\t\tif (isdigit(*(c+1)) && *(c+1) <= '7') {\n\t\t\t\t\t\t\t\t\t\tc++;\n\t\t\t\t\t\t\t\t\t\ti = (i << 3) | (*c - '0');\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tputchar(i);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tputchar('\\\\');\n\t\t\t\t\t\t\tputchar(*c);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tputchar(*c);\n\t\t\t\t}\n\t\t\t\tc++;\n\t\t\t}\n\t\t} else {\n\t\t\tprintf(\"%s\",argv[i]);\n\t\t}\n\t\tif (i != argc - 1) {\n\t\t\tprintf(\" \");\n\t\t}\n\t}\n\n\tif (use_newline) {\n\t\tprintf(\"\\n\");\n\t}\n\n\tfflush(stdout);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/env.c",
    "content": "/**\n * @brief env - Print or set environment\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <errno.h>\n\nextern int _environ_size;\n\nint main(int argc, char ** argv) {\n\tint start = 1;\n\n\tif (start < argc && !strcmp(argv[start],\"-i\")) {\n\t\tfor (int i = 0; i < _environ_size; ++i) {\n\t\t\tenviron[i] = NULL;\n\t\t}\n\t\tstart++;\n\t}\n\n\tfor (; start < argc; ++start) {\n\t\tif (!strchr(argv[start],'=')) {\n\t\t\tbreak;\n\t\t} else {\n\t\t\tputenv(argv[start]);\n\t\t}\n\t}\n\n\tif (start < argc) {\n\t\t/* Execute command */\n\t\tif (execvp(argv[start], &argv[start])) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[start], strerror(errno));\n\t\t}\n\t} else {\n\t\tchar ** env = environ;\n\n\t\twhile (*env) {\n\t\t\tprintf(\"%s\\n\", *env);\n\t\t\tenv++;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/esh.c",
    "content": "/**\n * @brief E-Shell\n *\n * This is \"experimental shell\" - a vaguely-unix-like command\n * interface. It has a very rudimentary parser that understands\n * some things like pipes or writing out to a file. It has a\n * handful of built-in commands, including ones that implement\n * some more useful shell syntax such as loops and conditionals.\n * There is support for tab completion of filenames and commands.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2022 K. Lange\n */\n#define _XOPEN_SOURCE 500\n#define _POSIX_C_SOURCE 200809L\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <time.h>\n#include <dirent.h>\n#include <signal.h>\n#include <getopt.h>\n#include <termios.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <ctype.h>\n#include <wchar.h>\n\n#include <sys/time.h>\n#include <sys/times.h>\n#include <sys/wait.h>\n#include <sys/utsname.h>\n#include <sys/stat.h>\n\n#include <toaru/list.h>\n#include <toaru/hashmap.h>\n#include <toaru/kbd.h>\n#include <toaru/rline.h>\n#include <toaru/decodeutf8.h>\n\n#ifndef environ\nextern char **environ;\n#endif\n\n#define PIPE_TOKEN \"\\xFF\\xFFPIPE\\xFF\\xFF\"\n#define STAR_TOKEN \"\\xFF\\xFFSTAR\\xFF\\xFF\"\n#define WRITE_TOKEN \"\\xFF\\xFFWRITE\\xFF\\xFF\"\n#define WRITE_ERR_TOKEN \"\\xFF\\xFFWRITEERR\\xFF\\xFF\"\n#define APPEND_TOKEN \"\\xFF\\xFF\"\"APPEND\\xFF\"\n\n/* A shell command is like a C program */\ntypedef uint32_t(*shell_command_t) (int argc, char ** argv);\n\n/* We have a static array that fits a certain number of them. */\nint SHELL_COMMANDS = 64;\nchar ** shell_commands;          /* Command names */\nshell_command_t * shell_pointers; /* Command functions */\nchar ** shell_descript;          /* Command descriptions */\nFILE * shell_stderr; /* specifically for `time` */\n\n/* This is the number of actual commands installed */\nint shell_commands_len = 0;\n\nint shell_interactive = 1;\nint last_ret = 0;\nchar ** shell_argv = NULL;\nint shell_argc = 0;\n\nstatic int current_line = 0;\nstatic char * current_file = NULL;\n\nint pid; /* Process ID of the shell */\nint my_pgid;\n\nint is_subshell = 0;\n\nstruct semaphore {\n\tint fds[2];\n};\n\nstruct semaphore create_semaphore(void) {\n\tstruct semaphore out;\n\tpipe(out.fds);\n\treturn out;\n}\n\nvoid raise_semaphore(struct semaphore s){\n\twrite(s.fds[1],\"x\",1);\n\tclose(s.fds[0]);\n\tclose(s.fds[1]);\n}\n\nvoid wait_semaphore(struct semaphore s) {\n\tclose(s.fds[1]);\n\tchar buf;\n\tread(s.fds[0], &buf, 1);\n\tclose(s.fds[0]);\n}\n\nvoid set_pgid(int pgid) {\n\tif (shell_interactive == 1) {\n\t\tsetpgid(0, pgid);\n\t}\n}\n\nvoid set_pgrp(int pgid) {\n\tif (shell_interactive == 1 && !is_subshell) {\n\t\tsigset_t ss;\n\t\tsigfillset(&ss);\n\t\tsigprocmask(SIG_SETMASK, &ss, NULL);\n\t\ttcsetpgrp(STDIN_FILENO, pgid);\n\t\tsigemptyset(&ss);\n\t\tsigprocmask(SIG_SETMASK, &ss, NULL);\n\t}\n}\n\nvoid reset_pgrp() {\n\tset_pgrp(my_pgid);\n}\n\nvoid shell_install_command(char * name, shell_command_t func, char * desc) {\n\tif (shell_commands_len == SHELL_COMMANDS-1) {\n\t\tSHELL_COMMANDS *= 2;\n\t\tshell_commands = realloc(shell_commands, sizeof(char *) * SHELL_COMMANDS);\n\t\tshell_pointers = realloc(shell_pointers, sizeof(shell_command_t) * SHELL_COMMANDS);\n\t\tshell_descript = realloc(shell_descript, sizeof(char *) * SHELL_COMMANDS);\n\t}\n\tshell_commands[shell_commands_len] = name;\n\tshell_pointers[shell_commands_len] = func;\n\tshell_descript[shell_commands_len] = desc;\n\tshell_commands_len++;\n\tshell_commands[shell_commands_len] = NULL;\n}\n\nshell_command_t shell_find(char * str) {\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tif (!strcmp(str, shell_commands[i])) {\n\t\t\treturn shell_pointers[i];\n\t\t}\n\t}\n\treturn NULL;\n}\n\nvoid install_commands();\n\n/* Maximum command length */\n#define LINE_LEN 4096\n\n/* Current working directory */\nchar cwd[1024] = {'/',0};\n\n/* Username */\nchar username[1024];\n\n/* Hostname for prompt */\nchar _hostname[256];\n\n/* function to update the cached username */\nvoid getuser() {\n\tchar * tmp = getenv(\"USER\");\n\tif (tmp) {\n\t\tstrcpy(username, tmp);\n\t} else {\n\t\tsprintf(username, \"%d\", getuid());\n\t}\n}\n\n/* function to update the cached hostname */\nvoid gethost() {\n\tstruct utsname buf;\n\n\tuname(&buf);\n\n\tint len = strlen(buf.nodename);\n\tmemcpy(_hostname, buf.nodename, len+1);\n}\n\nint display_width_of_string(const char * str) {\n\tuint8_t * s = (uint8_t *)str;\n\n\tint out = 0;\n\tuint32_t c, state = 0;\n\twhile (*s) {\n\t\tif (!decode(&state, &c, *s)) {\n\t\t\tout += wcwidth(c);\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t\ts++;\n\t}\n\n\treturn out;\n}\n\nvoid print_extended_ps(char * format, char * buffer, int * display_width) {\n\t/* Get the time */\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL); //time(NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\t/* Format the date and time for prompt display */\n\tchar date_buffer[80];\n\tstrftime(date_buffer, 80, \"%m/%d\", timeinfo);\n\tchar time_buffer[80];\n\tstrftime(time_buffer, 80, \"%H:%M:%S\", timeinfo);\n\n\t/* Collect the current working directory */\n\tgetcwd(cwd, 512);\n\tchar _cwd[512];\n\tstrcpy(_cwd, cwd);\n\n\t/* Collect the user's home directory and apply it to cwd */\n\tchar * home = getenv(\"HOME\");\n\tif (home && strstr(cwd, home) == cwd) {\n\t\tchar * c = cwd + strlen(home);\n\t\tif (*c == '/' || *c == 0) {\n\t\t\tsprintf(_cwd, \"~%s\", c);\n\t\t}\n\t}\n\n\tchar ret[80] = {0};\n\tif (last_ret != 0) {\n\t\tsprintf(ret, \"%d \", last_ret);\n\t}\n\n\tsize_t offset = 0;\n\tint is_visible = 1;\n\tchar dispchars[1024] = {0};\n\tchar * dispout = dispchars;\n\n\twhile (*format) {\n\t\tif (*format == '\\\\') {\n\t\t\tformat++;\n\t\t\tswitch (*format) {\n\t\t\t\tcase '\\\\':\n\t\t\t\t\tbuffer[offset++] = *format;\n\t\t\t\t\tif (is_visible) *dispout++ = *format;\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '[':\n\t\t\t\t\tis_visible = 0;\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase ']':\n\t\t\t\t\tis_visible = 1;\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '0':\n\t\t\t\tcase '1':\n\t\t\t\tcase '2':\n\t\t\t\tcase '3':\n\t\t\t\tcase '4':\n\t\t\t\tcase '5':\n\t\t\t\tcase '6':\n\t\t\t\tcase '7':\n\t\t\t\t\t{\n\t\t\t\t\t\tint i = (*format) - '0';\n\t\t\t\t\t\tformat++;\n\t\t\t\t\t\tif (*format >= '0' && *format <= '7') {\n\t\t\t\t\t\t\ti *= 8;\n\t\t\t\t\t\t\ti += (*format) - '0';\n\t\t\t\t\t\t\tformat++;\n\t\t\t\t\t\t\tif (*format >= '0' && *format <= '7') {\n\t\t\t\t\t\t\t\ti *= 8;\n\t\t\t\t\t\t\t\ti += (*format) - '0';\n\t\t\t\t\t\t\t\tformat++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbuffer[offset++] = i;\n\t\t\t\t\t\tif (is_visible) *dispout++ = i;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'e':\n\t\t\t\t\tbuffer[offset++] = '\\033';\n\t\t\t\t\tif (is_visible) *dispout++ = '\\033';\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'd':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", date_buffer);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", date_buffer); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 't':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", time_buffer);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", time_buffer); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'h':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", _hostname);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", _hostname); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'u':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", username);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", username); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'w':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", _cwd);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", _cwd); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '$':\n\t\t\t\t\tbuffer[offset++] = (getuid() == 0 ? '#' : '$');\n\t\t\t\t\tif (is_visible) *dispout++ = (getuid() == 0 ? '#' : '$');\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'U': /* prompt color string */\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", getuid() == 0 ? \"\\033[1;38;5;196m\" : \"\\033[1;38;5;47m\");\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\t/* Does not affect size */\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'r':\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"%s\", ret);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"%s\", ret); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t{\n\t\t\t\t\t\tint size = sprintf(buffer+offset, \"\\\\%c\", *format);\n\t\t\t\t\t\toffset += size;\n\t\t\t\t\t\tif (is_visible) { dispout += sprintf(dispout, \"\\\\%c\", *format); }\n\t\t\t\t\t}\n\t\t\t\t\tformat++;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tbuffer[offset++] = *format;\n\t\t\tif (is_visible) *dispout++ = *format;\n\t\t\tformat++;\n\t\t}\n\t}\n\n\t*display_width = display_width_of_string(dispchars);\n\n\tbuffer[offset] = '\\0';\n}\n\nvolatile int break_while = 0;\npid_t suspended_pgid = 0;\nhashmap_t * job_hash = NULL;\nhashmap_t * desc_hash = NULL;\n\nvoid sig_break_loop(int sig) {\n\t/* Interrupt handler */\n\tif ((shell_interactive == 1 && !is_subshell)) {\n\t\tbreak_while = sig;\n\t\tsignal(sig, sig_break_loop);\n\t} else {\n\t\tsignal(sig, SIG_DFL);\n\t\traise(sig);\n\t}\n}\n\nvoid tab_complete_func(rline_context_t * c) {\n\tchar * dup = malloc(LINE_LEN);\n\t\n\tmemcpy(dup, c->buffer, LINE_LEN);\n\n\tchar *pch, *save;\n\tchar *argv[1024];\n\tint argc = 0;\n\tint cursor = 0;\n\n\tpch = strtok_r(dup, \" \", &save);\n\n\tif (!pch) {\n\t\targv[0] = \"\";\n\t\targc = 0;\n\t}\n\n\twhile (pch != NULL) {\n\t\tif (pch - dup <= c->offset) cursor = argc;\n\t\targv[argc] = pch;\n\t\t++argc;\n\t\tpch = strtok_r(NULL, \" \", &save);\n\t}\n\targv[argc] = NULL;\n\n\tif (c->offset && c->buffer[c->offset-1] == ' ' && argc) {\n\t\tcursor++;\n\t}\n\n\tchar * word = argv[cursor];\n\tint word_offset = word ? (c->offset - (argv[cursor] - dup)) : 0;\n\n\tchar * prefix = malloc(word_offset + 1);\n\tif (word) memcpy(prefix, word, word_offset);\n\tprefix[word_offset] = '\\0';\n\n\t/* Complete file path */\n\tlist_t * matches = list_create();\n\tchar * match = NULL;\n\tint free_matches = 0;\n\tint no_space_if_only = 0;\n\n\t/* TODO custom auto-complete as a configuration file? */\n#define COMPLETE_FILE    1\n#define COMPLETE_COMMAND 2\n#define COMPLETE_CUSTOM  3\n#define COMPLETE_VARIABLE 4\n\tint complete_mode = COMPLETE_FILE;\n\tint with_dollar = 0;\n\n\tint command_adj = 0;\n\tint cursor_adj = cursor;\n\n\twhile (command_adj < argc && strstr(argv[command_adj],\"=\")) {\n\t\tcursor_adj -= 1;\n\t\tcommand_adj += 1;\n\t}\n\n\t/* Various commands are generally prefixes */\n\tif (command_adj < argc && (\n\t\t!strcmp(argv[command_adj], \"sudo\") ||\n\t\t!strcmp(argv[command_adj], \"gsudo\") ||\n\t\t!strcmp(argv[command_adj], \"time\") ||\n\t\t/* TODO: Both of these may take additional arguments... */\n\t\t!strcmp(argv[command_adj], \"strace\") ||\n\t\t!strcmp(argv[command_adj], \"dbg\")\n\t)) {\n\t\tcursor_adj -= 1;\n\t\tcommand_adj += 1;\n\t}\n\n\t/* initial tab completion should be commands, unless typing a file path */\n\tif (cursor_adj == 0 && !strchr(prefix,'/')) {\n\t\tcomplete_mode = COMPLETE_COMMAND;\n\t}\n\n\tif (cursor_adj >= 1 && !strcmp(argv[command_adj], \"toggle-abs-mouse\")) {\n\t\tcomplete_mode = COMPLETE_CUSTOM;\n\t}\n\n\tif (cursor_adj >= 1 && !strcmp(argv[command_adj], \"msk\")) {\n\t\tcomplete_mode = COMPLETE_CUSTOM;\n\t}\n\n\tif (cursor_adj >= 1 && !strcmp(argv[command_adj], \"ifconfig\")) {\n\t\tcomplete_mode = COMPLETE_CUSTOM;\n\t}\n\n\tif (cursor_adj >= 1 && !strcmp(argv[command_adj], \"unset\")) {\n\t\tcomplete_mode = COMPLETE_VARIABLE;\n\t}\n\n\t/* complete variable names */\n\tif (*prefix == '$') {\n\t\tcomplete_mode = COMPLETE_VARIABLE;\n\t\twith_dollar = 1;\n\t}\n\n\tif (complete_mode == COMPLETE_COMMAND) {\n\t\t/* Complete a command name */\n\n\t\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\t\tif (strstr(shell_commands[i], prefix) == shell_commands[i]) {\n\t\t\t\tlist_insert(matches, shell_commands[i]);\n\t\t\t\tmatch = shell_commands[i];\n\t\t\t}\n\t\t}\n\t} else if (complete_mode == COMPLETE_FILE) {\n\t\t/* Complete a file path */\n\n\t\tfree_matches = 1;\n\t\tchar * tmp = strdup(prefix);\n\t\tchar * last_slash = strrchr(tmp, '/');\n\t\tDIR * dirp;\n\t\tchar * compare = prefix;\n\t\tif (last_slash) {\n\t\t\t*last_slash = '\\0';\n\t\t\tword = word + (last_slash - tmp) + 1;\n\t\t\tword_offset = word_offset - (last_slash - tmp + 1);\n\t\t\tcompare = word;\n\t\t\tif (last_slash == tmp) {\n\t\t\t\tdirp = opendir(\"/\");\n\t\t\t} else {\n\t\t\t\tchar * home;\n\t\t\t\tif (*tmp == '~' && (home = getenv(\"HOME\"))) {\n\t\t\t\t\tchar * t = malloc(strlen(tmp) + strlen(home) + 4);\n\t\t\t\t\tsprintf(t, \"%s%s\",home,tmp+1);\n\t\t\t\t\tdirp = opendir(t);\n\t\t\t\t\tfree(t);\n\t\t\t\t} else {\n\t\t\t\t\tdirp = opendir(tmp);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tdirp = opendir(\".\");\n\t\t}\n\n\t\tif (!dirp) {\n\t\t\tfree(tmp);\n\t\t\tgoto finish_tab;\n\t\t}\n\n\t\tstruct dirent * ent = readdir(dirp);\n\t\twhile (ent != NULL) {\n\t\t\tif (ent->d_name[0] != '.' || compare[0] == '.') {\n\t\t\t\tif (!word || strstr(ent->d_name, compare) == ent->d_name) {\n\t\t\t\t\tstruct stat statbuf;\n\t\t\t\t\t/* stat it */\n\t\t\t\t\tif (last_slash) {\n\t\t\t\t\t\tchar * x;\n\t\t\t\t\t\tchar * home;\n\t\t\t\t\t\tif (tmp[0] == '~' && (home = getenv(\"HOME\"))) {\n\t\t\t\t\t\t\tx = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1);\n\t\t\t\t\t\t\tsnprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1, \"%s%s/%s\",home,tmp+1,ent->d_name);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tx = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1);\n\t\t\t\t\t\t\tsnprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1, \"%s/%s\",tmp,ent->d_name);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlstat(x, &statbuf);\n\t\t\t\t\t\tfree(x);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlstat(ent->d_name, &statbuf);\n\t\t\t\t\t}\n\t\t\t\t\tchar * s;\n\t\t\t\t\tif (S_ISDIR(statbuf.st_mode)) {\n\t\t\t\t\t\ts = malloc(strlen(ent->d_name) + 2);\n\t\t\t\t\t\tsprintf(s,\"%s/\", ent->d_name);\n\t\t\t\t\t\tno_space_if_only = 1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts = strdup(ent->d_name);\n\t\t\t\t\t}\n\t\t\t\t\tlist_insert(matches, s);\n\t\t\t\t\tmatch = s;\n\t\t\t\t}\n\t\t\t}\n\t\t\tent = readdir(dirp);\n\t\t}\n\t\tclosedir(dirp);\n\n\t\tfree(tmp);\n\t} else if (complete_mode == COMPLETE_CUSTOM) {\n\n\t\tchar * none[] = {NULL};\n\t\tchar ** completions = none;\n\t\tchar * toggle_abs_mouse_completions[] = {\"relative\",\"absolute\",NULL};\n\t\tchar * msk_commands[] = {\"update\",\"install\",\"list\",\"count\",\"--version\",NULL};\n\t\tchar * ifconfig_commands[] = {\"inet\",\"netmask\",\"gateway\",NULL};\n\n\t\tif (!strcmp(argv[command_adj],\"toggle-abs-mouse\")) {\n\t\t\tcompletions = toggle_abs_mouse_completions;\n\t\t} else if (!strcmp(argv[command_adj], \"msk\")) {\n\t\t\tif (cursor_adj == 1) {\n\t\t\t\tcompletions = msk_commands;\n\t\t\t} else if (cursor_adj > 1 && !strcmp(argv[command_adj+1],\"install\")) {\n\t\t\t\tFILE * f = fopen(\"/var/msk/manifest\",\"r\");\n\t\t\t\tlist_t * packages = list_create();\n\t\t\t\tif (f) {\n\t\t\t\t\twhile (!feof(f)) {\n\t\t\t\t\t\tchar tmp[4096] = {0};\n\t\t\t\t\t\tif (!fgets(tmp, 4096, f)) break;\n\t\t\t\t\t\tif (tmp[0] == '[') {\n\t\t\t\t\t\t\tchar * s = strstr(tmp, \"]\");\n\t\t\t\t\t\t\tif (s) { *s = '\\0'; }\n\t\t\t\t\t\t\tlist_insert(packages, strdup(tmp+1));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcompletions = malloc(sizeof(char *) * (packages->length + 1));\n\t\t\t\t\tfree_matches = 1;\n\t\t\t\t\tsize_t i = 0;\n\t\t\t\t\tforeach(node, packages) {\n\t\t\t\t\t\tcompletions[i++] = node->value;\n\t\t\t\t\t}\n\t\t\t\t\tcompletions[i] = NULL;\n\t\t\t\t\tlist_free(packages);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (!strcmp(argv[command_adj], \"ifconfig\")) {\n\t\t\tif (cursor_adj == 1) {\n\t\t\t\t/* interface names */\n\t\t\t\tDIR * d = opendir(\"/dev/net\");\n\t\t\t\tif (d) {\n\t\t\t\t\tfree_matches = 1;\n\t\t\t\t\tlist_t * interfaces = list_create();\n\n\t\t\t\t\tstruct dirent * ent;\n\t\t\t\t\twhile ((ent = readdir(d))) {\n\t\t\t\t\t\tif (ent->d_name[0] == '.') continue;\n\t\t\t\t\t\tlist_insert(interfaces, strdup(ent->d_name));\n\t\t\t\t\t}\n\t\t\t\t\tclosedir(d);\n\n\t\t\t\t\tcompletions = malloc(sizeof(char*) * (interfaces->length + 1));\n\t\t\t\t\tsize_t i = 0;\n\t\t\t\t\tforeach(node, interfaces) {\n\t\t\t\t\t\tcompletions[i++] = node->value;\n\t\t\t\t\t}\n\t\t\t\t\tcompletions[i] = NULL;\n\t\t\t\t\tlist_free(interfaces);\n\t\t\t\t}\n\t\t\t} else if (cursor_adj > 1) {\n\t\t\t\tcompletions = ifconfig_commands;\n\t\t\t}\n\t\t}\n\n\t\twhile (*completions) {\n\t\t\tif (strstr(*completions, prefix) == *completions) {\n\t\t\t\tlist_insert(matches, *completions);\n\t\t\t\tmatch = *completions;\n\t\t\t}\n\t\t\tcompletions++;\n\t\t}\n\n\t} else if (complete_mode == COMPLETE_VARIABLE) {\n\n\t\tchar ** envvar = environ;\n\t\tfree_matches = 1;\n\n\t\twhile (*envvar) {\n\t\t\tchar * tmp = strdup(*envvar);\n\t\t\tchar * c = strchr(tmp, '=');\n\t\t\t*c = '\\0';\n\t\t\tif (strstr(tmp, prefix+with_dollar) == tmp) {\n\t\t\t\tchar * m = malloc(strlen(tmp)+1+with_dollar);\n\t\t\t\tsprintf(m, \"%s%s\", with_dollar ? \"$\" : \"\", tmp);\n\t\t\t\tlist_insert(matches, m);\n\t\t\t\tmatch = m;\n\t\t\t}\n\t\t\tfree(tmp);\n\t\t\tenvvar++;\n\t\t}\n\n\t}\n\n\tif (matches->length == 1) {\n\t\t/* Insert */\n\t\trline_insert(c, &match[word_offset]);\n\t\tif (word && word_offset == (int)strlen(word) && !no_space_if_only) {\n\t\t\trline_insert(c, \" \");\n\t\t}\n\t\trline_redraw(c);\n\t} else if (matches->length > 1) {\n\t\tif (!c->tabbed) {\n\t\t\t/* see if there is a minimum subset we can fill in */\n\t\t\tsize_t j = word_offset;\n\t\t\tdo {\n\t\t\t\tchar d = match[j];\n\t\t\t\tint diff = 0;\n\t\t\t\tforeach(node, matches) {\n\t\t\t\t\tchar * match = (char *)node->value;\n\t\t\t\t\tif (match[j] != d || match[j] == '\\0') diff = 1;\n\t\t\t\t}\n\t\t\t\tif (diff) break;\n\t\t\t\tj++;\n\t\t\t} while (j < (size_t)c->requested);\n\t\t\tif (j > (size_t)word_offset) {\n\t\t\t\tchar * tmp = strdup(match);\n\t\t\t\ttmp[j] = '\\0';\n\t\t\t\trline_insert(c, &tmp[word_offset]);\n\t\t\t\trline_redraw(c);\n\t\t\t\tfree(tmp);\n\t\t\t} else {\n\t\t\t\tc->tabbed = 1;\n\t\t\t}\n\t\t} else {\n\t\t\t/* Print matches */\n\t\t\tfprintf(stderr,\"\\n\\033[0m\");\n\t\t\tsize_t j = 0;\n\t\t\tforeach(node, matches) {\n\t\t\t\tchar * match = (char *)node->value;\n\t\t\t\tfprintf(stderr, \"%s\", match);\n\t\t\t\t++j;\n\t\t\t\tif (j < matches->length) {\n\t\t\t\t\tfprintf(stderr, \", \");\n\t\t\t\t}\n\t\t\t}\n\t\t\tfprintf(stderr,\"\\n\");\n\t\t\tc->callbacks->redraw_prompt(c);\n\t\t\tfprintf(stderr, \"\\033[s\");\n\t\t\trline_redraw(c);\n\t\t}\n\t}\n\nfinish_tab:\n\tif (free_matches) list_destroy(matches);\n\tlist_free(matches);\n\tfree(prefix);\n\tfree(dup);\n\n}\n\nvoid add_argument(list_t * argv, char * buf) {\n\tchar * c = malloc(strlen(buf) + 1);\n\tmemcpy(c, buf, strlen(buf) + 1);\n\n\tlist_insert(argv, c);\n}\n\nvoid add_environment(list_t * env) {\n\tif (env->length) {\n\t\tforeach (node, env) {\n\t\t\tchar * c = node->value;\n\t\t\tputenv(strdup(c));\n\t\t}\n\t}\n}\n\n#define FALLBACK_PS1 \"\\\\u@\\\\h \\\\w\\\\$ \"\nint read_entry(char * buffer) {\n\tchar lprompt[1024], rprompt[1024];\n\tint lwidth, rwidth;\n\n\tchar * ps1 = getenv(\"PS1_LEFT\");\n\tprint_extended_ps(ps1 ? ps1 : FALLBACK_PS1, lprompt, &lwidth);\n\n\tchar * ps1r = getenv(\"PS1_RIGHT\");\n\tprint_extended_ps(ps1r ? ps1r : \"\", rprompt, &rwidth);\n\n\trline_exit_string=\"exit\";\n\trline_exp_set_syntax(\"esh\");\n\trline_exp_set_prompts(lprompt, rprompt, lwidth, rwidth);\n\trline_exp_set_shell_commands(shell_commands, shell_commands_len);\n\trline_exp_set_tab_complete_func(tab_complete_func);\n\treturn rline(buffer, LINE_LEN);\n}\n\nint read_entry_continued(char * buffer) {\n\trline_exit_string=\"exit\";\n\trline_exp_set_syntax(\"esh\");\n\trline_exp_set_prompts(\"> \", \"\", 2, 0);\n\trline_exp_set_shell_commands(shell_commands, shell_commands_len);\n\trline_exp_set_tab_complete_func(tab_complete_func);\n\treturn rline(buffer, LINE_LEN);\n}\n\nint variable_char(uint8_t c) {\n\tif (c >= 'A' && c <= 'Z')  return 1;\n\tif (c >= 'a' && c <= 'z') return 1;\n\tif (c >= '0' && c <= '9')  return 1;\n\tif (c == '_') return 1;\n\treturn 0;\n}\n\nint variable_char_first(uint8_t c) {\n\tif (c == '?') return 1;\n\tif (c == '$') return 1;\n\tif (c == '#') return 1;\n\treturn 0;\n}\n\nstruct alternative {\n\tconst char * command;\n\tconst char * replacement;\n\tconst char * description;\n};\n\n#define ALT_BIM    \"bim\", \"vi-like text editor\"\n#define ALT_FETCH  \"fetch\", \"URL downloader\"\n#define ALT_NETIF  \"ifconfig\", \"to see network configuration\"\n\nstatic struct alternative cmd_alternatives[] = {\n\t/* Propose bim as an alternative for common text editors */\n\t{\"vim\",   ALT_BIM},\n\t{\"vi\",    ALT_BIM},\n\t{\"emacs\", ALT_BIM},\n\t{\"nano\",  ALT_BIM},\n\n\t/* Propose fetch for some common URL-getting tools */\n\t{\"curl\",  ALT_FETCH},\n\t{\"wget\",  ALT_FETCH},\n\n\t/* We don't have ip or ifconfig commands, suggest cat /proc/netif */\n\t{\"ipconfig\", ALT_NETIF},\n\t{\"ip\", ALT_NETIF},\n\n\t/* Some random other stuff */\n\t{\"more\", \"bim -\", \"paging to a text editor\"},\n\t{\"less\", \"bim -\", \"paging to a text editor\"},\n\n\t{NULL, NULL, NULL},\n};\n\nvoid run_cmd(char ** args) {\n\tint i = execvp(*args, args);\n\tshell_command_t func = shell_find(*args);\n\tif (func) {\n\t\tint argc = 0;\n\t\twhile (args[argc]) {\n\t\t\targc++;\n\t\t}\n\t\ti = func(argc, args);\n\t} else {\n\t\tif (i != 0) {\n\t\t\tif (errno == ENOENT) {\n\t\t\t\tfprintf(stderr, \"%s: Command not found\\n\", *args);\n\t\t\t\tfor (struct alternative * alt = cmd_alternatives; alt->command; alt++) {\n\t\t\t\t\tif (!strcmp(*args, alt->command)) {\n\t\t\t\t\t\tfprintf(stderr, \"Consider this alternative:\\n\\n\\t%s -- \\033[3m%s\\033[0m\\n\\n\",\n\t\t\t\t\t\t\talt->replacement,\n\t\t\t\t\t\t\talt->description);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (errno == ELOOP) {\n\t\t\t\tfprintf(stderr, \"esh: Bad interpreter (maximum recursion depth reached)\\n\");\n\t\t\t} else if (errno == ENOEXEC) {\n\t\t\t\tfprintf(stderr, \"esh: Bad interpreter\\n\");\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"esh: Invalid executable\\n\");\n\t\t\t}\n\t\t\ti = 127;\n\t\t}\n\t}\n\texit(i);\n}\n\nint is_number(const char * c) {\n\twhile (*c) {\n\t\tif (!isdigit(*c)) return 0;\n\t\tc++;\n\t}\n\treturn 1;\n}\n\n/**\n * Prints \"Segmentation fault\", etc.\n */\nstatic void handle_status(int ret_code) {\n\tif (WIFSIGNALED(ret_code)) {\n\t\tint sig = WTERMSIG(ret_code);\n\t\tif (sig == SIGINT || sig == SIGPIPE) {\n\t\t\tfprintf(stderr, \"\\n\");\n\t\t\treturn;\n\t\t}\n\t\tchar * str = strsignal(sig);\n\t\tif (shell_interactive == 1) {\n\t\t\tfprintf(stderr, \"%s\\n\", str);\n\t\t} else if (shell_interactive == 2) {\n\t\t\tfprintf(stderr, \"%s: line %d: %s\\n\", current_file, current_line, str);\n\t\t}\n\t}\n}\n\nstatic void describe_job(pid_t pid);\n\nint wait_for_child(int pgid, char * name, int retpid) {\n\tint waitee = (shell_interactive == 1 && !is_subshell) ? -pgid : pgid;\n\tint outpid;\n\tint ret_code = 0;\n\tint ret_code_real = 0;\n\tint e;\n\tint _stopped = 0;\n\n\tdo {\n\t\toutpid = waitpid(waitee, &ret_code, WSTOPPED);\n\t\te = errno;\n\t\tif (outpid == retpid) {\n\t\t\tret_code_real = ret_code;\n\t\t}\n\t\tif (outpid != -1 && WIFSTOPPED(ret_code)) {\n\t\t\tsuspended_pgid = pgid;\n\t\t\tif (name) {\n\t\t\t\tvoid * old = hashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(name));\n\t\t\t\tif (old) free(old);\n\t\t\t}\n\t\t\tvoid * old = hashmap_set(desc_hash, (void*)(intptr_t)pgid, strdup(WSTOPSIG(ret_code) ? strsignal(WSTOPSIG(ret_code)) : \"Stopped\"));\n\t\t\tif (old) free(old);\n\t\t\t_stopped = 1;\n\t\t\tif (!shell_interactive) continue;\n\t\t\tbreak;\n\t\t} else if (outpid != -1) {\n\t\t\tsuspended_pgid = 0;\n\t\t\tif (hashmap_has(job_hash, (void*)(intptr_t)pgid)) {\n\t\t\t\thashmap_remove(job_hash, (void*)(intptr_t)pgid);\n\t\t\t\thashmap_remove(desc_hash, (void*)(intptr_t)pgid);\n\t\t\t}\n\t\t}\n\t} while (outpid != -1 || (outpid == -1 && e != ECHILD));\n\treset_pgrp();\n\thandle_status(ret_code_real);\n\tif (_stopped && shell_interactive) {\n\t\tfprintf(stderr, \"\\n\");\n\t\tdescribe_job(pgid);\n\t}\n\treturn WEXITSTATUS(ret_code_real);\n}\n\nint shell_exec(char * buffer, size_t size, FILE * file, char ** out_buffer) {\n\n\t*out_buffer = NULL;\n\n\t/* Read previous history entries */\n\tif (buffer[0] == '!') {\n\t\tint x = atoi((char *)((uintptr_t)buffer + 1));\n\t\tif (x > 0 && x <= rline_history_count) {\n\t\t\tbuffer = rline_history_get(x - 1);\n\t\t} else {\n\t\t\tfprintf(stderr, \"esh: !%d: event not found\\n\", x);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tchar * argv[1024];\n\tint tokenid = 0;\n\n\tchar quoted = 0;\n\tchar backtick = 0;\n\tchar buffer_[512] = {0};\n\tint collected = 0;\n\tint force_collected = 0;\n\n\tlist_t * args = list_create();\n\tint have_star = 0;\n\n\twhile (1) {\n\n\t\tchar * p = buffer;\n\n\t\twhile (*p) {\n\t\t\tswitch (*p) {\n\t\t\t\tcase '$':\n\t\t\t\t\tif (quoted == '\\'') {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp++;\n\t\t\t\t\t\tchar var[100];\n\t\t\t\t\t\tint  coll = 0;\n\t\t\t\t\t\tif (*p == '{') {\n\t\t\t\t\t\t\tp++;\n\t\t\t\t\t\t\twhile (*p != '}' && *p != '\\0' && (coll < 100)) {\n\t\t\t\t\t\t\t\tvar[coll] = *p;\n\t\t\t\t\t\t\t\tcoll++;\n\t\t\t\t\t\t\t\tvar[coll] = '\\0';\n\t\t\t\t\t\t\t\tp++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (*p == '}') {\n\t\t\t\t\t\t\t\tp++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile (*p != '\\0' && (variable_char(*p) || (coll == 0 && variable_char_first(*p)))  && (coll < 100)) {\n\t\t\t\t\t\t\t\tvar[coll] = *p;\n\t\t\t\t\t\t\t\tcoll++;\n\t\t\t\t\t\t\t\tvar[coll] = '\\0';\n\t\t\t\t\t\t\t\tif (coll == 1 && (isdigit(*p) || *p == '?' || *p == '$' || *p == '#')) {\n\t\t\t\t\t\t\t\t\tp++;\n\t\t\t\t\t\t\t\t\tbreak; /* Don't let these keep going */\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tp++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t/* Special cases */\n\t\t\t\t\t\tchar *c = NULL;\n\t\t\t\t\t\tchar tmp[128];\n\t\t\t\t\t\tif (!strcmp(var, \"?\")) {\n\t\t\t\t\t\t\tsprintf(tmp,\"%d\",last_ret);\n\t\t\t\t\t\t\tc = tmp;\n\t\t\t\t\t\t} else if (!strcmp(var, \"$\")) {\n\t\t\t\t\t\t\tsprintf(tmp,\"%d\",getpid());\n\t\t\t\t\t\t\tc = tmp;\n\t\t\t\t\t\t} else if (!strcmp(var, \"#\")) {\n\t\t\t\t\t\t\tsprintf(tmp,\"%d\",shell_argc ? shell_argc-1 : 0);\n\t\t\t\t\t\t\tc = tmp;\n\t\t\t\t\t\t} else if (is_number(var)) {\n\t\t\t\t\t\t\tint a = atoi(var);\n\t\t\t\t\t\t\tif (a >= 0 && a < shell_argc) {\n\t\t\t\t\t\t\t\tc = shell_argv[a];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (!strcmp(var, \"RANDOM\")) {\n\t\t\t\t\t\t\tsprintf(tmp,\"%d\",rand()%32768); /* sure, modulo is bad for range restriction, shut up */\n\t\t\t\t\t\t\tc = tmp;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tc = getenv(var);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (c) {\n\t\t\t\t\t\t\tbacktick = 0;\n\t\t\t\t\t\t\tfor (int i = 0; i < (int)strlen(c); ++i) {\n\t\t\t\t\t\t\t\tif (c[i] == ' ' && !quoted) {\n\t\t\t\t\t\t\t\t\t/* If we are not quoted and we reach a space, it signals a new argument */\n\t\t\t\t\t\t\t\t\tif (collected || force_collected) {\n\t\t\t\t\t\t\t\t\t\tbuffer_[collected] = '\\0';\n\t\t\t\t\t\t\t\t\t\tadd_argument(args, buffer_);\n\t\t\t\t\t\t\t\t\t\tbuffer_[0] = '\\0';\n\t\t\t\t\t\t\t\t\t\thave_star = 0;\n\t\t\t\t\t\t\t\t\t\tcollected = 0;\n\t\t\t\t\t\t\t\t\t\tforce_collected = 0;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tbuffer_[collected] = c[i];\n\t\t\t\t\t\t\t\t\tcollected++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbuffer_[collected] = '\\0';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\tcase '~':\n\t\t\t\t\tif (quoted || collected || backtick) {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (p[1] == 0 || p[1] == '/' || p[1] == '\\n' || p[1] == ' ' || p[1] == ';') {\n\t\t\t\t\t\t\tchar * c = getenv(\"HOME\");\n\t\t\t\t\t\t\tif (!c) {\n\t\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (int i = 0; i < (int)strlen(c); ++i) {\n\t\t\t\t\t\t\t\tbuffer_[collected] = c[i];\n\t\t\t\t\t\t\t\tcollected++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbuffer_[collected] = '\\0';\n\t\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase '\\\"':\n\t\t\t\t\tforce_collected = 1;\n\t\t\t\t\tif (quoted == '\\\"') {\n\t\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tquoted = 0;\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t} else if (!quoted) {\n\t\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tquoted = *p;\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '\\'':\n\t\t\t\t\tforce_collected = 1;\n\t\t\t\t\tif (quoted == '\\'') {\n\t\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tquoted = 0;\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t} else if (!quoted) {\n\t\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tquoted = *p;\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '*':\n\t\t\t\t\tif (quoted) {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t}\n\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t}\n\t\t\t\t\tif (have_star) {\n\t\t\t\t\t\tgoto _just_add; /* TODO multiple globs */\n\t\t\t\t\t}\n\t\t\t\t\thave_star = 1;\n\t\t\t\t\tcollected += sprintf(&buffer_[collected], STAR_TOKEN);\n\t\t\t\t\tgoto _next;\n\t\t\t\tcase '\\\\':\n\t\t\t\t\tif (quoted == '\\'') {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t}\n\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t}\n\t\t\t\t\tbacktick = 1;\n\t\t\t\t\tgoto _next;\n\t\t\t\tcase ' ':\n\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\tgoto _just_add;\n\t\t\t\t\t}\n\t\t\t\t\tif (!quoted) {\n\t\t\t\t\t\tgoto _new_arg;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '\\n':\n\t\t\t\t\tif (!quoted) {\n\t\t\t\t\t\tgoto _done;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '|':\n\t\t\t\t\tif (!quoted && !backtick) {\n\t\t\t\t\t\tif (collected || force_collected) {\n\t\t\t\t\t\t\tadd_argument(args, buffer_);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tforce_collected = 0;\n\t\t\t\t\t\tcollected = sprintf(buffer_, \"%s\", PIPE_TOKEN);\n\t\t\t\t\t\tgoto _new_arg;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '>':\n\t\t\t\t\tif (!quoted && !backtick) {\n\t\t\t\t\t\tif (collected || force_collected) {\n\t\t\t\t\t\t\tif (!strcmp(buffer_,\"2\")) {\n\t\t\t\t\t\t\t\t/* Special case */\n\t\t\t\t\t\t\t\tforce_collected = 0;\n\t\t\t\t\t\t\t\tcollected = sprintf(buffer_,\"%s\", WRITE_ERR_TOKEN);\n\t\t\t\t\t\t\t\tgoto _new_arg;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tadd_argument(args, buffer_);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tforce_collected = 0;\n\t\t\t\t\t\tcollected = sprintf(buffer_, \"%s\", WRITE_TOKEN);\n\t\t\t\t\t\tgoto _new_arg;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase ';':\n\t\t\t\t\tif (!quoted && !backtick) {\n\t\t\t\t\t\t*out_buffer = ++p;\n\t\t\t\t\t\tgoto _done;\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tcase '#':\n\t\t\t\t\tif (!quoted && !backtick) {\n\t\t\t\t\t\tgoto _done; /* Support comments; must not be part of an existing arg */\n\t\t\t\t\t}\n\t\t\t\t\tgoto _just_add;\n\t\t\t\tdefault:\n\t\t\t\t\tif (backtick) {\n\t\t\t\t\t\tbuffer_[collected] = '\\\\';\n\t\t\t\t\t\tcollected++;\n\t\t\t\t\t\tbuffer_[collected] = '\\0';\n\t\t\t\t\t}\n_just_add:\n\t\t\t\t\tbacktick = 0;\n\t\t\t\t\tbuffer_[collected] = *p;\n\t\t\t\t\tcollected++;\n\t\t\t\t\tbuffer_[collected] = '\\0';\n\t\t\t\t\tgoto _next;\n\t\t\t}\n\n_new_arg:\n\t\t\tbacktick = 0;\n\t\t\tif (collected || force_collected) {\n\t\t\t\tadd_argument(args, buffer_);\n\t\t\t\tbuffer_[0] = '\\0';\n\t\t\t\thave_star = 0;\n\t\t\t\tcollected = 0;\n\t\t\t\tforce_collected = 0;\n\t\t\t}\n\n_next:\n\t\t\tp++;\n\t\t}\n\n_done:\n\n\t\tif (quoted || backtick) {\n\t\t\tbacktick = 0;\n\t\t\tif (shell_interactive == 1) {\n\t\t\t\tbreak_while = 0;\n\t\t\t\tread_entry_continued(buffer);\n\t\t\t\tif (break_while) {\n\t\t\t\t\tbreak_while = 0;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\trline_history_append_line(buffer);\n\t\t\t\tcontinue;\n\t\t\t} else if (shell_interactive == 2) {\n\t\t\t\tfgets(buffer, size, file);\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\tfprintf(stderr, \"Syntax error: Unterminated quoted string.\\n\");\n\t\t\t\treturn 127;\n\t\t\t}\n\t\t}\n\n\t\tif (collected || force_collected) {\n\t\t\tadd_argument(args, buffer_);\n\t\t\tbreak;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tint cmdi = 0;\n\tchar ** arg_starts[100] = { &argv[0], NULL };\n\tchar * output_files[100] = { NULL };\n\tint file_args[100] = {0};\n\tint argcs[100] = {0};\n\tchar * err_files[100] = { NULL };\n\tint err_args[100] = {0};\n\tint next_is_file = 0;\n\tint next_is_err = 0;\n\n\tlist_t * extra_env = list_create();\n\n\tlist_t * glob_args = list_create();\n\n\tint i = 0;\n\tforeach(node, args) {\n\t\tchar * c = node->value;\n\n\t\tif (next_is_err) {\n\t\t\tif (next_is_err == 1 && !strcmp(c, WRITE_TOKEN)) {\n\t\t\t\tnext_is_err = 2;\n\t\t\t\terr_args[cmdi] = O_WRONLY | O_CREAT | O_APPEND;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\terr_files[cmdi] = c;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(c, WRITE_ERR_TOKEN)) {\n\t\t\tnext_is_err = 1;\n\t\t\terr_args[cmdi] = O_WRONLY | O_CREAT | O_TRUNC;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (next_is_file) {\n\t\t\tif (next_is_file == 1 && !strcmp(c, WRITE_TOKEN)) {\n\t\t\t\tnext_is_file = 2;\n\t\t\t\tfile_args[cmdi] = O_WRONLY | O_CREAT | O_APPEND;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\toutput_files[cmdi] = c;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(c, WRITE_TOKEN)) {\n\t\t\tnext_is_file = 1;\n\t\t\tfile_args[cmdi] = O_WRONLY | O_CREAT | O_TRUNC;\n\t\t\tcontinue;\n\t\t}\n\n\n\t\tif (!strcmp(c, PIPE_TOKEN)) {\n\t\t\tif (arg_starts[cmdi] == &argv[i]) {\n\t\t\t\tfprintf(stderr, \"Syntax error: Unexpected pipe token\\n\");\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\targv[i] = 0;\n\t\t\ti++;\n\t\t\tcmdi++;\n\t\t\targ_starts[cmdi] = &argv[i];\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (i == 0 && strstr(c,\"=\")) {\n\t\t\tlist_insert(extra_env, c);\n\t\t\tcontinue;\n\t\t}\n\n\t\tchar * glob = strstr(c, STAR_TOKEN);\n\t\tif (glob) {\n\t\t\t/* Globbing */\n\t\t\tglob[0] = '\\0';\n\t\t\tglob[1] = '\\0';\n\n\t\t\tchar * before = c;\n\t\t\tchar * after = &glob[8];\n\t\t\tchar * dir = NULL;\n\n\t\t\tint has_before = !!strlen(before);\n\t\t\tint has_after = !!strlen(after);\n\n\t\t\tif (1) {\n\t\t\t\t/* read current directory, add all */\n\t\t\t\tDIR * dirp;\n\t\t\t\tchar * prepend = \"\";\n\t\t\t\tif (has_before) {\n\t\t\t\t\tdir = strrchr(before,'/');\n\n\t\t\t\t\tif (!dir) {\n\t\t\t\t\t\tdirp = opendir(\".\");\n\t\t\t\t\t} else if (dir == before) {\n\t\t\t\t\t\tdirp = opendir(\"/\");\n\t\t\t\t\t\tprepend = before;\n\t\t\t\t\t\tbefore++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t*dir = '\\0';\n\t\t\t\t\t\tdirp = opendir(before);\n\t\t\t\t\t\tprepend = before;\n\t\t\t\t\t\tbefore = dir+1;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tdirp = opendir(\".\");\n\t\t\t\t}\n\n\t\t\t\tint before_i = i;\n\t\t\t\tif (dirp) {\n\t\t\t\t\tstruct dirent * ent = readdir(dirp);\n\t\t\t\t\twhile (ent != NULL) {\n\t\t\t\t\t\tif (ent->d_name[0] != '.' || (dir ? (dir[1] == '.') : (before && before[0] == '.'))) {\n\t\t\t\t\t\t\tchar * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1));\n\t\t\t\t\t\t\tmemcpy(s, ent->d_name, strlen(ent->d_name) + 1);\n\n\t\t\t\t\t\t\tchar * t = s;\n\n\t\t\t\t\t\t\tif (has_before) {\n\t\t\t\t\t\t\t\tif (strstr(s,before) != s) {\n\t\t\t\t\t\t\t\t\tfree(s);\n\t\t\t\t\t\t\t\t\tgoto _nope;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tt = &s[strlen(before)];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (has_after) {\n\t\t\t\t\t\t\t\tif (strlen(t) >= strlen(after)) {\n\t\t\t\t\t\t\t\t\tif (!strcmp(after,&t[strlen(t)-strlen(after)])) {\n\t\t\t\t\t\t\t\t\t\tchar * out = malloc(strlen(s) + 2 + strlen(prepend));\n\t\t\t\t\t\t\t\t\t\tsprintf(out,\"%s%s%s\", prepend, !!*prepend ? \"/\" : \"\", s);\n\t\t\t\t\t\t\t\t\t\tlist_insert(glob_args, out);\n\t\t\t\t\t\t\t\t\t\targv[i] = out;\n\t\t\t\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t\t\t\t\targcs[cmdi]++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tchar * out = malloc(strlen(s) + 2 + strlen(prepend));\n\t\t\t\t\t\t\t\tsprintf(out,\"%s%s%s\", prepend, !!*prepend ? \"/\" : \"\", s);\n\t\t\t\t\t\t\t\tlist_insert(glob_args, out);\n\t\t\t\t\t\t\t\targv[i] = out;\n\t\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t\t\targcs[cmdi]++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfree(s);\n\t\t\t\t\t\t}\n_nope:\n\t\t\t\t\t\tent = readdir(dirp);\n\t\t\t\t\t}\n\t\t\t\t\tclosedir(dirp);\n\t\t\t\t}\n\n\t\t\t\tif (before_i == i) {\n\t\t\t\t\t/* no matches */\n\t\t\t\t\tglob[0] = '*';\n\t\t\t\t\tif (dir) {\n\t\t\t\t\t\t*dir = '/';\n\t\t\t\t\t}\n\t\t\t\t\tmemmove(&glob[1], after, strlen(after)+1);\n\t\t\t\t\targv[i] = c;\n\t\t\t\t\ti++;\n\t\t\t\t\targcs[cmdi]++;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\targv[i] = c;\n\t\t\ti++;\n\t\t\targcs[cmdi]++;\n\t\t}\n\t}\n\targv[i] = NULL;\n\n\t/* Ensure globs get freed */\n\tforeach(node, glob_args) {\n\t\tlist_insert(args, node->value);\n\t}\n\tlist_free(glob_args);\n\tfree(glob_args);\n\n\tif (i == 0) {\n\t\tadd_environment(extra_env);\n\t\tlist_free(extra_env);\n\t\tfree(extra_env);\n\t\tlist_destroy(args);\n\t\tfree(args);\n\t\treturn -1;\n\t}\n\n\tif (!*arg_starts[cmdi]) {\n\t\tfprintf(stderr, \"Syntax error: Unexpected end of input\\n\");\n\t\tlist_free(extra_env);\n\t\tfree(extra_env);\n\t\tlist_destroy(args);\n\t\tfree(args);\n\t\treturn 2;\n\t}\n\n\ttokenid = i;\n\n\tunsigned int child_pid;\n\tint last_child;\n\n\tint nowait = (!strcmp(argv[tokenid-1],\"&\"));\n\tif (nowait) {\n\t\targv[tokenid-1] = NULL;\n\t}\n\n\tint pgid = 0;\n\tif (cmdi > 0) {\n\t\tint last_output[2];\n\t\tpipe(last_output);\n\n\t\tchild_pid = fork();\n\t\tif (!child_pid) {\n\t\t\tset_pgid(0);\n\t\t\tif (!nowait) set_pgrp(getpid());\n\t\t\tis_subshell = 1;\n\t\t\tdup2(last_output[1], STDOUT_FILENO);\n\t\t\tclose(last_output[0]);\n\t\t\tadd_environment(extra_env);\n\t\t\trun_cmd(arg_starts[0]);\n\t\t}\n\n\t\tpgid = child_pid;\n\n\t\tfor (int j = 1; j < cmdi; ++j) {\n\t\t\tint tmp_out[2];\n\t\t\tpipe(tmp_out);\n\t\t\tif (!fork()) {\n\t\t\t\tis_subshell = 1;\n\t\t\t\tset_pgid(pgid);\n\t\t\t\tdup2(tmp_out[1], STDOUT_FILENO);\n\t\t\t\tdup2(last_output[0], STDIN_FILENO);\n\t\t\t\tclose(tmp_out[0]);\n\t\t\t\tclose(last_output[1]);\n\t\t\t\tadd_environment(extra_env);\n\t\t\t\trun_cmd(arg_starts[j]);\n\t\t\t}\n\t\t\tclose(last_output[0]);\n\t\t\tclose(last_output[1]);\n\t\t\tlast_output[0] = tmp_out[0];\n\t\t\tlast_output[1] = tmp_out[1];\n\t\t}\n\n\t\tstruct semaphore s = create_semaphore();\n\t\tlast_child = fork();\n\t\tif (!last_child) {\n\t\t\tis_subshell = 1;\n\t\t\tset_pgid(pgid);\n\t\t\traise_semaphore(s);\n\t\t\tif (output_files[cmdi]) {\n\t\t\t\tint fd = open(output_files[cmdi], file_args[cmdi], 0666);\n\t\t\t\tif (fd < 0) {\n\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", output_files[cmdi], strerror(errno));\n\t\t\t\t\treturn -1;\n\t\t\t\t} else {\n\t\t\t\t\tdup2(fd, STDOUT_FILENO);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err_files[cmdi]) {\n\t\t\t\tint fd = open(err_files[cmdi], err_args[cmdi], 0666);\n\t\t\t\tif (fd < 0) {\n\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", err_files[cmdi], strerror(errno));\n\t\t\t\t\treturn -1;\n\t\t\t\t} else {\n\t\t\t\t\tdup2(fd, STDERR_FILENO);\n\t\t\t\t}\n\t\t\t}\n\t\t\tdup2(last_output[0], STDIN_FILENO);\n\t\t\tclose(last_output[1]);\n\t\t\tadd_environment(extra_env);\n\t\t\trun_cmd(arg_starts[cmdi]);\n\t\t}\n\t\tclose(last_output[0]);\n\t\tclose(last_output[1]);\n\t\twait_semaphore(s);\n\n\t\t/* Now execute the last piece and wait on all of them */\n\t} else {\n\t\tshell_command_t func = shell_find(*arg_starts[0]);\n\t\tif (func) {\n\t\t\tint old_out = -1;\n\t\t\tint old_err = -1;\n\t\t\tif (output_files[0]) {\n\t\t\t\told_out = dup(STDOUT_FILENO);\n\t\t\t\tint fd = open(output_files[cmdi], file_args[cmdi], 0666);\n\t\t\t\tif (fd < 0) {\n\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", output_files[cmdi], strerror(errno));\n\t\t\t\t\treturn -1;\n\t\t\t\t} else {\n\t\t\t\t\tdup2(fd, STDOUT_FILENO);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (err_files[0]) {\n\t\t\t\told_err = dup(STDERR_FILENO);\n\t\t\t\tint fd = open(err_files[cmdi], err_args[cmdi], 0666);\n\t\t\t\tif (fd < 0) {\n\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", err_files[cmdi], strerror(errno));\n\t\t\t\t\treturn -1;\n\t\t\t\t} else {\n\t\t\t\t\tdup2(fd, STDERR_FILENO);\n\t\t\t\t}\n\t\t\t}\n\t\t\tint result = func(argcs[0], arg_starts[0]);\n\t\t\tif (old_out != -1) dup2(old_out, STDOUT_FILENO);\n\t\t\tif (old_err != -1) dup2(old_err, STDERR_FILENO);\n\t\t\treturn result;\n\t\t} else {\n\t\t\tstruct semaphore s = create_semaphore();\n\t\t\tchild_pid = fork();\n\t\t\tif (!child_pid) {\n\t\t\t\tset_pgid(0);\n\t\t\t\tif (!nowait) set_pgrp(getpid());\n\t\t\t\traise_semaphore(s);\n\t\t\t\tis_subshell = 1;\n\t\t\t\tif (output_files[cmdi]) {\n\t\t\t\t\tint fd = open(output_files[cmdi], file_args[cmdi], 0666);\n\t\t\t\t\tif (fd < 0) {\n\t\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", output_files[cmdi], strerror(errno));\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdup2(fd, STDOUT_FILENO);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (err_files[cmdi]) {\n\t\t\t\t\tint fd = open(err_files[cmdi], err_args[cmdi], 0666);\n\t\t\t\t\tif (fd < 0) {\n\t\t\t\t\t\tfprintf(stderr, \"sh: %s: %s\\n\", err_files[cmdi], strerror(errno));\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdup2(fd, STDERR_FILENO);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tadd_environment(extra_env);\n\t\t\t\trun_cmd(arg_starts[0]);\n\t\t\t}\n\n\t\t\twait_semaphore(s);\n\n\t\t\tpgid = child_pid;\n\t\t\tlast_child = child_pid;\n\t\t}\n\t}\n\n\tif (nowait) {\n\t\tif (shell_interactive == 1) {\n\t\t\tfprintf(stderr, \"[%d] %s\\n\", pgid, arg_starts[0][0]);\n\t\t\thashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(arg_starts[0][0]));\n\t\t\thashmap_set(desc_hash, (void*)(intptr_t)pgid, strdup(\"Running\"));\n\t\t} else {\n\t\t\thashmap_set(job_hash, (void*)(intptr_t)last_child, strdup(arg_starts[0][0]));\n\t\t}\n\t\tlist_free(extra_env);\n\t\tfree(extra_env);\n\t\tlist_destroy(args);\n\t\tfree(args);\n\t\treturn 0;\n\t}\n\n\n\tint ret = wait_for_child(shell_interactive == 1 ? pgid : last_child, arg_starts[0][0], last_child);\n\n\tlist_free(extra_env);\n\tfree(extra_env);\n\tlist_destroy(args);\n\tfree(args);\n\treturn ret;\n}\n\nvoid add_path_contents(char * path) {\n\tDIR * dirp = opendir(path);\n\n\tif (!dirp) return; /* Failed to load directly */\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (ent->d_name[0] != '.') {\n\t\t\tchar * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1));\n\t\t\tmemcpy(s, ent->d_name, strlen(ent->d_name) + 1);\n\t\t\tshell_install_command(s, NULL, NULL);\n\t\t}\n\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n}\n\nstruct command {\n\tchar * string;\n\tvoid * func;\n\tchar * desc;\n};\n\nstatic int comp_shell_commands(const void *p1, const void *p2) {\n\treturn strcmp(((struct command *)p1)->string, ((struct command *)p2)->string);\n}\n\nvoid sort_commands() {\n\tstruct command commands[SHELL_COMMANDS];\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tcommands[i].string = shell_commands[i];\n\t\tcommands[i].func   = shell_pointers[i];\n\t\tcommands[i].desc   = shell_descript[i];\n\t}\n\tqsort(&commands, shell_commands_len, sizeof(struct command), comp_shell_commands);\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tshell_commands[i] = commands[i].string;\n\t\tshell_pointers[i] = commands[i].func;\n\t\tshell_descript[i] = commands[i].desc;\n\t}\n}\n\nvoid show_version(void) {\n\tprintf(\"esh 1.10.0\\n\");\n}\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"Esh: The Experimental Shell\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-lha] [path]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -c \\033[4mcmd\\033[0m \\033[3mparse and execute cmd\\033[0m\\n\"\n\t\t\t//-c cmd \\033[...\n\t\t\t\" -R     \\033[3mdisable experimental line editor\\033[0m\\n\"\n\t\t\t\" -v     \\033[3mshow version information\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nvoid add_path(void) {\n\n\tchar * envvar = getenv(\"PATH\");\n\n\tif (!envvar) {\n\t\tadd_path_contents(\"/bin\");\n\t\treturn;\n\t}\n\n\tchar * tmp = strdup(envvar);\n\n\tdo {\n\t\tchar * end = strstr(tmp,\":\");\n\t\tif (end) {\n\t\t\t*end = '\\0';\n\t\t\tend++;\n\t\t}\n\t\tadd_path_contents(tmp);\n\t\ttmp = end;\n\t} while (tmp);\n\n\tfree(tmp);\n}\n\nint run_script(FILE * f) {\n\tcurrent_line = 1;\n\twhile (!feof(f)) {\n\t\tchar buf[LINE_LEN] = {0};\n\t\tfgets(buf, LINE_LEN, f);\n\t\tint ret;\n\t\tchar * out = NULL;\n\t\tchar * b = buf;\n\t\tdo {\n\t\t\tret = shell_exec(b, LINE_LEN, f, &out);\n\t\t\tb = out;\n\t\t} while (b);\n\t\tcurrent_line++;\n\t\tif (ret >= 0) last_ret = ret;\n\t}\n\n\tfclose(f);\n\n\treturn last_ret;\n}\n\nvoid source_eshrc(void) {\n\tchar * home = getenv(\"HOME\");\n\n\tif (!home) return;\n\n\tchar tmp[512];\n\tsprintf(tmp, \"%s/.eshrc\", home);\n\n\tFILE * f = fopen(tmp, \"r\");\n\tif (!f) return;\n\n\tcurrent_file = tmp;\n\trun_script(f);\n}\n\nint main(int argc, char ** argv) {\n\n\tpid = getpid();\n\n\tsignal(SIGINT, sig_break_loop);\n\n\tsrand(getpid() + time(0));\n\n\tjob_hash = hashmap_create_int(10);\n\tdesc_hash = hashmap_create_int(10);\n\n\tgetuser();\n\tgethost();\n\n\tinstall_commands();\n\n\tint err = dup(STDERR_FILENO);\n\tshell_stderr = fdopen(err, \"w\");\n\n\tif (argc > 1) {\n\t\tint c;\n\t\twhile ((c = getopt(argc, argv, \"c:v?\")) != -1) {\n\t\t\tswitch (c) {\n\t\t\t\tcase 'c':\n\t\t\t\t\tshell_interactive = 0;\n\t\t\t\t\t{\n\t\t\t\t\t\tchar * out = NULL;\n\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\tlast_ret = shell_exec(optarg, strlen(optarg), NULL, &out);\n\t\t\t\t\t\t\toptarg = out;\n\t\t\t\t\t\t} while (optarg);\n\t\t\t\t\t}\n\t\t\t\t\treturn (last_ret == -1) ? 0 : last_ret;\n\t\t\t\tcase 'v':\n\t\t\t\t\tshow_version();\n\t\t\t\t\treturn 0;\n\t\t\t\tcase '?':\n\t\t\t\t\tshow_usage(argc, argv);\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\tshell_interactive = 2;\n\t\tFILE * f = fopen(argv[optind],\"r\");\n\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\n\t\tshell_argc = argc - 1;\n\t\tshell_argv = &argv[optind];\n\t\tcurrent_file = argv[optind];\n\n\t\treturn run_script(f);\n\t}\n\n\tshell_interactive = 1;\n\n\tmy_pgid = getpgid(0);\n\n\tsource_eshrc();\n\tadd_path();\n\tsort_commands();\n\n\twhile (1) {\n\t\tchar buffer[LINE_LEN] = {0};\n\n\t\tlist_t * keys = hashmap_keys(job_hash);\n\t\tforeach(node, keys) {\n\t\t\tint pid = (intptr_t)node->value;\n\t\t\tint status = 0;\n\t\t\tif (waitpid(-pid, &status, WNOHANG|WUNTRACED) > 0) {\n\t\t\t\tchar * desc;\n\t\t\t\tif (WIFSTOPPED(status)) {\n\t\t\t\t\tdesc = strdup(WSTOPSIG(status) ? strsignal(WSTOPSIG(status)) : \"Stopped\");\n\t\t\t\t} else {\n\t\t\t\t\tdesc = strdup(WTERMSIG(status) ? strsignal(WTERMSIG(status)) : \"Done\");\n\t\t\t\t}\n\t\t\t\tvoid * old = hashmap_set(desc_hash, (void*)(intptr_t)pid, desc);\n\t\t\t\tif (old) free(old);\n\t\t\t\tdescribe_job(pid);\n\t\t\t\tif (WIFEXITED(status)) {\n\t\t\t\t\tif (hashmap_has(job_hash, (void*)(intptr_t)pid)) {\n\t\t\t\t\t\thashmap_remove(job_hash, (void*)(intptr_t)pid);\n\t\t\t\t\t\thashmap_remove(desc_hash, (void*)(intptr_t)pid);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlist_free(keys);\n\t\tfree(keys);\n\n\t\tread_entry(buffer);\n\n\t\tchar * history = malloc(strlen(buffer) + 1);\n\t\tmemcpy(history, buffer, strlen(buffer) + 1);\n\n\t\tif (buffer[0] != ' ' && buffer[0] != '\\n' && buffer[0] != '!') {\n\t\t\trline_history_insert(history);\n\t\t} else {\n\t\t\tfree(history);\n\t\t}\n\n\t\tint ret;\n\t\tchar * out = NULL;\n\t\tchar * b = buffer;\n\t\tdo {\n\t\t\tret = shell_exec(b, LINE_LEN, stdin, &out);\n\t\t\tb = out;\n\t\t} while (b);\n\t\tif (ret >= 0) last_ret = ret;\n\t\trline_scroll = 0;\n\t}\n\n\treturn 0;\n}\n\n/*\n * cd [path]\n */\nuint32_t shell_cmd_cd(int argc, char * argv[]) {\n\tif (argc > 1) {\n\t\tif (chdir(argv[1])) {\n\t\t\tgoto cd_error;\n\t\t} /* else success */\n\t} else /* argc < 2 */ {\n\t\tchar * home = getenv(\"HOME\");\n\t\tif (home) {\n\t\t\tif (chdir(home)) {\n\t\t\t\tgoto cd_error;\n\t\t\t}\n\t\t} else {\n\t\t\tchar home_path[1200];\n\t\t\tsprintf(home_path, \"/home/%s\", username);\n\t\t\tif (chdir(home_path)) {\n\t\t\t\tgoto cd_error;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\ncd_error:\n\tfprintf(stderr, \"%s: could not cd '%s': %s\\n\", argv[0], argv[1], strerror(errno));\n\treturn 1;\n}\n\n/*\n * history\n */\nuint32_t shell_cmd_history(int argc, char * argv[]) {\n\tfor (int i = 0; i < rline_history_count; ++i) {\n\t\tprintf(\"%d\\t%s\\n\", i + 1, rline_history_get(i));\n\t}\n\treturn 0;\n}\n\nuint32_t shell_cmd_export(int argc, char * argv[]) {\n\tif (argc > 1) {\n\t\tif (putenv(argv[1]) < 0) {\n\t\t\tfprintf(stderr, \"%s: putenv: %s\\n\", argv[0], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nuint32_t shell_cmd_exit(int argc, char * argv[]) {\n\tif (argc > 1) {\n\t\texit(atoi(argv[1]));\n\t} else {\n\t\texit(0);\n\t}\n\treturn -1;\n}\n\nuint32_t shell_cmd_help(int argc, char * argv[]) {\n\tshow_version();\n\tprintf(\"\\nThis shell is not POSIX-compliant, please be careful.\\n\\n\");\n\tprintf(\"Built-in commands:\\n\");\n\n\t/* First, determine max width of command names */\n\tunsigned int max_len = 0;\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tif (!shell_descript[i]) continue;\n\t\tif (strlen(shell_commands[i]) > max_len) {\n\t\t\tmax_len = strlen(shell_commands[i]);\n\t\t}\n\t}\n\n\t/* Then print the commands their help text */\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tif (!shell_descript[i]) continue;\n\t\tprintf(\" %-*s - %s\\n\", max_len + 1, shell_commands[i], shell_descript[i]);\n\t}\n\n\treturn 0;\n}\n\nuint32_t shell_cmd_if(int argc, char * argv[]) {\n\tchar ** if_args = &argv[1];\n\tchar ** then_args = NULL;\n\tchar ** else_args = NULL;\n\n\tfor (int i = 2; i < argc; ++i) {\n\t\tif (!strcmp(argv[i],\"then\")) {\n\t\t\targv[i] = NULL;\n\t\t\tthen_args = &argv[i+1];\n\t\t} else if (!strcmp(argv[i],\"else\")) {\n\t\t\targv[i] = NULL;\n\t\t\telse_args = &argv[i+1];\n\t\t}\n\t}\n\n\tif (!then_args) {\n\t\tfprintf(stderr, \"%s: syntax error: expected 'then' clause\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tif (else_args && else_args < then_args) {\n\t\tfprintf(stderr, \"%s: syntax error: 'else' clause before 'then' clase\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tpid_t child_pid = fork();\n\tif (!child_pid) {\n\t\tset_pgid(0);\n\t\tset_pgrp(getpid());\n\t\tis_subshell = 1;\n\t\trun_cmd(if_args);\n\t}\n\n\tint pid, ret_code = 0;\n\tdo {\n\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\n\thandle_status(ret_code);\n\n\tif (WEXITSTATUS(ret_code) == 0) {\n\t\tshell_command_t func = shell_find(*then_args);\n\t\tif (func) {\n\t\t\tint argc = 0;\n\t\t\twhile (then_args[argc]) {\n\t\t\t\targc++;\n\t\t\t}\n\t\t\treturn func(argc, then_args);\n\t\t} else {\n\t\t\tchild_pid = fork();\n\t\t\tif (!child_pid) {\n\t\t\t\tset_pgid(0);\n\t\t\t\tset_pgrp(getpid());\n\t\t\t\tis_subshell = 1;\n\t\t\t\trun_cmd(then_args);\n\t\t\t}\n\t\t\tdo {\n\t\t\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t\t\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\t\t\treset_pgrp();\n\t\t\thandle_status(ret_code);\n\t\t\treturn WEXITSTATUS(ret_code);\n\t\t}\n\t} else if (else_args) {\n\t\tshell_command_t func = shell_find(*else_args);\n\t\tif (func) {\n\t\t\tint argc = 0;\n\t\t\twhile (else_args[argc]) {\n\t\t\t\targc++;\n\t\t\t}\n\t\t\treturn func(argc, else_args);\n\t\t} else {\n\t\t\tchild_pid = fork();\n\t\t\tif (!child_pid) {\n\t\t\t\tset_pgid(0);\n\t\t\t\tset_pgrp(getpid());\n\t\t\t\tis_subshell = 1;\n\t\t\t\trun_cmd(else_args);\n\t\t\t}\n\t\t\tdo {\n\t\t\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t\t\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\t\t\thandle_status(ret_code);\n\t\t\treturn WEXITSTATUS(ret_code);\n\t\t}\n\t}\n\n\treset_pgrp();\n\treturn 0;\n}\n\nuint32_t shell_cmd_while(int argc, char * argv[]) {\n\tchar ** while_args = &argv[1];\n\tchar ** do_args = NULL;\n\n\tfor (int i = 2; i < argc; ++i) {\n\t\tif (!strcmp(argv[i],\"do\")) {\n\t\t\targv[i] = NULL;\n\t\t\tdo_args = &argv[i+1];\n\t\t}\n\t}\n\n\tif (!do_args) {\n\t\tfprintf(stderr, \"%s: syntax error: expected 'do' clause\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tbreak_while = 0;\n\treset_pgrp();\n\n\tdo {\n\t\tpid_t child_pid = fork();\n\t\tif (!child_pid) {\n\t\t\tset_pgid(0);\n\t\t\tset_pgrp(getpid());\n\t\t\tis_subshell = 1;\n\t\t\trun_cmd(while_args);\n\t\t}\n\n\t\tint pid, ret_code = 0;\n\t\tdo {\n\t\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\n\t\thandle_status(ret_code);\n\t\tif (WEXITSTATUS(ret_code) == 0) {\n\t\t\tchild_pid = fork();\n\t\t\tif (!child_pid) {\n\t\t\t\tset_pgid(0);\n\t\t\t\tset_pgrp(getpid());\n\t\t\t\tis_subshell = 1;\n\t\t\t\trun_cmd(do_args);\n\t\t\t}\n\t\t\tdo {\n\t\t\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t\t\t\tif (pid != -1 && WIFSIGNALED(ret_code)) {\n\t\t\t\t\tint sig = WTERMSIG(ret_code);\n\t\t\t\t\tif (sig == SIGINT) return 127; /* break */\n\t\t\t\t}\n\t\t\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\t\t} else {\n\t\t\treset_pgrp();\n\t\t\treturn WEXITSTATUS(ret_code);\n\t\t}\n\t} while (!break_while);\n\n\treturn 127;\n}\n\nuint32_t shell_cmd_export_cmd(int argc, char * argv[]) {\n\n\tif (argc < 3) {\n\t\tfprintf(stderr, \"%s: syntax error: not enough arguments\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint pipe_fds[2];\n\tpipe(pipe_fds);\n\tpid_t child_pid = fork();\n\tif (!child_pid) {\n\t\tset_pgid(0);\n\t\tset_pgrp(getpid());\n\t\tis_subshell = 1;\n\t\tdup2(pipe_fds[1], STDOUT_FILENO);\n\t\tclose(pipe_fds[0]);\n\t\trun_cmd(&argv[2]);\n\t}\n\n\tclose(pipe_fds[1]);\n\n\tchar buf[1024];\n\tsize_t accum = 0;\n\n\tdo {\n\t\tint r = read(pipe_fds[0], buf+accum, 1023-accum);\n\n\t\tif (r == 0) break;\n\t\tif (r < 0) {\n\t\t\treturn -r;\n\t\t}\n\n\t\taccum += r;\n\t} while (accum < 1023);\n\n\twaitpid(child_pid, NULL, 0);\n\n\treset_pgrp();\n\n\tbuf[accum] = '\\0';\n\n\tif (accum && buf[accum-1] == '\\n') {\n\t\tbuf[accum-1] = '\\0';\n\t}\n\n\tsetenv(argv[1], buf, 1);\n\treturn 0;\n}\n\nuint32_t shell_cmd_empty(int argc, char * argv[]) {\n\n\tfor (int i = 1; i < argc; i++) {\n\t\tif (argv[i] && *argv[i]) return 1;\n\t}\n\n\treturn 0;\n}\n\nuint32_t shell_cmd_equals(int argc, char * argv[]) {\n\n\tif (argc < 3) return 1;\n\n\treturn !!strcmp(argv[1], argv[2]);\n}\n\nuint32_t shell_cmd_return(int argc, char * argv[]) {\n\tif (argc < 2) return 0;\n\n\treturn atoi(argv[1]);\n}\n\nuint32_t shell_cmd_source(int argc, char * argv[]) {\n\tif (argc < 2) return 0;\n\n\tFILE * f = fopen(argv[1], \"r\");\n\n\tif (!f) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[1], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tcurrent_file = argv[1];\n\treturn run_script(f);\n}\n\nuint32_t shell_cmd_exec(int argc, char * argv[]) {\n\tif (argc < 2) return 1;\n\treturn execvp(argv[1], &argv[1]);\n}\n\nuint32_t shell_cmd_not(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: expected command argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tint ret_code = 0;\n\tpid_t child_pid = fork();\n\tif (!child_pid) {\n\t\tset_pgid(0);\n\t\tset_pgrp(getpid());\n\t\tis_subshell = 1;\n\t\trun_cmd(&argv[1]);\n\t}\n\tdo {\n\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\treset_pgrp();\n\thandle_status(ret_code);\n\treturn !WEXITSTATUS(ret_code);\n}\n\nuint32_t shell_cmd_unset(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: expected command argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tif (unsetenv(argv[1]) < 0) {\n\t\tfprintf(stderr, \"%s: unsetenv: %s\\n\", argv[0], strerror(errno));\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nuint32_t shell_cmd_read(int argc, char * argv[]) {\n\tint raw = 0;\n\tint i = 1;\n\tchar * var = \"REPLY\";\n\tif (i < argc && !strcmp(argv[i], \"-r\")) {\n\t\traw = 1;\n\t\ti++;\n\t}\n\tif (i < argc) {\n\t\tvar = argv[i];\n\t}\n\n\tchar tmp[4096];\n\tfgets(tmp, 4096, stdin);\n\n\tif (*tmp && tmp[strlen(tmp)-1] == '\\n') {\n\t\ttmp[strlen(tmp)-1] = '\\0';\n\t}\n\n\tif (raw) {\n\t\tsetenv(var, tmp, 1);\n\t\treturn 0;\n\t}\n\n\tchar tmp2[4096] = {0};\n\tchar * out = tmp2;\n\tchar * in = tmp;\n\n\t/* TODO: This needs to actually read more if a \\ at the end of the line is found */\n\twhile (*in) {\n\t\tif (*in == '\\\\') {\n\t\t\tin++;\n\t\t\tif (*in == '\\n') {\n\t\t\t\tin++;\n\t\t\t}\n\t\t} else {\n\t\t\t*out = *in;\n\t\t\tout++;\n\t\t\tin++;\n\t\t}\n\t}\n\n\tsetenv(var, tmp2, 1);\n\treturn 0;\n}\n\nint get_available_job(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tif (!suspended_pgid) {\n\t\t\tlist_t * keys = hashmap_keys(job_hash);\n\t\t\tforeach(node, keys) {\n\t\t\t\tsuspended_pgid = (intptr_t)node->value;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlist_free(keys);\n\t\t\tfree(keys);\n\t\t\tif (!suspended_pgid) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\treturn suspended_pgid;\n\t} else {\n\t\treturn atoi(argv[1]);\n\t}\n}\n\nuint32_t shell_cmd_fg(int argc, char * argv[]) {\n\tint pid = get_available_job(argc,argv);\n\tif (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) {\n\t\tfprintf(stderr, \"no current job\\n\");\n\t\treturn 1;\n\t}\n\n\tset_pgrp(pid);\n\n\tif (kill(-pid, SIGCONT) < 0) {\n\t\treset_pgrp();\n\t\tfprintf(stderr, \"no current job / bad pid\\n\");\n\t\tif (hashmap_has(job_hash, (void*)(intptr_t)pid)) {\n\t\t\thashmap_remove(job_hash, (void*)(intptr_t)pid);\n\t\t\thashmap_remove(desc_hash, (void*)(intptr_t)pid);\n\t\t}\n\t\treturn 1;\n\t}\n\n\treturn wait_for_child(pid, NULL, pid);\n}\n\nstatic void describe_job(pid_t pid) {\n\tfprintf(stderr, \"[%d] %s\\t\\t%s\\n\", pid, (char*)hashmap_get(desc_hash, (void*)(intptr_t)pid), (char*)hashmap_get(job_hash, (void*)(intptr_t)pid));\n}\n\nuint32_t shell_cmd_bg(int argc, char * argv[]) {\n\tint pid = get_available_job(argc,argv);\n\tif (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) {\n\t\tfprintf(stderr, \"no current job\\n\");\n\t\treturn 1;\n\t}\n\n\tif (kill(-pid, SIGCONT) < 0) {\n\t\tfprintf(stderr, \"no current job / bad pid\\n\");\n\t\tif (hashmap_has(job_hash, (void*)(intptr_t)pid)) {\n\t\t\thashmap_remove(job_hash, (void*)(intptr_t)pid);\n\t\t\thashmap_remove(desc_hash, (void*)(intptr_t)pid);\n\t\t}\n\t\treturn 1;\n\t}\n\n\tdescribe_job(pid);\n\treturn 0;\n}\n\nuint32_t shell_cmd_jobs(int argc, char * argv[]) {\n\tlist_t * keys = hashmap_keys(job_hash);\n\tforeach(node, keys) {\n\t\tint pid = (intptr_t)node->value;\n\t\tdescribe_job(pid);\n\t}\n\tlist_free(keys);\n\tfree(keys);\n\treturn 0;\n}\n\nuint32_t shell_cmd_rehash(int argc, char * argv[]) {\n\t/* PATH commands are malloc'd */\n\tfor (int i = 0; i < shell_commands_len; ++i) {\n\t\tif (!shell_pointers[i]) {\n\t\t\tfree(shell_commands[i]);\n\t\t}\n\t}\n\t/* Clear array */\n\tshell_commands_len = 0;\n\t/* Reset capacity */\n\tSHELL_COMMANDS = 64;\n\t/* Free existing */\n\tfree(shell_commands);\n\tfree(shell_pointers);\n\tfree(shell_descript);\n\t/* Reallocate + install builtins */\n\tinstall_commands();\n\t/* Reload PATH */\n\tadd_path();\n\n\treturn 0;\n}\n\nuint32_t shell_cmd_time(int argc, char * argv[]) {\n\tint pid, ret_code = 0;\n\tstruct timeval start, end;\n\tgettimeofday(&start, NULL);\n\n\tstruct tms timeBefore;\n\ttimes(&timeBefore);\n\n\tif (argc > 1) {\n\t\tpid_t child_pid = fork();\n\t\tif (!child_pid) {\n\t\t\tset_pgid(0);\n\t\t\tset_pgrp(getpid());\n\t\t\tis_subshell = 1;\n\t\t\trun_cmd(&argv[1]);\n\t\t}\n\n\t\tdo {\n\t\t\tpid = waitpid(child_pid, &ret_code, 0);\n\t\t} while (pid != -1 || (pid == -1 && errno != ECHILD));\n\n\t\treset_pgrp();\n\t\thandle_status(ret_code);\n\t}\n\n\tgettimeofday(&end, NULL);\n\n\ttime_t sec_diff = end.tv_sec - start.tv_sec;\n\tsuseconds_t usec_diff = end.tv_usec - start.tv_usec;\n\tif (end.tv_usec < start.tv_usec) {\n\t\tsec_diff -= 1;\n\t\tusec_diff = (1000000 + end.tv_usec) - start.tv_usec;\n\t}\n\n\tint minutes = sec_diff / 60;\n\tsec_diff = sec_diff % 60;\n\n\tfprintf(shell_stderr, \"\\nreal\\t%dm%d.%.03ds\\n\", minutes, (int)sec_diff, (int)(usec_diff / 1000));\n\n\t/* User and system times from children */\n\tstruct tms timeBuf;\n\ttimes(&timeBuf);\n\n\tfprintf(shell_stderr, \"user\\t%dm%d.%.03ds\\n\",\n\t\t(int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (60 * CLOCKS_PER_SEC))),\n\t\t(int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (CLOCKS_PER_SEC)) % 60),\n\t\t(int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (CLOCKS_PER_SEC / 1000)) % 1000));\n\n\tfprintf(shell_stderr, \"sys\\t%dm%d.%.03ds\\n\",\n\t\t(int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (60 * CLOCKS_PER_SEC))),\n\t\t(int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (CLOCKS_PER_SEC)) % 60),\n\t\t(int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (CLOCKS_PER_SEC / 1000)) % 1000));\n\n\treturn WEXITSTATUS(ret_code);\n}\n\nvoid install_commands() {\n\tshell_commands = malloc(sizeof(char *) * SHELL_COMMANDS);\n\tshell_pointers = malloc(sizeof(shell_command_t) * SHELL_COMMANDS);\n\tshell_descript = malloc(sizeof(char *) * SHELL_COMMANDS);\n\n\tshell_install_command(\"cd\",      shell_cmd_cd, \"change directory\");\n\tshell_install_command(\"exit\",    shell_cmd_exit, \"exit the shell\");\n\tshell_install_command(\"export\",  shell_cmd_export, \"set environment variables: export VAR=value\");\n\tshell_install_command(\"help\",    shell_cmd_help, \"display this help text\");\n\tshell_install_command(\"history\", shell_cmd_history, \"list command history\");\n\tshell_install_command(\"if\",      shell_cmd_if, \"if ... then ... [else ...]\");\n\tshell_install_command(\"while\",   shell_cmd_while, \"while ... do ...\");\n\tshell_install_command(\"empty?\",  shell_cmd_empty, \"empty? args...\");\n\tshell_install_command(\"equals?\", shell_cmd_equals, \"equals? arg1 arg2\");\n\tshell_install_command(\"return\",  shell_cmd_return, \"return status code\");\n\tshell_install_command(\"export-cmd\",   shell_cmd_export_cmd, \"set variable to result of command: export-cmd VAR command...\");\n\tshell_install_command(\"source\",  shell_cmd_source, \"run a shell script in the context of this shell\");\n\tshell_install_command(\".\",       shell_cmd_source, \"alias for 'source'\");\n\tshell_install_command(\"exec\",    shell_cmd_exec, \"replace shell (or subshell) with command\");\n\tshell_install_command(\"not\",     shell_cmd_not, \"invert status of command\");\n\tshell_install_command(\"unset\",   shell_cmd_unset, \"unset variable\");\n\tshell_install_command(\"read\",    shell_cmd_read, \"read user input\");\n\tshell_install_command(\"fg\",      shell_cmd_fg, \"resume a suspended job\");\n\tshell_install_command(\"jobs\",    shell_cmd_jobs, \"list stopped jobs\");\n\tshell_install_command(\"bg\",      shell_cmd_bg, \"restart suspended job in the background\");\n\tshell_install_command(\"rehash\",  shell_cmd_rehash, \"reset shell command memory\");\n\tshell_install_command(\"time\",    shell_cmd_time, \"time a command\");\n}\n"
  },
  {
    "path": "apps/false.c",
    "content": "/**\n * @brief false - returns a failure status code.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\nint main() {\n\treturn 1;\n}\n"
  },
  {
    "path": "apps/fetch.c",
    "content": "/**\n * @file  apps/fetch.c\n * @brief Obtain files over HTTP.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2021 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <getopt.h>\n#include <time.h>\n#include <termios.h>\n#include <unistd.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n\n#include <toaru/hashmap.h>\n\n#define SIZE 512\n#define BOUNDARY \"------ToaruOSFetchUploadBoundary\"\n\nstruct http_req {\n\tchar domain[SIZE];\n\tchar path[SIZE];\n\tint port;\n\tint ssl;\n};\n\nstruct {\n\tint show_headers;\n\tconst char * output_file;\n\tconst char * cookie;\n\tFILE * out;\n\tint prompt_password;\n\tconst char * upload_file;\n\tchar * password;\n\tint show_progress;\n\tsize_t content_length;\n\tsize_t size;\n\tstruct timeval start;\n\tint calculate_output;\n\tint slow_upload;\n\tint machine_readable;\n} fetch_options = {0};\n\nint parse_url(char * d, struct http_req * r) {\n\tif (strstr(d, \"http://\") == d) {\n\t\td += strlen(\"http://\");\n\t\tr->port = 80;\n\t\tr->ssl = 0;\n\t} else if (strstr(d, \"https://\") == d) {\n\t\td += strlen(\"https://\");\n\t\tr->port = 443;\n\t\tr->ssl = 1;\n\t} else {\n\t\tfprintf(stderr, \"Unrecognized protocol: %s\\n\", d);\n\t\treturn 1;\n\t}\n\n\tchar * s = strstr(d, \"/\");\n\tif (!s) {\n\t\tstrcpy(r->domain, d);\n\t\tstrcpy(r->path, \"\");\n\t} else {\n\t\t*s = 0;\n\t\ts++;\n\t\tstrcpy(r->domain, d);\n\t\tstrcpy(r->path, s);\n\t}\n\tif (strstr(r->domain,\":\")) {\n\t\tchar * port = strstr(r->domain,\":\");\n\t\t*port = '\\0';\n\t\tport++;\n\t\tr->port = atoi(port);\n\t}\n\n\treturn 0;\n}\n\n#define BAR_WIDTH 20\n#define bar_perc \"||||||||||||||||||||\"\n#define bar_spac \"                    \"\nvoid print_progress(int force) {\n\tstatic uint64_t last_size = 0;\n\tif (!fetch_options.show_progress) return;\n\tif (!force && (last_size + 102400 > fetch_options.size)) return;\n\tlast_size = fetch_options.size;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\tfprintf(stderr,\"\\033[?25l\\033[G%6dkB\",(int)fetch_options.size/1024);\n\tif (fetch_options.content_length) {\n\t\tint percent = (fetch_options.size * BAR_WIDTH) / (fetch_options.content_length);\n\t\tfprintf(stderr,\" / %6dkB [%.*s%.*s]\", (int)fetch_options.content_length/1024, percent,bar_perc,BAR_WIDTH-percent,bar_spac);\n\t}\n\n\tdouble timediff = (double)(now.tv_sec - fetch_options.start.tv_sec) + (double)(now.tv_usec - fetch_options.start.tv_usec)/1000000.0;\n\tif (timediff > 0.0) {\n\t\tdouble rate = (double)(fetch_options.size) / timediff;\n\t\tdouble s = rate/(1024.0) * 8.0;\n\t\tif (s > 1024.0) {\n\t\t\tfprintf(stderr,\" %.2f Mbps\", s/1024.0);\n\t\t} else {\n\t\t\tfprintf(stderr,\" %.2f Kbps\", s);\n\t\t}\n\n\t\tif (!force && fetch_options.content_length) {\n\t\t\tif (rate > 0.0) {\n\t\t\t\tdouble remaining = (double)(fetch_options.content_length - fetch_options.size) / rate;\n\t\t\t\tfprintf(stderr,\" (%.2f sec remaining)\", remaining);\n\t\t\t}\n\t\t} else {\n\t\t\tfprintf(stderr,\" (%.2f sec elapsed)\", timediff);\n\t\t}\n\t}\n\tfprintf(stderr,\"\\033[K\\033[?25h\");\n\tfflush(stderr);\n}\n\nint usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"fetch - download files over HTTP\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-hOvmp?] [-c cookie] [-o file] [-u file] [-s speed] URL\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -h     \\033[3mshow headers\\033[0m\\n\"\n\t\t\t\" -O     \\033[3msave the file based on the filename in the URL\\033[0m\\n\"\n\t\t\t\" -v     \\033[3mshow progress\\033[0m\\n\"\n\t\t\t\" -m     \\033[3mmachine readable output\\033[0m\\n\"\n\t\t\t\" -p     \\033[3mprompt for password\\033[0m\\n\"\n\t\t\t\" -c ... \\033[3mset cookies\\033[0m\\n\"\n\t\t\t\" -o ... \\033[3msave to the specified file\\033[0m\\n\"\n\t\t\t\" -u ... \\033[3mupload the specified file\\033[0m\\n\"\n\t\t\t\" -s ... \\033[3mspecify the speed for uploading slowly\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nint collect_password(char * password) {\n\tfprintf(stdout, \"Password for upload: \");\n\tfflush(stdout);\n\n\t/* Disable echo */\n\tstruct termios old, new;\n\ttcgetattr(fileno(stdin), &old);\n\tnew = old;\n\tnew.c_lflag &= (~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n\n\tfgets(password, 1024, stdin);\n\tpassword[strlen(password)-1] = '\\0';\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n\tfprintf(stdout, \"\\n\");\n\n\treturn 0;\n}\n\n#define MAX_HTTP_LINE 1024\nvoid read_http_line(char * buf, FILE * f) {\n\tmemset(buf, 0x00, MAX_HTTP_LINE);\n\n\tfgets(buf, MAX_HTTP_LINE-1, f);\n\tchar * _r = strchr(buf, '\\r');\n\tif (_r) {\n\t\t*_r = '\\0';\n\t}\n\tif (!_r) {\n\t\t_r = strchr(buf, '\\n'); /* that's not right, but, whatever */\n\t\tif (_r) {\n\t\t\t*_r = '\\0';\n\t\t}\n\t}\n}\n\nvoid bad_response(void) {\n\tfprintf(stderr, \"Bad response.\\n\");\n\texit(1);\n}\n\nint http_fetch(FILE * f) {\n\thashmap_t * headers = hashmap_create(10);\n\n\t/* Parse response */\n\t{\n\t\tchar buf[MAX_HTTP_LINE];\n\t\tread_http_line(buf, f);\n\n\t\tchar * elements[3];\n\n\t\telements[0] = buf;\n\t\telements[1] = strchr(elements[0], ' ');\n\t\tif (!elements[1]) bad_response();\n\t\t*elements[1] = '\\0';\n\t\telements[1]++;\n\n\t\telements[2] = strchr(elements[1], ' ');\n\t\tif (!elements[2]) bad_response();\n\t\t*elements[2] = '\\0';\n\t\telements[2]++;\n\n\t\tif (strcmp(elements[1], \"200\")) {\n\t\t\tfprintf(stderr, \"Bad response code: %s\\n\", elements[1]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/* Parse headers */\n\twhile (1) {\n\t\tchar buf[MAX_HTTP_LINE];\n\t\tread_http_line(buf, f);\n\n\t\tif (!*buf) {\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Split */\n\t\tchar * name = buf;\n\t\tchar * value = strstr(buf, \": \");\n\t\tif (!value) bad_response();\n\t\t*value = '\\0';\n\t\tvalue += 2;\n\n\t\thashmap_set(headers, name, strdup(value));\n\t}\n\n\tif (fetch_options.show_headers) {\n\t\tlist_t * hash_keys = hashmap_keys(headers);\n\t\tforeach(_key, hash_keys) {\n\t\t\tchar * key = (char *)_key->value;\n\t\t\tfprintf(stderr, \"[%s] = %s\\n\", key, (char*)hashmap_get(headers, key));\n\t\t}\n\t\tlist_free(hash_keys);\n\t\tfree(hash_keys);\n\t}\n\n\t/* determine how many bytes we should read now */\n\tif (!hashmap_has(headers, \"Content-Length\")) {\n\t\tfprintf(stderr, \"Don't know how much to read.\\n\");\n\t\treturn 1;\n\t}\n\n\tint bytes_to_read = atoi(hashmap_get(headers, \"Content-Length\"));\n\tfetch_options.content_length = bytes_to_read;\n\n\tgettimeofday(&fetch_options.start, NULL);\n\n\twhile (bytes_to_read > 0) {\n\t\tchar buf[1024];\n\t\tsize_t r = fread(buf, 1, bytes_to_read < 1024 ? bytes_to_read : 1024, f);\n\t\tfwrite(buf, 1, r, fetch_options.out);\n\t\tfetch_options.size += r;\n\t\tprint_progress(0);\n\t\tif (fetch_options.machine_readable && fetch_options.content_length) {\n\t\t\tfprintf(stdout,\"%zu %zu\\n\",fetch_options.size,fetch_options.content_length);\n\t\t}\n\t\tbytes_to_read -= r;\n\t}\n\tprint_progress(1);\n\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\n\tint opt;\n\n\twhile ((opt = getopt(argc, argv, \"?c:hmo:Opu:vs:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'O':\n\t\t\t\tfetch_options.calculate_output = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tfetch_options.cookie = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tfetch_options.show_headers = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\tfetch_options.output_file = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'u':\n\t\t\t\tfetch_options.upload_file = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tfetch_options.show_progress = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'm':\n\t\t\t\tfetch_options.machine_readable = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'p':\n\t\t\t\tfetch_options.prompt_password = 1;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tfetch_options.slow_upload = atoi(optarg);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind >= argc) {\n\t\treturn usage(argv);\n\t}\n\n\tstruct http_req my_req;\n\tif (parse_url(argv[optind], &my_req)) {\n\t\treturn 1;\n\t}\n\n\tif (my_req.ssl) {\n\t\t/* TODO look for a viable backend. */\n\t\tfprintf(stderr, \"%s: no tls backend\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tif (fetch_options.calculate_output) {\n\t\tchar * tmp = strdup(my_req.path);\n\t\tchar * x = strrchr(tmp,'/');\n\t\tif (x) {\n\t\t\ttmp = x + 1;\n\t\t}\n\t\tfetch_options.output_file = tmp;\n\t}\n\n\tfetch_options.out = stdout;\n\tif (fetch_options.output_file) {\n\t\tfetch_options.out = fopen(fetch_options.output_file, \"w+\");\n\t\tif (!fetch_options.out) {\n\t\t\tperror(\"fopen\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tint sock = socket(AF_INET, SOCK_STREAM, 0);\n\tif (sock < 0) {\n\t\tperror(\"socket\");\n\t\treturn 1;\n\t}\n\n\tstruct hostent * remote = gethostbyname(my_req.domain);\n\n\tif (!remote) {\n\t\tperror(\"gethostbyname\");\n\t\treturn 1;\n\t}\n\n\tstruct sockaddr_in addr;\n\taddr.sin_family = AF_INET;\n\tmemcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length);\n\taddr.sin_port = htons(my_req.port);\n\n\tif (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {\n\t\tperror(\"connect\");\n\t\treturn 1;\n\t}\n\n\tFILE * f = fdopen(sock,\"w+\");\n\n\tif (!f) {\n\t\tfprintf(stderr, \"Nope.\\n\");\n\t\treturn 1;\n\t}\n\n\tif (fetch_options.prompt_password) {\n\t\tfetch_options.password = malloc(100);\n\t\tcollect_password(fetch_options.password);\n\t}\n\n\tif (fetch_options.upload_file) {\n\t\tFILE * in_file = fopen(fetch_options.upload_file, \"r\");\n\n\t\tsrand(time(NULL));\n\t\tint boundary_fuzz = rand();\n\t\tchar tmp[512];\n\n\t\tsize_t out_size = 0;\n\t\tif (fetch_options.password) {\n\t\t\tout_size += sprintf(tmp,\n\t\t\t\t\"--\" BOUNDARY \"%08x\\r\\n\"\n\t\t\t\t\"Content-Disposition: form-data; name=\\\"password\\\"\\r\\n\"\n\t\t\t\t\"\\r\\n\"\n\t\t\t\t\"%s\\r\\n\",boundary_fuzz, fetch_options.password);\n\t\t}\n\n\t\tout_size += strlen(\"--\" BOUNDARY \"00000000\\r\\n\"\n\t\t\t\t\"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"\\\"\\r\\n\"\n\t\t\t\t\"Content-Type: application/octet-stream\\r\\n\"\n\t\t\t\t\"\\r\\n\"\n\t\t\t\t/* Data goes here */\n\t\t\t\t\"\\r\\n\"\n\t\t\t\t\"--\" BOUNDARY \"00000000\" \"--\\r\\n\");\n\n\t\tout_size += strlen(fetch_options.upload_file);\n\n\t\tfseek(in_file, 0, SEEK_END);\n\t\tout_size += ftell(in_file);\n\t\tfseek(in_file, 0, SEEK_SET);\n\n\t\tfprintf(f,\n\t\t\t\"POST /%s HTTP/1.0\\r\\n\"\n\t\t\t\"User-Agent: curl/7.35.0\\r\\n\"\n\t\t\t\"Host: %s\\r\\n\"\n\t\t\t\"Accept: */*\\r\\n\"\n\t\t\t\"Content-Length: %d\\r\\n\"\n\t\t\t\"Content-Type: multipart/form-data; boundary=\" BOUNDARY \"%08x\\r\\n\"\n\t\t\t\"\\r\\n\", my_req.path, my_req.domain, (int)out_size, boundary_fuzz);\n\n\t\tfprintf(f,\"%s\",tmp);\n\t\tfprintf(f,\n\t\t\t\t\"--\" BOUNDARY \"%08x\\r\\n\"\n\t\t\t\t\"Content-Disposition: form-data; name=\\\"file\\\"; filename=\\\"%s\\\"\\r\\n\"\n\t\t\t\t\"Content-Type: application/octet-stream\\r\\n\"\n\t\t\t\t\"\\r\\n\", boundary_fuzz, fetch_options.upload_file);\n\n\t\twhile (!feof(in_file)) {\n\t\t\tchar buf[1024];\n\t\t\tsize_t r = fread(buf, 1, 1024, in_file);\n\t\t\tfwrite(buf, 1, r, f);\n\t\t\tif (fetch_options.slow_upload) {\n\t\t\t\tusleep(1000 * fetch_options.slow_upload); /* TODO fix terrible network stack; hopefully this ensures we send stuff right. */\n\t\t\t}\n\t\t}\n\n\t\tfclose(in_file);\n\n\t\tfprintf(f,\"\\r\\n--\" BOUNDARY \"%08x--\\r\\n\", boundary_fuzz);\n\t\tfflush(f);\n\n\t} else if (fetch_options.cookie) {\n\t\tfprintf(f,\n\t\t\t\"GET /%s HTTP/1.0\\r\\n\"\n\t\t\t\"User-Agent: curl/7.35.0\\r\\n\"\n\t\t\t\"Host: %s\\r\\n\"\n\t\t\t\"Accept: */*\\r\\n\"\n\t\t\t\"Cookie: %s\\r\\n\"\n\t\t\t\"\\r\\n\", my_req.path, my_req.domain, fetch_options.cookie);\n\n\t} else {\n\t\tfprintf(f,\n\t\t\t\"GET /%s HTTP/1.0\\r\\n\"\n\t\t\t\"User-Agent: curl/7.35.0\\r\\n\"\n\t\t\t\"Host: %s\\r\\n\"\n\t\t\t\"Accept: */*\\r\\n\"\n\t\t\t\"\\r\\n\", my_req.path, my_req.domain);\n\t}\n\n\thttp_fetch(f);\n\n\tfflush(fetch_options.out);\n\n\tif (fetch_options.show_progress) {\n\t\tfprintf(stderr,\"\\n\");\n\t}\n\n\tif (fetch_options.machine_readable) {\n\t\tfprintf(stdout,\"done\\n\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/file-browser.c",
    "content": "/**\n * @brief Graphical file browser\n * @file apps/file-browser.c\n *\n * Based on the original Python implementation and inspired by\n * Nautilus and Thunar. Also provides a \"wallpaper\" mode for\n * managing the desktop backgrond.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2022 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <time.h>\n#include <math.h>\n#include <libgen.h>\n#include <signal.h>\n#include <ctype.h>\n#include <errno.h>\n\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n#include <sys/fswait.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/icon_cache.h>\n#include <toaru/list.h>\n#include <toaru/text.h>\n#include <toaru/button.h>\n\n#define APPLICATION_TITLE \"File Browser\"\n#define SCROLL_AMOUNT 120\n#define WALLPAPER_PATH \"/usr/share/wallpaper.jpg\"\n\nstruct File {\n\tchar name[256];      /* Displayed name (icon label) */\n\tchar icon[256];      /* Icon identifier */\n\tchar link[256];      /* Link target for symlinks */\n\tchar launcher[256];  /* Launcher spec */\n\tchar filename[256];  /* Actual filename for launchers */\n\tchar filetype[256];  /* Textual description of file type */\n\tuint64_t size;       /* File size */\n\tint type;            /* File type: 0 = normal, 1 = directory, 2 = launcher */\n\tint selected;        /* Selection status */\n};\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * main_window;\nstatic gfx_context_t * ctx;\n\nstatic int application_running = 1; /* Big loop exit condition */\nstatic int show_hidden = 0; /* Whether or not show hidden files */\nstatic int scroll_offset = 0; /* How far the icon view should be scrolled */\nstatic int available_height = 0; /* How much space is available in the main window for the icon view */\nstatic int is_desktop_background = 0; /* If we're in desktop background mode */\nstatic int is_picker_dialog = 0; /* If we're an open-file dialog */\nstatic int menu_bar_height = MENU_BAR_HEIGHT; /* Height of the menu bar, if present - it's not in desktop mode */\nstatic int nav_bar_height = 36;\nstatic sprite_t * wallpaper_buffer = NULL; /* Prebaked wallpaper texture */\nstatic sprite_t * wallpaper_old = NULL; /* Previous wallpaper when transitioning */\nstatic uint64_t timer = 0; /* Timer for wallpaper transition fade */\nstatic char title[512]; /* Application title bar */\nstatic int FILE_HEIGHT = 80; /* Height of one row of icons */\nstatic int FILE_WIDTH = 100; /* Width of one column of icons */\nstatic int FILE_PTR_WIDTH = 1; /* How many icons wide the display should be */\nstatic sprite_t * contents_sprite = NULL; /* Icon view rendering context */\nstatic gfx_context_t * contents = NULL; /* Icon view rendering context */\nstatic char * current_directory = NULL; /* Current directory path */\nstatic int hilighted_offset = -1; /* Which file is hovered by the mouse */\nstatic struct File ** file_pointers = NULL; /* List of file pointers */\nstatic ssize_t file_pointers_len = 0; /* How many files are in the current list */\nstatic uint64_t last_click = 0; /* For double click */\nstatic int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */\nstatic struct TT_Font * tt_font_thin = NULL;\nstatic struct TT_Font * tt_font_bold = NULL;\n\nstatic struct MenuEntry * _menu_entry_show_icons = NULL;\nstatic struct MenuEntry * _menu_entry_show_tiles = NULL;\nstatic struct MenuEntry * _menu_entry_show_list  = NULL;\nstatic struct MenuEntry * _menu_entry_up = NULL;\nstatic struct MenuEntry * _menu_entry_up_ctx_a = NULL;\nstatic struct MenuEntry * _menu_entry_up_ctx_b = NULL;\n\n/**\n * Navigation input box\n */\nstatic char nav_bar[512] = {0};\nstatic int  nav_bar_cursor = 0;\nstatic int  nav_bar_cursor_x = 0;\nstatic int  nav_bar_focused = 0;\nstatic int  nav_bar_blink = 0;\nstatic struct timeval nav_bar_last_blinked;\n\n/* Status bar displayed at the bottom of the window */\nstatic char window_status[1024] = {0};\n\n/* Button row visibility statuses */\nstatic int _button_hilights[4] = {3,3,3,3};\nstatic int _button_disabled[4] = {1,1,0,0};\nstatic int _button_hover = -1;\n\n/* Menu bar entries */\nstatic struct menu_bar menu_bar = {0};\nstatic struct menu_bar_entries menu_entries[] = {\n\t{\"File\", \"file\"}, /* 0 */\n\t{\"Edit\", \"edit\"}, /* 1 */\n\t{\"View\", \"view\"}, /* 2 */\n\t{\"Go\", \"go\"},     /* 3 */\n\t{\"Help\", \"help\"}, /* 4 */\n\t{NULL, NULL},\n};\n\n/* Right click context menu */\nstatic struct MenuList * context_menu = NULL;\nstatic struct MenuList * directory_context_menu = NULL;\n\n/**\n * Accurate time comparison.\n *\n * These methods were taken from the compositor and\n * allow us to time double-clicks accurately.\n */\nstatic uint64_t precise_current_time(void) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttime_t sec_diff = t.tv_sec;\n\tsuseconds_t usec_diff = t.tv_usec;\n\n\treturn (uint64_t)((uint64_t)sec_diff * 1000LL + usec_diff / 1000);\n}\n\nstatic uint64_t precise_time_since(uint64_t start_time) {\n\n\tuint64_t now = precise_current_time();\n\tuint64_t diff = now - start_time; /* Milliseconds */\n\n\treturn diff;\n}\n\n/**\n * When in desktop mode, we fake decoration boundaries to\n * position the icon view correctly. When in normal mode,\n * we just passt through the actual bounds.\n */\nstatic int _decor_get_bounds(yutani_window_t * win, struct decor_bounds * bounds) {\n\tif (is_desktop_background) {\n\t\tmemset(bounds, 0, sizeof(struct decor_bounds));\n\t\tbounds->height = 54;\n\t\tbounds->width = 20;\n\t\tbounds->top_height = 54;\n\t\tbounds->left_width = 20;\n\t\treturn 0;\n\t}\n\treturn decor_get_bounds(win, bounds);\n}\n\n/**\n * This should probably be in a yutani core library...\n *\n * If a down and up event were close enough together to be considered a click.\n */\nstatic int _close_enough(struct yutani_msg_window_mouse_event * me) {\n\tif (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n/**\n * Select view mode.\n */\n#define VIEW_MODE_ICONS 0\n#define VIEW_MODE_TILES 1\n#define VIEW_MODE_LIST  2\nstatic int view_mode = VIEW_MODE_ICONS;\n\n/**\n * Clear out the space for an icon.\n * We clear to transparent so that the desktop background can be shown in desktop mode.\n */\nstatic void clear_offset(int offset) {\n\t/* From the flat array offset, figure out the x/y offset. */\n\tint offset_y = offset / FILE_PTR_WIDTH;\n\tint offset_x = offset % FILE_PTR_WIDTH;\n\tdraw_rectangle_solid(contents, offset_x * FILE_WIDTH, offset_y * FILE_HEIGHT, FILE_WIDTH, FILE_HEIGHT, rgba(0,0,0,0));\n}\n\nstatic int print_human_readable_size(char * _out, uint64_t s) {\n\tif (s >= 1LL << 30) {\n\t\tsize_t t = s / (1LL << 30);\n\t\treturn sprintf(_out, \"%d.%1d GiB\", (int)t, (int)((int)(s - t * (1LL << 30)) / ((1LL << 30) / 10)));\n\t} else if (s >= 1<<20) {\n\t\tsize_t t = s / (1 << 20);\n\t\treturn sprintf(_out, \"%d.%1d MiB\", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10));\n\t} else if (s >= 1<<10) {\n\t\tsize_t t = s / (1 << 10);\n\t\treturn sprintf(_out, \"%d.%1d KiB\", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10));\n\t} else {\n\t\treturn sprintf(_out, \"%d B\", (int)s);\n\t}\n}\n\n#define HILIGHT_BORDER_TOP rgb(54,128,205)\n#define HILIGHT_GRADIENT_TOP rgb(93,163,236)\n#define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220)\n#define HILIGHT_BORDER_BOTTOM rgb(47,106,167)\n\n/**\n * Draw an icon view entry\n */\nstatic void draw_file(struct File * f, int offset) {\n\n\t/* From the flat array offset, figure out the x/y offset. */\n\tint offset_y = offset / FILE_PTR_WIDTH;\n\tint offset_x = offset % FILE_PTR_WIDTH;\n\tint x = offset_x * FILE_WIDTH;\n\tint y = offset_y * FILE_HEIGHT;\n\n\t/* Load the icon sprite from the cache */\n\tif (view_mode == VIEW_MODE_ICONS) {\n\t\tsprite_t * icon = icon_get_48(f->icon);\n\n\t\t/* If the display name is too long to fit, cut it with an ellipsis. */\n\t\tint name_width;\n\t\tchar * name = tt_ellipsify(f->name, 13, tt_font_thin, FILE_WIDTH - 8, &name_width);\n\n\t\t/* Draw the icon */\n\t\tint center_x_icon = (FILE_WIDTH - icon->width) / 2;\n\t\tint center_x_text = (FILE_WIDTH - name_width) / 2;\n\t\tdraw_sprite(contents, icon, center_x_icon + x, y + 2);\n\n\t\tif (f->selected) {\n\t\t\ttt_set_size(tt_font_thin, 13);\n\t\t\t/* If this file is selected, paint the icon blue... */\n\t\t\tif (main_window->focused) {\n\t\t\t\tdraw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.5, rgb(72,167,255));\n\t\t\t\tdraw_rounded_rectangle(contents, center_x_text + x - 2, y + 53, name_width + 6, 20, 3, rgb(72,167,255));\n\t\t\t\ttt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(255,255,255));\n\t\t\t} else {\n\t\t\t\tdraw_rounded_rectangle(contents, center_x_text + x - 2, y + 53, name_width + 6, 20, 3, rgb(190,190,190));\n\t\t\t\ttt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(20,20,20));\n\t\t\t}\n\t\t} else {\n\t\t\tif (is_desktop_background) {\n\t\t\t\t/* If this is the desktop view, white text with a drop shadow */\n\t\t\t\ttt_draw_string_shadow(contents, tt_font_thin, name, 13, center_x_text + x, y + 54, rgba(0,0,0,0), rgb(0,0,0), 4);\n\t\t\t\ttt_draw_string_shadow(contents, tt_font_thin, name, 13, center_x_text + x, y + 54, rgb(255,255,255), rgb(0,0,0), 4);\n\t\t\t} else {\n\t\t\t\t/* Otherwise, black text */\n\t\t\t\ttt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(0,0,0));\n\t\t\t}\n\t\t}\n\n\t\tif (offset == hilighted_offset) {\n\t\t\t/* The hovered icon should have some added brightness, so paint it white */\n\t\t\tdraw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.3, rgb(255,255,255));\n\t\t}\n\n\t\tif (f->link[0]) {\n\t\t\t/* For symlinks, draw an indicator */\n\t\t\tsprite_t * arrow = icon_get_16(\"forward\");\n\t\t\tdraw_sprite(contents, arrow, center_x_icon + 32 + x, y + 32);\n\t\t}\n\n\t\tfree(name);\n\t} else if (view_mode == VIEW_MODE_TILES) {\n\t\tsprite_t * icon = icon_get_48(f->icon);\n\n\t\tuint32_t text_color = rgb(0,0,0);\n\n\t\t/* If selected, draw background box */\n\t\tif (f->selected) {\n\t\t\tstruct gradient_definition edge = {FILE_HEIGHT - 4, y+2, HILIGHT_BORDER_TOP, HILIGHT_BORDER_BOTTOM};\n\t\t\tstruct gradient_definition body = {FILE_HEIGHT - 6, y+3, HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM};\n\t\t\tdraw_rounded_rectangle_pattern(contents, x + 2,y + 2, FILE_WIDTH-4, FILE_HEIGHT-4, 3, gfx_vertical_gradient_pattern, &edge);\n\t\t\tdraw_rounded_rectangle_pattern(contents, x + 3,y + 3, FILE_WIDTH-6, FILE_HEIGHT-6, 4, gfx_vertical_gradient_pattern, &body);\n\n\t\t\ttext_color = rgb(255,255,255);\n\t\t}\n\n\t\tdraw_sprite(contents, icon, x + 11, y + 11);\n\t\tif (offset == hilighted_offset) {\n\t\t\t/* The hovered icon should have some added brightness, so paint it white */\n\t\t\tdraw_sprite_alpha_paint(contents, icon, x + 11, y + 11, 0.3, rgb(255,255,255));\n\t\t}\n\n\t\tchar * name = tt_ellipsify(f->name, 13, tt_font_bold, FILE_WIDTH - 81, NULL);\n\t\tchar * type = tt_ellipsify(f->filetype, 13, tt_font_thin, FILE_WIDTH - 81, NULL);\n\n\t\tif (f->type == 0) {\n\t\t\ttt_set_size(tt_font_bold, 13);\n\t\t\ttt_draw_string(contents, tt_font_bold, x + 70, y + 8 + 13, name, text_color);\n\t\t\ttt_set_size(tt_font_thin, 13);\n\t\t\ttt_draw_string(contents, tt_font_thin, x + 70, y + 25 + 13, type, text_color);\n\n\t\t\tchar line_three[48] = {0};\n\t\t\tif (*f->link) {\n\t\t\t\tsprintf(line_three, \"Symbolic link\");\n\t\t\t} else {\n\t\t\t\tprint_human_readable_size(line_three, f->size);\n\t\t\t}\n\t\t\ttt_draw_string(contents, tt_font_thin, x + 70, y + 42 + 13, line_three, text_color);\n\t\t} else {\n\t\t\ttt_set_size(tt_font_bold, 13);\n\t\t\ttt_draw_string(contents, tt_font_bold, x + 70, y + 15 + 13, name, text_color);\n\t\t\ttt_set_size(tt_font_thin, 13);\n\t\t\ttt_draw_string(contents, tt_font_thin, x + 70, y + 32 + 13, type, text_color);\n\t\t}\n\n\t\tfree(name);\n\t\tfree(type);\n\t} else if (view_mode == VIEW_MODE_LIST) {\n\t\tsprite_t * icon = icon_get_16(f->icon);\n\t\tuint32_t text_color = rgb(0,0,0);\n\n\t\tif (f->selected) {\n\t\t\tstruct gradient_definition edge = {FILE_HEIGHT - 4, y+2, HILIGHT_BORDER_TOP, HILIGHT_BORDER_BOTTOM};\n\t\t\tstruct gradient_definition body = {FILE_HEIGHT - 6, y+3, HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM};\n\t\t\tdraw_rounded_rectangle_pattern(contents, x + 2,y + 2, FILE_WIDTH-4, FILE_HEIGHT-4, 3, gfx_vertical_gradient_pattern, &edge);\n\t\t\tdraw_rounded_rectangle_pattern(contents, x + 3,y + 3, FILE_WIDTH-6, FILE_HEIGHT-6, 4, gfx_vertical_gradient_pattern, &body);\n\n\t\t\ttext_color = rgb(255,255,255);\n\t\t} else if (offset == hilighted_offset) {\n\t\t\tdraw_rounded_rectangle(contents, x + 2, y + 2, FILE_WIDTH - 4, FILE_HEIGHT - 4, 3, rgb(180,180,180));\n\t\t\tdraw_rounded_rectangle(contents, x + 3, y + 3, FILE_WIDTH - 6, FILE_HEIGHT - 6, 4, rgb(255,255,255));\n\t\t}\n\n\t\tif (icon->width != 16 || icon->height != 16) {\n\t\t\tdraw_sprite_scaled(contents, icon, x + 4, y + 4, 16, 16);\n\t\t} else {\n\t\t\tdraw_sprite(contents, icon, x + 4, y + 4);\n\t\t}\n\n\t\tchar * name = tt_ellipsify(f->name, 13, tt_font_thin, FILE_WIDTH - 26, NULL);\n\n\t\ttt_set_size(tt_font_thin, 13);\n\t\ttt_draw_string(contents, tt_font_thin, x + 24, y + 15, name, text_color);\n\n\t\tfree(name);\n\n\t}\n}\n\n/**\n * Get file from array offset, with bounds check\n */\nstatic struct File * get_file_at_offset(int offset) {\n\tif (offset >= 0 && offset < file_pointers_len) {\n\t\treturn file_pointers[offset];\n\t}\n\treturn NULL;\n}\n\n/**\n * Redraw all icon view entries\n */\nstatic void redraw_files(void) {\n\t/* Fill to blank */\n\tdraw_fill(contents, rgba(0,0,0,0));\n\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tdraw_file(file_pointers[i], i);\n\t}\n}\n\n/**\n * Set the application title.\n */\nstatic void set_title(char * directory) {\n\t/* Do nothing in desktop mode to avoid advertisement. */\n\tif (is_desktop_background) return;\n\n\tif (is_picker_dialog) {\n\t\t/* TODO: Maybe make this an option... */\n\t\tsprintf(title, \"Open File\");\n\t} else if (directory) {\n\t\tsprintf(title, \"%s - \" APPLICATION_TITLE, directory);\n\t} else {\n\t\t/* Otherwise, just \"File Browser\" */\n\t\tsprintf(title, APPLICATION_TITLE);\n\t}\n\n\t/* Advertise to the panel */\n\tyutani_window_advertise_icon(yctx, main_window, title, \"folder\");\n}\n\n/**\n * Check if a file name ends with an extension.\n *\n * Can also be used for exact matches.\n */\nstatic int has_extension(struct File * f, char * extension) {\n\tint i = strlen(f->name);\n\tint j = strlen(extension);\n\n\tdo {\n\t\tif (f->name[i] != (extension)[j]) break;\n\t\tif (j == 0) return 1;\n\t\tif (i == 0) break;\n\t\ti--;\n\t\tj--;\n\t} while (1);\n\treturn 0;\n}\n\n/**\n * Forward/backward history; we're always in the middle of these.\n * When we navigate somewhere new, clear the forward history, but\n * keep the back history and append the previous location.\n */\nstatic list_t * history_back;\nstatic list_t * history_forward;\n\n/**\n * Update the status bar text at the bottom of the window\n * based on the selected items in the file view.\n */\nstatic void update_status(void) {\n\tuint64_t total_size = 0;\n\tuint64_t selected_size = 0;\n\tint selected_count = 0;\n\tstruct File * selected = NULL;\n\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\ttotal_size += file_pointers[i]->size;\n\t\tif (file_pointers[i]->selected) {\n\t\t\tselected_count += 1;\n\t\t\tselected_size += file_pointers[i]->size;\n\t\t\tselected = file_pointers[i];\n\t\t}\n\t}\n\n\tchar tmp_size[50];\n\tif (selected_count == 0) {\n\t\tprint_human_readable_size(tmp_size, total_size);\n\t\tsprintf(window_status, \"%zd item%s (%s)\", file_pointers_len, file_pointers_len == 1 ? \"\" : \"s\", tmp_size);\n\t} else if (selected_count == 1) {\n\t\tprint_human_readable_size(tmp_size, selected->size);\n\t\tsprintf(window_status, \"\\\"%s\\\" (%s) %s\", selected->name, tmp_size, selected->filetype);\n\t} else {\n\t\tprint_human_readable_size(tmp_size, selected_size);\n\t\tsprintf(window_status, \"%d items selected (%s)\", selected_count, tmp_size);\n\t}\n}\n\n/**\n * Read the contents of a directory into the icon view.\n */\nstatic void load_directory(const char * path, int modifies_history) {\n\n\t/* Free the current icon view entries */\n\tDIR * dirp = opendir(path);\n\n\tif (!dirp) {\n\t\t/*\n\t\t * Display a warning dialog with the appropriate error message for\n\t\t * why this directory failed to load. XXX This uses `showdialog`\n\t\t * but it should use a dialog library like with the buttons.\n\t\t */\n\t\tif (!fork()) {\n\t\t\tchar tmp[512];\n\t\t\tsprintf(tmp, \"Could not open directory \\\"%s\\\": %s\", path, strerror(errno));\n\t\t\tchar * args[] = {\"showdialog\",\"File Browser\",\"/usr/share/icons/48/folder.png\",tmp,NULL};\n\t\t\texecvp(args[0],args);\n\t\t\texit(0);\n\t\t}\n\t\treturn;\n\t}\n\n\t/* Free the previously loaded directory */\n\tif (file_pointers) {\n\t\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\t\tfree(file_pointers[i]);\n\t\t}\n\t\tfree(file_pointers);\n\t}\n\n\tif (modifies_history) {\n\t\t/* Clear forward history */\n\t\tlist_destroy(history_forward);\n\t\tlist_free(history_forward);\n\t\tfree(history_forward);\n\t\thistory_forward = list_create();\n\t\t/* Append current pointer */\n\t\tif (current_directory) {\n\t\t\tlist_insert(history_back, strdup(current_directory));\n\t\t}\n\t}\n\n\tif (current_directory) {\n\t\tfree(current_directory);\n\t}\n\n\t/* Set button displays appropriately */\n\t_button_disabled[0] = !(history_back->length);    /* Back */\n\t_button_disabled[1] = !(history_forward->length); /* Forward */\n\t_button_disabled[2] = 0; /* Up */\n\t_button_disabled[3] = 0; /* Home */\n\n\tchar * home = getenv(\"HOME\");\n\tif (home && !strcmp(path, home)) {\n\t\t/* If the current directory is the user's homedir, present it that way in the title */\n\t\tset_title(\"Home\");\n\t\t/* Disable the 'go home' button */\n\t\t_button_disabled[3] = 1;\n\t} else if (!strcmp(path, \"/\")) {\n\t\tset_title(\"File System\");\n\t\t/* If this is the root of the file system, disable the up button */\n\t\t_button_disabled[2] = 1;\n\t} else {\n\t\t/* Otherwise use just the directory base name */\n\t\tchar * tmp = strdup(path);\n\t\tchar * base = basename(tmp);\n\t\tset_title(base);\n\t\tfree(tmp);\n\t}\n\n\textern void menu_update_enabled(struct MenuEntry*, int);\n\tif (_menu_entry_up) menu_update_enabled(_menu_entry_up, !_button_disabled[2]);\n\tif (_menu_entry_up_ctx_a) menu_update_enabled(_menu_entry_up_ctx_a, !_button_disabled[2]);\n\tif (_menu_entry_up_ctx_b) menu_update_enabled(_menu_entry_up_ctx_b, !_button_disabled[2]);\n\n\t/* If we ended up in a path with //two/initial/slashes, fix that. */\n\tif (path[0] == '/' && path[1] == '/') {\n\t\tcurrent_directory = strdup(path+1);\n\t} else {\n\t\tcurrent_directory = strdup(path);\n\t}\n\n\tstrcpy(nav_bar, current_directory);\n\n\t/* TODO: Show relative time informaton... */\n#if 0\n\t/* Get the current time */\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL); //time(NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tint this_year = timeinfo->tm_year;\n#endif\n\n\tlist_t * file_list = list_create();\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (ent->d_name[0] == '.' &&\n\t\t\t(ent->d_name[1] == '\\0' || \n\t\t\t (ent->d_name[1] == '.' &&\n\t\t\t  ent->d_name[2] == '\\0'))) {\n\t\t\t/* skip . and .. */\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\tif (show_hidden || (ent->d_name[0] != '.')) {\n\n\t\t\t/* Set display name from file name */\n\t\t\tstruct File * f = malloc(sizeof(struct File));\n\t\t\tsprintf(f->name, \"%s\", ent->d_name); /* snprintf? copy min()? */\n\n\t\t\tstruct stat statbuf;\n\t\t\tstruct stat statbufl;\n\n\t\t\t/* Calculate absolute path to file */\n\t\t\tchar tmp[strlen(path)+strlen(ent->d_name)+2];\n\t\t\tsprintf(tmp, \"%s/%s\", path, ent->d_name);\n\t\t\tlstat(tmp, &statbuf);\n\n\t\t\tf->size = statbuf.st_size;\n\n\t\t\t/* Read link target for symlinks */\n\t\t\tif (S_ISLNK(statbuf.st_mode)) {\n\t\t\t\tmemcpy(&statbufl, &statbuf, sizeof(struct stat));\n\t\t\t\tstat(tmp, &statbuf);\n\t\t\t\treadlink(tmp, f->link, 256);\n\t\t\t} else {\n\t\t\t\tf->link[0] = '\\0';\n\t\t\t}\n\n\t\t\tf->launcher[0] = '\\0';\n\t\t\tf->filetype[0] = '\\0';\n\t\t\tf->selected = 0;\n\n\t\t\tif (S_ISDIR(statbuf.st_mode)) {\n\t\t\t\t/* Is this /cdrom? */\n\t\t\t\tif (!strcmp(tmp,\"//cdrom\")) {\n\t\t\t\t\tsprintf(f->icon, \"cd\");\n\t\t\t\t} else {\n\t\t\t\t\tsprintf(f->icon, \"folder\");\n\t\t\t\t}\n\t\t\t\tsprintf(f->filetype, \"Directory\");\n\t\t\t\tf->type = 1;\n\t\t\t} else {\n\t\t\t\t/* Regular file */\n\n\t\t\t\t/* Default regular files to open in bim */\n\t\t\t\tsprintf(f->launcher, \"exec terminal bim\");\n\n\t\t\t\tif (is_desktop_background && has_extension(f, \".launcher\")) {\n\t\t\t\t\t/* In desktop mode, read launchers specially */\n\t\t\t\t\tFILE * file = fopen(tmp,\"r\");\n\t\t\t\t\tchar tbuf[1024];\n\t\t\t\t\twhile (!feof(file)) {\n\t\t\t\t\t\tfgets(tbuf, 1024, file);\n\t\t\t\t\t\tchar * nl = strchr(tbuf,'\\n');\n\t\t\t\t\t\tif (nl) *nl = '\\0';\n\t\t\t\t\t\tchar * eq = strchr(tbuf,'=');\n\t\t\t\t\t\tif (!eq) continue;\n\t\t\t\t\t\t*eq = '\\0'; eq++;\n\n\t\t\t\t\t\tif (!strcmp(tbuf, \"icon\")) {\n\t\t\t\t\t\t\tsprintf(f->icon, \"%s\", eq);\n\t\t\t\t\t\t} else if (!strcmp(tbuf, \"run\")) {\n\t\t\t\t\t\t\tsprintf(f->launcher, \"%s #\", eq);\n\t\t\t\t\t\t} else if (!strcmp(tbuf, \"title\")) {\n\t\t\t\t\t\t\tsprintf(f->name, eq);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tsprintf(f->filetype, \"Launcher\");\n\t\t\t\t\tsprintf(f->filename, \"%s\", ent->d_name);\n\t\t\t\t\tf->type = 2;\n\t\t\t\t} else {\n\t\t\t\t\t/* Handle various file types */\n\t\t\t\t\tif (has_extension(f, \".c\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"c\");\n\t\t\t\t\t\tsprintf(f->filetype, \"C Source\");\n\t\t\t\t\t} else if (has_extension(f, \".h\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"h\");\n\t\t\t\t\t\tsprintf(f->filetype, \"C Header\");\n\t\t\t\t\t} else if (has_extension(f, \".bmp\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"image\");\n\t\t\t\t\t\tsprintf(f->launcher, \"exec imgviewer\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Bitmap Image\");\n\t\t\t\t\t} else if (has_extension(f, \".tga\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"image\");\n\t\t\t\t\t\tsprintf(f->launcher, \"exec imgviewer\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Targa Image\");\n\t\t\t\t\t} else if (has_extension(f, \".jpg\") || has_extension(f,\".jpeg\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"image\");\n\t\t\t\t\t\tsprintf(f->launcher, \"exec imgviewer\");\n\t\t\t\t\t\tsprintf(f->filetype, \"JPEG Image\");\n\t\t\t\t\t} else if (has_extension(f, \".png\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"image\");\n\t\t\t\t\t\tsprintf(f->launcher, \"exec imgviewer\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Portable Network Graphics Image\");\n\t\t\t\t\t} else if (has_extension(f, \".sdf\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"font\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Legacy SDF Font\");\n\t\t\t\t\t} else if (has_extension(f, \".ttf\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"font\");\n\t\t\t\t\t\tsprintf(f->launcher,\"exec font-preview\");\n\t\t\t\t\t\tsprintf(f->filetype, \"TrueType Font\");\n\t\t\t\t\t} else if (has_extension(f, \".pdf\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"pdf\");\n\t\t\t\t\t\tsprintf(f->launcher,\"exec maybe-pdfviewer.krk\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Portable Document Format\");\n\t\t\t\t\t} else if (has_extension(f, \".tgz\") || has_extension(f, \".tar.gz\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"package_targz\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Compressed Archive File\");\n\t\t\t\t\t\t/* TODO: Archive viewer */\n\t\t\t\t\t} else if (has_extension(f, \".tar\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"package_tar\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Archive File\");\n\t\t\t\t\t} else if (has_extension(f, \".a\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"package_a\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Archive File\");\n\t\t\t\t\t} else if (has_extension(f, \".zip\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"package_zip\");\n\t\t\t\t\t\tsprintf(f->filetype, \"ZIP Archive File\");\n\t\t\t\t\t} else if (has_extension(f, \".sh\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"sh\");\n\t\t\t\t\t\tif (statbuf.st_mode & 0111) {\n\t\t\t\t\t\t\t/* Make executable */\n\t\t\t\t\t\t\tsprintf(f->launcher, \"SELF\");\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Executable Shell Script\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Shell Script\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (has_extension(f, \".krk\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"krk\");\n\t\t\t\t\t\tif (statbuf.st_mode & 0111) {\n\t\t\t\t\t\t\t/* Make executable */\n\t\t\t\t\t\t\tsprintf(f->launcher, \"SELF\");\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Executable Kuroko Script\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Kuroko Script\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (has_extension(f, \".py\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"py\");\n\t\t\t\t\t\tif (statbuf.st_mode & 0111) {\n\t\t\t\t\t\t\t/* Make executable */\n\t\t\t\t\t\t\tsprintf(f->launcher, \"SELF\");\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Executable Python Script\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsprintf(f->filetype, \"Python Script\");\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (has_extension(f, \".ko\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"so\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Kernel Module\");\n\t\t\t\t\t} else if (has_extension(f, \".o\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"so\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Object File\");\n\t\t\t\t\t} else if (has_extension(f, \".so\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"so\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Shared Object File\");\n\t\t\t\t\t} else if (has_extension(f, \".S\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Assembly Source\");\n\t\t\t\t\t} else if (has_extension(f, \".ld\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Linker Script\");\n\t\t\t\t\t} else if (has_extension(f, \".md\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Markdown Text Document\");\n\t\t\t\t\t} else if (has_extension(f, \".eshrc\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"sh\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Shell Configuration\");\n\t\t\t\t\t} else if (has_extension(f, \".bim3rc\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"krk\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Bim Configuration\");\n\t\t\t\t\t} else if (has_extension(f, \".biminfo\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Bim Status Cache\");\n\t\t\t\t\t} else if (has_extension(f, \".conf\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Configuration File\");\n\t\t\t\t\t} else if (has_extension(f, \".launcher\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Application Launcher\");\n\t\t\t\t\t} else if (has_extension(f, \".trt\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Toaru Rich Text Document\");\n\t\t\t\t\t\tsprintf(f->launcher, \"exec help-browser\");\n\t\t\t\t\t} else if (has_extension(f, \".json\")) {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"JavaScript Object Notation File\");\n\t\t\t\t\t} else if (statbuf.st_mode & 0111) {\n\t\t\t\t\t\t/* Executable files - use their name for their icon, and launch themselves. */\n\t\t\t\t\t\tsprintf(f->icon, \"%s\", f->name);\n\t\t\t\t\t\tsprintf(f->launcher, \"SELF\");\n\t\t\t\t\t\tsprintf(f->filetype, \"Executable\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsprintf(f->icon, \"file\");\n\t\t\t\t\t\tsprintf(f->filetype, \"File\");\n\t\t\t\t\t}\n\t\t\t\t\tf->type = 0;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlist_insert(file_list, f);\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\t/* Store the entries in a flat array. */\n\tfile_pointers = malloc(sizeof(struct File *) * file_list->length);\n\tfile_pointers_len = file_list->length;\n\tint i = 0;\n\tforeach (node, file_list) {\n\t\tfile_pointers[i] = node->value;\n\t\ti++;\n\t}\n\n\tupdate_status();\n\n\t/* Free our temporary linked list */\n\tlist_free(file_list);\n\tfree(file_list);\n\n\t/* Sort files */\n\tint comparator(const void * c1, const void * c2) {\n\t\tconst struct File * f1 = *(const struct File **)(c1);\n\t\tconst struct File * f2 = *(const struct File **)(c2);\n\t\t/* Launchers before directories before files */\n\t\tif (f1->type > f2->type) return -1;\n\t\tif (f2->type > f1->type) return 1;\n\t\t/* Launchers sorted by filename, not by display name */\n\t\tif (f1->type == 2 && f2->type == 2) {\n\t\t\treturn strcmp(f1->filename, f2->filename);\n\t\t}\n\t\t/* Files sorted by name */\n\t\treturn strcmp(f1->name, f2->name);\n\t}\n\tqsort(file_pointers, file_pointers_len, sizeof(struct File *), comparator);\n\n\t/* Reset scroll offset when navigating */\n\tscroll_offset = 0;\n}\n\n/**\n * Resize and redraw the icon view */\nstatic void reinitialize_contents(void) {\n\n\t/* If there already is a context, free it. */\n\tif (contents) {\n\t\tfree(contents);\n\t}\n\n\t/* If there already is a context buffer, free it. */\n\tif (contents_sprite) {\n\t\tsprite_free(contents_sprite);\n\t}\n\n\t/* Get window bounds to determine how wide we can make our icon view */\n\tstruct decor_bounds bounds;\n\t_decor_get_bounds(main_window, &bounds);\n\n\tif (is_desktop_background) {\n\t\t/**\n\t\t * TODO: Actually calculate an optimal FILE_PTR_WIDTH or fix this to\n\t\t *       work properly with vertical rows of files\n\t\t */\n\t\tFILE_PTR_WIDTH = 1;\n\t} else if (view_mode == VIEW_MODE_LIST) {\n\t\tFILE_PTR_WIDTH = 1;\n\t\tFILE_WIDTH = (ctx->width - bounds.width);\n\t} else if (view_mode == VIEW_MODE_TILES) {\n\t\t/* We want to get close to 260 */\n\t\tint avail = ctx->width - bounds.width;\n\t\tint columns = avail / 260;\n\t\tFILE_WIDTH = avail / columns;\n\t\tFILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH;\n\t} else if (view_mode == VIEW_MODE_ICONS) {\n\t\tint avail = ctx->width - bounds.width;\n\t\tint columns = avail / 100;\n\t\tFILE_WIDTH = avail / columns;\n\t\tFILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH;\n\t} else {\n\t\tFILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH;\n\t}\n\n\t/* Calculate required height to fit files */\n\tint calculated_height = (file_pointers_len / FILE_PTR_WIDTH + !!(file_pointers_len % FILE_PTR_WIDTH)) * FILE_HEIGHT;\n\n\t/* Create buffer */\n\tcontents_sprite = create_sprite(FILE_PTR_WIDTH * FILE_WIDTH, calculated_height, ALPHA_EMBEDDED);\n\tcontents = init_graphics_sprite(contents_sprite);\n\n\t/* Draw file entries */\n\tredraw_files();\n}\n\n#define BUTTON_SPACE 34\n#define BUTTON_COUNT 4\n/**\n * Render toolbar buttons\n */\nstatic void _draw_buttons(struct decor_bounds bounds) {\n\n\t/* Draws the toolbar background as a gradient; XXX hardcoded theme details */\n\tuint32_t gradient_top = rgb(59,59,59);\n\tuint32_t gradient_bot = rgb(40,40,40);\n\tfor (int i = 0; i < 36; ++i) {\n\t\tuint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36);\n\t\tdraw_rectangle(ctx, bounds.left_width, bounds.top_height + menu_bar_height + i,\n\t\t\t\tBUTTON_SPACE * BUTTON_COUNT, 1, c);\n\t}\n\n\tint x = 0;\n\tint i = 0;\n#define draw_button(label) do { \\\n\tstruct TTKButton _up = {bounds.left_width + 2 + x,bounds.top_height + menu_bar_height + 2,32,32,\"\\033\" label,_button_hilights[i] | (_button_disabled[i] << 8)}; \\\n\tttk_button_draw(ctx, &_up); \\\n\tx += BUTTON_SPACE; i++; } while (0)\n\n\t/* Draw actual buttons */\n\tdraw_button(\"back\");\n\tdraw_button(\"forward\");\n\tdraw_button(\"up\");\n\tdraw_button(\"home\");\n}\n\n/**\n * Determine what character offset the cursor should be at for\n * a given X coordinate.\n */\nstatic void _figure_out_navbar_cursor(int x, struct decor_bounds bounds) {\n\tx = x - bounds.left_width - 2 - BUTTON_SPACE * BUTTON_COUNT - 5;\n\tif (x <= 0) {\n\t\tnav_bar_cursor_x = 0;\n\t\treturn;\n\t}\n\n\tchar * tmp = strdup(nav_bar);\n\tint candidate = 0;\n\ttt_set_size(tt_font_thin, 13);\n\twhile (*tmp && x + 2 < (candidate = tt_string_width(tt_font_thin, tmp))) {\n\t\ttmp[strlen(tmp)-1] = '\\0';\n\t}\n\tnav_bar_cursor_x = candidate;\n\tnav_bar_cursor = strlen(tmp);\n\tfree(tmp);\n}\n\n/**\n * Recalculate the location of the cursor indicator bar\n * based on the current cursor character offset;\n * also handles cursor bounds within the text\n * (eg. to avoid cursor moving beyond the beginning)\n */\nstatic void _recalculate_nav_bar_cursor(void) {\n\tif (nav_bar_cursor < 0) {\n\t\tnav_bar_cursor = 0;\n\t}\n\tif (nav_bar_cursor > (int)strlen(nav_bar)) {\n\t\tnav_bar_cursor = strlen(nav_bar);\n\t}\n\tchar * tmp = strdup(nav_bar);\n\ttmp[nav_bar_cursor] = '\\0';\n\ttt_set_size(tt_font_thin, 13);\n\tnav_bar_cursor_x = tt_string_width(tt_font_thin, tmp);\n\tfree(tmp);\n}\n\n/**\n * Draw the navigation input box.\n */\nstatic void _draw_nav_bar(struct decor_bounds bounds) {\n\n\t/* Draw toolbar background */\n\tuint32_t gradient_top = rgb(59,59,59);\n\tuint32_t gradient_bot = rgb(40,40,40);\n\tint x = BUTTON_SPACE * BUTTON_COUNT;\n\n\tfor (int i = 0; i < 36; ++i) {\n\t\tuint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36);\n\t\tdraw_rectangle(ctx, bounds.left_width + BUTTON_SPACE * BUTTON_COUNT, bounds.top_height + menu_bar_height + i,\n\t\t\t\tctx->width - bounds.width - BUTTON_SPACE * BUTTON_COUNT, 1, c);\n\t}\n\n\t/* Draw input box */\n\tif (nav_bar_focused && main_window->focused) {\n\t\tstruct gradient_definition edge = {28, bounds.top_height + menu_bar_height + 3, rgb(0,120,220), rgb(0,120,220)};\n\t\tdraw_rounded_rectangle_pattern(ctx, bounds.left_width + 2 + x + 1, bounds.top_height + menu_bar_height + 4, main_window->width - bounds.width - x - 6, 26, 4, gfx_vertical_gradient_pattern, &edge);\n\t\tdraw_rounded_rectangle(ctx, bounds.left_width + 2 + x + 3, bounds.top_height + menu_bar_height + 6, main_window->width - bounds.width - x - 10, 22, 2, rgb(250,250,250));\n\t} else {\n\t\tstruct gradient_definition edge = {28, bounds.top_height + menu_bar_height + 3, rgb(90,90,90), rgb(110,110,110)};\n\t\tdraw_rounded_rectangle_pattern(ctx, bounds.left_width + 2 + x + 1, bounds.top_height + menu_bar_height + 4, main_window->width - bounds.width - x - 6, 26, 4, gfx_vertical_gradient_pattern, &edge);\n\t\tdraw_rounded_rectangle(ctx, bounds.left_width + 2 + x + 2, bounds.top_height + menu_bar_height + 5, main_window->width - bounds.width - x - 8, 24, 3, rgb(250,250,250));\n\t}\n\n\t/* Draw the nav bar text, ellipsified if needed */\n\tint max_width = main_window->width - bounds.width - x - 12;\n\tchar * name = tt_ellipsify(nav_bar, 13, tt_font_thin, max_width, NULL);\n\ttt_draw_string(ctx, tt_font_thin, bounds.left_width + 2 + x + 5, bounds.top_height + menu_bar_height + 8 + 13, name, rgb(0,0,0));\n\tfree(name);\n\n\tif (nav_bar_focused && main_window->focused && !nav_bar_blink) {\n\t\t/* Draw cursor indicator at cursor_x */\n\t\tdraw_line(ctx,\n\t\t\t\tbounds.left_width + 2 + x + 5 + nav_bar_cursor_x,\n\t\t\t\tbounds.left_width + 2 + x + 5 + nav_bar_cursor_x,\n\t\t\t\tbounds.top_height + menu_bar_height + 8,\n\t\t\t\tbounds.top_height + menu_bar_height + 8 + 15,\n\t\t\t\trgb(0,0,0));\n\t}\n}\n\n#define STATUS_HEIGHT 24\n/**\n * Draw the status bar at the bottom of the window\n */\nstatic void _draw_status(struct decor_bounds bounds) {\n\n\t/* Background gradient */\n\tuint32_t gradient_top = rgb(80,80,80);\n\tuint32_t gradient_bot = rgb(59,59,59);\n\tdraw_rectangle(ctx, bounds.left_width, ctx->height - bounds.bottom_height - STATUS_HEIGHT,\n\t\t\tctx->width - bounds.width, 1, rgb(110,110,110) );\n\tfor (int i = 1; i < STATUS_HEIGHT; ++i) {\n\t\tuint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / STATUS_HEIGHT);\n\t\tdraw_rectangle(ctx, bounds.left_width, ctx->height - bounds.bottom_height - STATUS_HEIGHT + i,\n\t\t\t\tctx->width - bounds.width, 1, c );\n\t}\n\n\n\t/* Text with draw shadow */\n\t{\n\t\tsprite_t * _tmp_s = create_sprite(ctx->width - bounds.width - 4, STATUS_HEIGHT-3, ALPHA_EMBEDDED);\n\t\tgfx_context_t * _tmp = init_graphics_sprite(_tmp_s);\n\n\t\tdraw_fill(_tmp, rgba(0,0,0,0));\n\t\ttt_set_size(tt_font_thin, 13);\n\t\ttt_draw_string(_tmp, tt_font_thin, 1, 14, window_status, rgb(0,0,0));\n\t\tblur_context_box(_tmp, 4);\n\n\t\ttt_draw_string(_tmp, tt_font_thin, 0, 13, window_status, rgb(255,255,255));\n\n\t\tfree(_tmp);\n\t\tdraw_sprite(ctx, _tmp_s, bounds.left_width + 4, ctx->height - bounds.bottom_height - STATUS_HEIGHT + 3);\n\t\tsprite_free(_tmp_s);\n\t}\n}\n\n/**\n * Redraw the navigation input box (while typing)\n */\nstatic void _redraw_nav_bar(void) {\n\tstruct decor_bounds bounds;\n\t_decor_get_bounds(main_window, &bounds);\n\t_draw_nav_bar(bounds);\n\tflip(ctx);\n\tyutani_flip(yctx, main_window);\n}\n\n/**\n * Blink the navbar cursor, maybe.\n */\nstatic void maybe_blink_cursor(void) {\n\tif (!nav_bar_focused) return;\n\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttime_t sec_diff = t.tv_sec - nav_bar_last_blinked.tv_sec;\n\tsuseconds_t usec_diff = t.tv_usec - nav_bar_last_blinked.tv_usec;\n\n\tif (t.tv_usec < nav_bar_last_blinked.tv_usec) {\n\t\tsec_diff -= 1;\n\t\tusec_diff = (1000000 + t.tv_usec) - nav_bar_last_blinked.tv_usec;\n\t}\n\n\tif (sec_diff >= 1 || usec_diff >= 530000) {\n\t\tnav_bar_blink = !nav_bar_blink;\n\t\tgettimeofday(&nav_bar_last_blinked, NULL);\n\t\t_redraw_nav_bar();\n\t}\n}\n\nstatic void nav_bar_set_focused(void) {\n\tnav_bar_focused = 1;\n\tnav_bar_blink = 0;\n\tgettimeofday(&nav_bar_last_blinked, NULL);\n}\n\n/**\n * navbar: Text editing helpers for ^W, deletes one directory element\n */\nstatic void nav_bar_backspace_word(void) {\n\tif (!*nav_bar) return;\n\tif (nav_bar_cursor == 0) return;\n\n\tchar * after = strdup(&nav_bar[nav_bar_cursor]);\n\n\tif (nav_bar[nav_bar_cursor-1] == '/') {\n\t\tnav_bar[nav_bar_cursor-1] = '\\0';\n\t\tnav_bar_cursor--;\n\t}\n\twhile (nav_bar_cursor && nav_bar[nav_bar_cursor-1] != '/') {\n\t\tnav_bar[nav_bar_cursor-1] = '\\0';\n\t\tnav_bar_cursor--;\n\t}\n\n\tstrcat(nav_bar, after);\n\tfree(after);\n\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\n/**\n * navbar: Text editing helper for backspace, deletes one character\n */\nstatic void nav_bar_backspace(void) {\n\tif (nav_bar_cursor == 0) return;\n\n\tchar * after = strdup(&nav_bar[nav_bar_cursor]);\n\n\tnav_bar[nav_bar_cursor-1] = '\\0';\n\tnav_bar_cursor--;\n\n\tstrcat(nav_bar, after);\n\tfree(after);\n\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\nstatic void nav_bar_delete(void) {\n\tif (!nav_bar[nav_bar_cursor]) return;\n\n\tchar * after = strdup(&nav_bar[nav_bar_cursor+1]);\n\tnav_bar[nav_bar_cursor] = '\\0';\n\n\tstrcat(nav_bar, after);\n\tfree(after);\n\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\n/**\n * navbar: Text editing helper for inserting characters\n */\nstatic void nav_bar_insert_char(char c) {\n\tchar * tmp = strdup(nav_bar);\n\ttmp[nav_bar_cursor] = '\\0';\n\tchar * after = strdup(&nav_bar[nav_bar_cursor]);\n\tsprintf(nav_bar, \"%s%c%s\", tmp, c, after);\n\tfree(tmp);\n\tfree(after);\n\n\tnav_bar_cursor += 1;\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\n/**\n * navbar: Move editing cursor one character left\n */\nstatic void nav_bar_cursor_left(int modifiers) {\n\tif (!*nav_bar) return;\n\tif (nav_bar_cursor == 0) return;\n\n\tif (modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) {\n\t\tif (nav_bar[nav_bar_cursor-1] == '/') {\n\t\t\tnav_bar_cursor--;\n\t\t}\n\t\twhile (nav_bar_cursor && nav_bar[nav_bar_cursor-1] != '/') {\n\t\t\tnav_bar_cursor--;\n\t\t}\n\t} else {\n\t\tnav_bar_cursor--;\n\t}\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\n/**\n * navbar: Move editing cursor one character right\n */\nstatic void nav_bar_cursor_right(int modifiers) {\n\tif (!*nav_bar) return;\n\n\tif (modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) {\n\t\tif (nav_bar[nav_bar_cursor] == '/') {\n\t\t\tnav_bar_cursor++;\n\t\t}\n\t\twhile (nav_bar[nav_bar_cursor] && nav_bar[nav_bar_cursor] != '/') {\n\t\t\tnav_bar_cursor++;\n\t\t}\n\t} else {\n\t\tnav_bar_cursor++;\n\t}\n\n\t_recalculate_nav_bar_cursor();\n\tnav_bar_set_focused();\n\t_redraw_nav_bar();\n}\n\n/**\n * Redraw the entire window.\n */\nstatic void redraw_window(void) {\n\tif (!is_desktop_background) {\n\t\t/* Clear to white and draw decorations */\n\t\tdraw_fill(ctx, rgb(255,255,255));\n\t\trender_decorations(main_window, ctx, title);\n\t} else {\n\t\t/* Draw wallpaper in desktop mode */\n\t\tif (wallpaper_old) {\n\t\t\tdraw_sprite(ctx, wallpaper_old, 0, 0);\n\t\t\tuint64_t ellapsed = precise_time_since(timer);\n\t\t\tif (ellapsed > 1000) {\n\t\t\t\tsprite_free(wallpaper_old);\n\t\t\t\twallpaper_old = NULL;\n\t\t\t\tdraw_sprite(ctx, wallpaper_buffer, 0, 0);\n\t\t\t} else {\n\t\t\t\tdraw_sprite_alpha(ctx, wallpaper_buffer, 0, 0, (float)ellapsed / 1000.0);\n\t\t\t}\n\t\t} else {\n\t\t\tdraw_sprite(ctx, wallpaper_buffer, 0, 0);\n\t\t}\n\t}\n\n\tstruct decor_bounds bounds;\n\t_decor_get_bounds(main_window, &bounds);\n\n\tif (!is_desktop_background) {\n\t\t/* Position, size, and draw the menu bar */\n\t\tif (menu_bar_height) {\n\t\t\tmenu_bar.x = bounds.left_width;\n\t\t\tmenu_bar.y = bounds.top_height;\n\t\t\tmenu_bar.width = ctx->width - bounds.width;\n\t\t\tmenu_bar.window = main_window;\n\t\t\tmenu_bar_render(&menu_bar, ctx);\n\t\t}\n\n\t\t/* Draw toolbar */\n\t\t_draw_buttons(bounds);\n\t\t_draw_nav_bar(bounds);\n\n\t\t_draw_status(bounds);\n\t}\n\n\t/* Draw the icon view, clipped to the viewport and scrolled appropriately. */\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, bounds.left_width, bounds.top_height + menu_bar_height + nav_bar_height, ctx->width - bounds.width, available_height);\n\tdraw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + menu_bar_height + nav_bar_height - scroll_offset);\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, 0, 0, ctx->width, ctx->height);\n\n\t/* Flip graphics context and inform compositor */\n\tflip(ctx);\n\tyutani_flip(yctx, main_window);\n}\n\n/**\n * Loads and bakes the wallpaper to the appropriate size.\n */\nstatic void draw_background(int width, int height) {\n\n\t/* If the wallpaper is already loaded, free it. */\n\tif (wallpaper_buffer) {\n\t\tif (wallpaper_old) {\n\t\t\tsprite_free(wallpaper_old);\n\t\t}\n\t\twallpaper_old = wallpaper_buffer;\n\t\ttimer = precise_current_time();\n\t}\n\n\t/* Open the wallpaper */\n\tsprite_t * wallpaper = malloc(sizeof(sprite_t));\n\n\tchar * wallpaper_path = WALLPAPER_PATH;\n\tint free_it = 0;\n\tchar * home = getenv(\"HOME\");\n\tif (home) {\n\t\tchar tmp[512];\n\t\tsprintf(tmp, \"%s/.wallpaper.conf\", home);\n\t\tFILE * c = fopen(tmp, \"r\");\n\t\tif (c) {\n\t\t\tchar line[1024];\n\t\t\twhile (!feof(c)) {\n\t\t\t\tfgets(line, 1024, c);\n\t\t\t\tchar * nl = strchr(line, '\\n');\n\t\t\t\tif (nl) *nl = '\\0';\n\t\t\t\tif (line[0] == ';') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (strstr(line, \"wallpaper=\") == line) {\n\t\t\t\t\tfree_it = 1;\n\t\t\t\t\twallpaper_path = strdup(line+strlen(\"wallpaper=\"));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfclose(c);\n\t\t}\n\t}\n\n\tload_sprite(wallpaper, wallpaper_path);\n\n\tif (free_it) {\n\t\tfree(wallpaper_path);\n\t}\n\n\t/* Create a new buffer to hold the baked wallpaper */\n\twallpaper_buffer = create_sprite(width, height, 0);\n\tgfx_context_t * ctx = init_graphics_sprite(wallpaper_buffer);\n\n\t/* Calculate the appropriate scaled size to fit the screen. */\n\tfloat x = (float)width / (float)wallpaper->width;\n\tfloat y = (float)height / (float)wallpaper->height;\n\n\tint nh = (int)(x * (float)wallpaper->height);\n\tint nw = (int)(y * (float)wallpaper->width);\n\n\t/* Clear to black to avoid odd transparency issues along edges */\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\t/* Scale the wallpaper into the buffer. */\n\tif (nw == wallpaper->width && nh == wallpaper->height) {\n\t\t/* No scaling necessary */\n\t\tdraw_sprite(ctx, wallpaper, 0, 0);\n\t} else if (nw >= width) {\n\t\t/* Scaled wallpaper is wider, height should match. */\n\t\tdraw_sprite_scaled(ctx, wallpaper, ((int)width - nw) / 2, 0, nw+2, height);\n\t} else {\n\t\t/* Scaled wallpaper is taller, width should match. */\n\t\tdraw_sprite_scaled(ctx, wallpaper, 0, ((int)height - nh) / 2, width+2, nh);\n\t}\n\n\t/* Free the original wallpaper. */\n\tsprite_free(wallpaper);\n\tfree(ctx);\n}\n\n/**\n * Resize window when asked by the compositor.\n */\nstatic void resize_finish(int w, int h) {\n\n\tif (w < 300 || h < 300) {\n\t\tyutani_window_resize_offer(yctx, main_window, w < 300 ? 300 : w, h < 300 ? 300 : h);\n\t\treturn;\n\t}\n\n\tint width_changed = (main_window->width != (unsigned int)w);\n\n\tyutani_window_resize_accept(yctx, main_window, w, h);\n\treinit_graphics_yutani(ctx, main_window);\n\n\tstruct decor_bounds bounds;\n\t_decor_get_bounds(main_window, &bounds);\n\n\t/* Recalculate available size */\n\tavailable_height = ctx->height - menu_bar_height - nav_bar_height - bounds.height - (is_desktop_background ? 0 : STATUS_HEIGHT);\n\tfprintf(stderr, \"available_height = %d; bounds.bottom_height = %d, (isd...) = %d\\n\", available_height, bounds.bottom_height, (is_desktop_background ? 0 : STATUS_HEIGHT));\n\n\t/* If the width changed, we need to rebuild the icon view */\n\tif (width_changed) {\n\t\treinitialize_contents();\n\t}\n\n\t/* Make sure we're not scrolled weirdly after resizing */\n\tif (available_height > contents->height) {\n\t\tscroll_offset = 0;\n\t} else {\n\t\tif (scroll_offset > contents->height - available_height) {\n\t\t\tscroll_offset = contents->height - available_height;\n\t\t}\n\t}\n\n\t/* If the desktop background changes size, we have to reload and rescale the wallpaper */\n\tif (is_desktop_background) {\n\t\tdraw_background(w, h);\n\t}\n\n\t/* Redraw */\n\tredraw_window();\n\tyutani_window_resize_done(yctx, main_window);\n\n\tyutani_flip(yctx, main_window);\n}\n\n/* TODO: We don't have an input box yet. */\n#if 0\nstatic void _menu_action_input_path(struct MenuEntry * entry) {\n\n}\n#endif\n\n/* File > Exit */\nstatic void _menu_action_exit(struct MenuEntry * entry) {\n\tapplication_running = 0;\n}\n\n/* Go > ... generic handler */\nstatic void _menu_action_navigate(struct MenuEntry * entry) {\n\t/* go to entry->action */\n\tstruct MenuEntry_Normal * _entry = (void*)entry;\n\tload_directory(_entry->action, 1);\n\treinitialize_contents();\n\tredraw_window();\n}\n\n/* Go > Up */\nstatic void _menu_action_up(struct MenuEntry * entry) {\n\t/* go up */\n\tchar * tmp = strdup(current_directory);\n\tchar * dir = dirname(tmp);\n\tload_directory(dir, 1);\n\treinitialize_contents();\n\tredraw_window();\n}\n\n/* [Context] > Refresh */\nstatic void _menu_action_refresh(struct MenuEntry * entry) {\n\tchar * tmp = strdup(current_directory);\n\tload_directory(tmp, 0);\n\treinitialize_contents();\n\tredraw_window();\n}\n\n/* Help > Contents */\nstatic void _menu_action_help(struct MenuEntry * entry) {\n\t/* show help documentation */\n\tsystem(\"help-browser file-browser.trt &\");\n\tredraw_window();\n}\n\n/* [Context] > Copy */\nstatic void _menu_action_copy(struct MenuEntry * entry) {\n\tsize_t output_size = 0;\n\n\t/* Calculate required space for the clipboard */\n\tint base_is_root = !strcmp(current_directory, \"/\"); /* avoid redundant slash */\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\toutput_size += strlen(current_directory) + !base_is_root + strlen(file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name) + 1; /* base / file \\n */\n\t\t}\n\t}\n\n\t/* Nothing to copy? */\n\tif (!output_size) return;\n\n\t/* Create the clipboard contents as a LF-separated list of absolute paths */\n\tchar * clipboard = malloc(output_size+1); /* last nil */\n\tclipboard[0] = '\\0';\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\tstrcat(clipboard, current_directory);\n\t\t\tif (!base_is_root) { strcat(clipboard, \"/\"); }\n\t\t\tstrcat(clipboard, file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name);\n\t\t\tstrcat(clipboard, \"\\n\");\n\t\t}\n\t}\n\n\tif (clipboard[output_size-1] == '\\n') {\n\t\t/* Remove trailing line feed */\n\t\tclipboard[output_size-1] = '\\0';\n\t}\n\n\n\tyutani_set_clipboard(yctx, clipboard);\n\tfree(clipboard);\n}\n\nstatic void _menu_action_paste(struct MenuEntry * entry) {\n\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD);\n}\n\nstatic void _menu_action_delete(struct MenuEntry * entry) {\n\tsize_t filesToDelete = 0;\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) filesToDelete++;\n\t}\n\n\tint base_is_root = !strcmp(current_directory, \"/\"); /* avoid redundant slash */\n\tchar ** args = malloc(sizeof(char*) * (filesToDelete + 3));\n\targs[0] = \"/bin/prompt_and_delete.krk\";\n\targs[1] = malloc(100);\n\tsnprintf(args[1],100,\"%d\",getpid());\n\tsize_t counter = 2;\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\tconst char * name = file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name;\n\t\t\tsize_t len = strlen(current_directory) + !base_is_root + strlen(name) + 1;\n\t\t\targs[counter] = malloc(len);\n\t\t\tsnprintf(args[counter], len, \"%s%s%s\", current_directory, base_is_root ? \"\" : \"/\", name);\n\t\t\tcounter++;\n\t\t}\n\t}\n\targs[counter] = NULL;\n\n\tpid_t child = fork();\n\n\tif (!child) {\n\t\texecv(args[0], args);\n\t\texit(1);\n\t} else if (child < 0) {\n\t\tfprintf(stderr, \"Error forking.\\n\");\n\t}\n\n\tfor (size_t i = 1; i < counter; ++i) {\n\t\tfree(args[i]);\n\t}\n\tfree(args);\n}\n\n/* Help > About File Browser */\nstatic void _menu_action_about(struct MenuEntry * entry) {\n\t/* Show About dialog */\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About File Browser\\\" /usr/share/icons/48/folder.png \\\"ToaruOS File Browser\\\" \\\"© 2018-2022 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\tredraw_window();\n}\n\n/**\n * Generic application launcher - like system(), but without the wait.\n * Also sets the working directory to the currently-opened directory.\n */\nstatic void launch_application(char * app) {\n\tif (!fork()) {\n\t\tif (current_directory) chdir(current_directory);\n\t\tchar * tmp = malloc(strlen(app) + 10);\n\t\tsprintf(tmp, \"%s\", app);\n\t\tchar * args[] = {\"/bin/sh\", \"-c\", tmp, NULL};\n\t\texecvp(args[0], args);\n\t\texit(1);\n\t}\n}\n\n/* Generic handler for various launcher menus */\nstatic void launch_application_menu(struct MenuEntry * self) {\n\tstruct MenuEntry_Normal * _self = (void *)self;\n\tlaunch_application((char *)_self->action);\n}\n\n/**\n * Perform the appropriate action to open a File\n */\nstatic void open_file(struct File * f) {\n\tif (f->type == 1) {\n\t\tchar tmp[1024];\n\t\tif (is_desktop_background) {\n\t\t\t/* Always open directories in new file browser windows when launched from desktop */\n\t\t\tsprintf(tmp,\"file-browser \\\"%s/%s\\\"\", current_directory, f->name);\n\t\t\tlaunch_application(tmp);\n\t\t} else {\n\t\t\t/* In normal mode, navigate to this directory. */\n\t\t\tsprintf(tmp,\"%s/%s\", current_directory, f->name);\n\t\t\tload_directory(tmp, 1);\n\t\t\treinitialize_contents();\n\t\t\tredraw_window();\n\t\t}\n\t} else if (f->launcher[0]) {\n\t\tif (is_picker_dialog) {\n\t\t\tint base_is_root = !strcmp(current_directory, \"/\"); /* avoid redundant slash */\n\t\t\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\t\t\tif (file_pointers[i]->selected) {\n\t\t\t\t\tprintf(\"%s%s%s\\n\", current_directory, base_is_root ? \"\" : \"/\",\n\t\t\t\t\t\tfile_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name);\n\t\t\t\t}\n\t\t\t}\n\t\t\texit(0);\n\t\t}\n\t\tchar tmp[4096];\n\t\tif (!strcmp(f->launcher, \"SELF\")) {\n\t\t\t/* \"SELF\" launchers are for binaries. */\n\t\t\tsprintf(tmp, \"exec ./%s\", f->name);\n\t\t} else {\n\t\t\t/* Other launchers should take file names as arguments.\n\t\t\t * NOTE: If you don't want the file name, you can append # to your launcher.\n\t\t\t *       Since it's parsed by the shell, this will yield a comment.\n\t\t\t */\n\t\t\tsprintf(tmp, \"%s \\\"%s\\\"\", f->launcher, f->name);\n\t\t}\n\t\tlaunch_application(tmp);\n\t}\n}\n\n/* [Context] > Open */\nstatic void _menu_action_open(struct MenuEntry * self) {\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\topen_file(file_pointers[i]);\n\t\t}\n\t}\n}\n\n/* [Context] > Edit in Bim */\nstatic void _menu_action_edit(struct MenuEntry * self) {\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\tchar tmp[1024];\n\t\t\tsprintf(tmp, \"exec terminal bim \\\"%s\\\"\", file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name);\n\t\t\tlaunch_application(tmp);\n\t\t}\n\t}\n}\n\n/* View > (Show/Hide) Hidden Files */\nstatic void _menu_action_toggle_hidden(struct MenuEntry * self) {\n\tshow_hidden = !show_hidden;\n\tmenu_update_toggle_state(self, show_hidden);\n\t_menu_action_refresh(NULL);\n}\n\nstatic void _menu_action_select_all(struct MenuEntry * self) {\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tfile_pointers[i]->selected = 1;\n\t}\n\treinitialize_contents();\n\tupdate_status();\n\tredraw_window();\n}\n\n/**\n * Set the view mode for the file view\n * We support three modes:\n * - Icons: Standard view, label centered below icon.\n * - Tiles: Like Windows Explorer, labels left-aligned to the right of\n *          the icon, with extra details also displayed. Bold file name.\n * - List:  One-line-per-file, icon on the left, left aligne file name.\n */\nstatic void set_view_mode(int mode) {\n\n\tswitch (mode) {\n\t\tdefault:\n\t\tcase VIEW_MODE_ICONS:\n\t\t\tFILE_HEIGHT = 80;\n\t\t\tFILE_WIDTH = 100;\n\t\t\tview_mode = VIEW_MODE_ICONS;\n\t\t\tbreak;\n\t\tcase VIEW_MODE_TILES:\n\t\t\tFILE_HEIGHT = 70;\n\t\t\tFILE_WIDTH = 260;\n\t\t\tview_mode = VIEW_MODE_TILES;\n\t\t\tbreak;\n\t\tcase VIEW_MODE_LIST:\n\t\t\tFILE_HEIGHT = 24;\n\t\t\tFILE_WIDTH  = 100; /* Readjusts elsewhere */\n\t\t\tview_mode = VIEW_MODE_LIST;\n\t\t\tbreak;\n\t}\n}\n\n/**\n * Dropdown action handler for view mode entries;\n * use menuitem action to determine which view mode to set.\n */\nstatic void _menu_action_view_mode(struct MenuEntry * entry) {\n\tstruct MenuEntry_Normal * _entry = (void*)entry;\n\tint mode = VIEW_MODE_ICONS;\n\tif (!strcmp(_entry->action, \"icons\")) {\n\t\tmode = VIEW_MODE_ICONS;\n\t} else if (!strcmp(_entry->action, \"tiles\")) {\n\t\tmode = VIEW_MODE_TILES;\n\t} else if (!strcmp(_entry->action, \"list\")) {\n\t\tmode = VIEW_MODE_LIST;\n\t}\n\tset_view_mode(mode);\n\tmenu_update_toggle_state(_menu_entry_show_icons, view_mode == VIEW_MODE_ICONS);\n\tmenu_update_toggle_state(_menu_entry_show_tiles, view_mode == VIEW_MODE_TILES);\n\tmenu_update_toggle_state(_menu_entry_show_list,  view_mode == VIEW_MODE_LIST);\n\treinitialize_contents();\n\tredraw_window();\n}\n\n/**\n * Receive pastes, which are presumed to be file names of files\n * which have been copied and should now be pasted into a new\n * directory; will not overwrite if pasted into the same directory\n * or a directory with a file with the same name.\n *\n * XXX: Calls `cp` to perform actual copy.\n *\n * TODO: Actually check if clipboard contains a file name.\n * TODO: Handle pastes into the navbar of arbitrary text.\n */\nstatic void handle_clipboard(char * contents) {\n\tfprintf(stderr, \"Received clipboard:\\n%s\\n\",contents);\n\n\tchar * file = contents;\n\twhile (file && *file) {\n\t\tchar * next_file = strchr(file, '\\n');\n\t\tif (next_file) {\n\t\t\t*next_file = '\\0';\n\t\t\tnext_file++;\n\t\t}\n\n\t\t/* determine if the destination already exists */\n\t\tchar * cheap_basename = strrchr(file, '/');\n\t\tif (!cheap_basename) cheap_basename = file;\n\t\telse cheap_basename++;\n\n\t\tchar destination[4096];\n\t\tsprintf(destination, \"%s/%s\", current_directory, cheap_basename);\n\n\t\tstruct stat statbuf;\n\t\tif (!stat(destination, &statbuf)) {\n\t\t\tchar message[4096];\n\t\t\tsprintf(message, \"showdialog \\\"File Browser\\\" /usr/share/icons/48/folder.png \\\"Not overwriting file '%s'.\\\"\", cheap_basename);\n\t\t\tlaunch_application(message);\n\t\t} else {\n\t\t\tchar cp[1024];\n\t\t\tsprintf(cp, \"cp -r \\\"%s\\\" \\\"%s\\\"\", file, current_directory);\n\t\t\tif (system(cp)) {\n\t\t\t\tchar message[4096];\n\t\t\t\tsprintf(message, \"showdialog \\\"File Browser\\\" /usr/share/icons/48/folder.png \\\"Error copying file '%s'.\\\"\", cheap_basename);\n\t\t\t\tlaunch_application(message);\n\t\t\t}\n\t\t}\n\t\tfile = next_file;\n\t}\n\n\t_menu_action_refresh(NULL);\n}\n\n/**\n * Toggle the selected status of the highlighted icon.\n *\n * When Ctrl is held, the current selection is maintained.\n */\nstatic void toggle_selected(int hilighted_offset, int modifiers) {\n\tstruct File * f = get_file_at_offset(hilighted_offset);\n\n\t/* No file at this offset, do nothing. */\n\tif (!f) return;\n\n\t/* Toggle selection of the current file */\n\tf->selected = !f->selected;\n\n\t/* If Ctrl wasn't held, unselect everything else. */\n\tif (!(modifiers & YUTANI_KEY_MODIFIER_CTRL)) {\n\t\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\t\tif (file_pointers[i] != f && file_pointers[i]->selected) {\n\t\t\t\tfile_pointers[i]->selected = 0;\n\t\t\t\tclear_offset(i);\n\t\t\t\tdraw_file(file_pointers[i], i);\n\t\t\t}\n\t\t}\n\t}\n\n\tupdate_status();\n\n\t/* Redraw the file */\n\tclear_offset(hilighted_offset);\n\tdraw_file(f, hilighted_offset);\n\n\t/* And repaint the window */\n\tredraw_window();\n}\n\n/**\n * Handle button hover highlights\n */\nstatic int _down_button = -1;\nstatic void _set_hilight(int index, int hilight) {\n\tint _update = 0;\n\tif (_button_hover != index || (_button_hover == index && index != -1 && _button_hilights[index] != hilight)) {\n\t\tif (_button_hover != -1 && _button_hilights[_button_hover] != 3) {\n\t\t\t_button_hilights[_button_hover] = 3;\n\t\t\t_update = 1;\n\t\t}\n\t\t_button_hover = index;\n\t\tif (index != -1 && !_button_disabled[index]) {\n\t\t\t_button_hilights[_button_hover] = hilight;\n\t\t\t_update = 1;\n\t\t}\n\t\tif (_update) {\n\t\t\tredraw_window();\n\t\t}\n\t}\n}\n\n/**\n * Handle toolbar button clicking\n */\nstatic void _handle_button_press(int index) {\n\tif (index != -1 && _button_disabled[index]) return; /* can't click disabled buttons */\n\tswitch (index) {\n\t\tcase 0:\n\t\t\t/* Back */\n\t\t\tif (history_back->length) {\n\t\t\t\tlist_insert(history_forward, strdup(current_directory));\n\t\t\t\tnode_t * next = list_pop(history_back);\n\t\t\t\tload_directory(next->value, 0);\n\t\t\t\tfree(next->value);\n\t\t\t\tfree(next);\n\t\t\t\treinitialize_contents();\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\t/* Forward */\n\t\t\tif (history_forward->length) {\n\t\t\t\tlist_insert(history_back, strdup(current_directory));\n\t\t\t\tnode_t * next = list_pop(history_forward);\n\t\t\t\tload_directory(next->value, 0);\n\t\t\t\tfree(next->value);\n\t\t\t\tfree(next);\n\t\t\t\treinitialize_contents();\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\t/* Up */\n\t\t\t_menu_action_up(NULL);\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\t/* Home */\n\t\t\t{\n\t\t\t\tstruct MenuEntry_Normal _fake = {.action = getenv(\"HOME\") };\n\t\t\t\t_menu_action_navigate(&_fake);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\t/* ??? */\n\t\t\tbreak;\n\t}\n}\n\n/**\n * Scroll file view up\n */\nstatic void _scroll_up(void) {\n\tscroll_offset -= SCROLL_AMOUNT;\n\tif (scroll_offset < 0) {\n\t\tscroll_offset = 0;\n\t}\n}\n\n/**\n * Scroll file view down\n */\nstatic void _scroll_down(void) {\n\tif (available_height > contents->height) {\n\t\tscroll_offset = 0;\n\t} else {\n\t\tscroll_offset += SCROLL_AMOUNT;\n\t\tif (scroll_offset > contents->height - available_height) {\n\t\t\tscroll_offset = contents->height - available_height;\n\t\t}\n\t}\n}\n\nstatic int signalResponse = 0;\n\n/**\n * Desktop mode responsds to sig_usr2 by returning to\n * the bottom of the Z-order stack.\n */\nstatic void sig_usr2(int sig) {\n\tsignalResponse |= 1;\n}\n\n/**\n * Desktop mode responds to sig_usr1 by resizing the window\n * to the current display size.\n */\nstatic void sig_usr1(int sig) {\n\tsignalResponse |= 2;\n}\n\nstatic void sig_urg(int sig) {\n\tsignalResponse |= 4;\n}\n\n/**\n * Accept keyboard arrows left/right/up/down and select the appropriate\n * file in the file view based on the currently selected file; if multiple\n * files are currently selected, the first one (up/left) is used as the basis\n * for the new selection.\n */\nstatic void arrow_select(int x, int y) {\n\tif (!file_pointers_len) return;\n\n\t/* Find first selected */\n\tint selected = -1;\n\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\tif (file_pointers[i]->selected) {\n\t\t\tselected = i;\n\t\t\tfile_pointers[i]->selected = 0;\n\t\t\tclear_offset(i);\n\t\t\tdraw_file(file_pointers[i], i);\n\t\t}\n\t}\n\n\tif (selected == -1) {\n\t\tselected = 0;\n\t} else {\n\t\tint offset_y = selected / FILE_PTR_WIDTH;\n\t\tint offset_x = selected % FILE_PTR_WIDTH;\n\n\t\toffset_y += y;\n\t\toffset_x += x;\n\n\t\tif (offset_x >= FILE_PTR_WIDTH) {\n\t\t\toffset_x = FILE_PTR_WIDTH - 1;\n\t\t}\n\t\tif (offset_x < 0) {\n\t\t\toffset_x = 0;\n\t\t}\n\t\tif (offset_y < 0) {\n\t\t\toffset_y = 0;\n\t\t}\n\n\t\tselected = offset_y * FILE_PTR_WIDTH + offset_x;\n\t\tif (selected >= file_pointers_len) selected = file_pointers_len - 1;\n\t\tif (selected < 0) selected = 0;\n\t}\n\n\tint offset_y = selected / FILE_PTR_WIDTH;\n\tif (offset_y * FILE_HEIGHT < scroll_offset) {\n\t\tscroll_offset = offset_y * FILE_HEIGHT;\n\t}\n\tif (offset_y * FILE_HEIGHT + FILE_HEIGHT > scroll_offset + available_height) {\n\t\tscroll_offset = offset_y * FILE_HEIGHT + FILE_HEIGHT - available_height;\n\t}\n\n\tfile_pointers[selected]->selected = 1;\n\tclear_offset(selected);\n\tdraw_file(file_pointers[selected], selected);\n\tupdate_status();\n\tredraw_window();\n}\n\nstatic void redraw_window_callback(struct menu_bar * self) {\n\t(void)self;\n\tredraw_window();\n}\n\nstatic void show_context_menu(struct yutani_msg_window_mouse_event * me) {\n\tif (!context_menu->window && !directory_context_menu->window) {\n\t\tstruct File * f = get_file_at_offset(hilighted_offset);\n\t\tif (f && !f->selected) {\n\t\t\ttoggle_selected(hilighted_offset, me->modifiers);\n\t\t}\n\n\t\tint _have_selection = 0;\n\t\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\t\tif (file_pointers[i]->selected) {\n\t\t\t\t_have_selection = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (_have_selection) {\n\t\t\tmenu_show_at(context_menu, main_window, me->new_x, me->new_y);\n\t\t} else {\n\t\t\tmenu_show_at(directory_context_menu, main_window, me->new_x, me->new_y);\n\t\t}\n\t}\n}\n\nstatic void set_signal_handler(int signum, void (*handler)(int)) {\n\tstruct sigaction action;\n\taction.sa_handler = handler;\n\tsigemptyset(&action.sa_mask);\n\taction.sa_flags = 0; /* do not restart syscalls, do not reset handler */\n\tsigaction(signum, &action, NULL);\n}\n\nint main(int argc, char * argv[]) {\n\n\tyctx = yutani_init();\n\tinit_decorations();\n\n\ttt_font_thin = tt_font_from_shm(\"sans-serif\");\n\ttt_font_bold = tt_font_from_shm(\"sans-serif.bold\");\n\n\tint arg_ind = 1;\n\n\tif (argc > 1 && !strcmp(argv[1], \"--wallpaper\")) {\n\t\tis_desktop_background = 1;\n\t\tmenu_bar_height = 0;\n\t\tnav_bar_height = 0;\n\t\tset_signal_handler(SIGUSR1, sig_usr1);\n\t\tset_signal_handler(SIGUSR2, sig_usr2);\n\t\tdraw_background(yctx->display_width, yctx->display_height);\n\t\tmain_window = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS);\n\t\tyutani_window_move(yctx, main_window, 0, 0);\n\t\tyutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM);\n\t\targ_ind++;\n\t\tFILE * f = fopen(\"/var/run/.wallpaper.pid\", \"w\");\n\t\tfprintf(f, \"%d\\n\", getpid());\n\t\tfclose(f);\n\t} else if (argc > 1 && !strcmp(argv[1], \"--picker\")) {\n\t\tis_picker_dialog = 1;\n\t\tmenu_bar_height = 0;\n\t\tmain_window = yutani_window_create_flags(yctx, 640, 400,  YUTANI_WINDOW_FLAG_DIALOG_ANIMATION);\n\t\tmain_window->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE;\n\t\tyutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2);\n\t\tset_view_mode(VIEW_MODE_LIST);\n\t} else {\n\t\tmain_window = yutani_window_create(yctx, 800, 600);\n\t\tyutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2);\n\t\tset_view_mode(VIEW_MODE_TILES);\n\t}\n\n\tset_signal_handler(SIGURG, sig_urg);\n\n\tif (arg_ind < argc) {\n\t\tchdir(argv[arg_ind]);\n\t}\n\n\tctx = init_graphics_yutani_double_buffer(main_window);\n\n\tstruct decor_bounds bounds;\n\t_decor_get_bounds(main_window, &bounds);\n\n\tset_title(NULL);\n\n\tif (menu_bar_height) {\n\t\tmenu_bar.entries = menu_entries;\n\t\tmenu_bar.redraw_callback = redraw_window_callback;\n\n\t\tmenu_bar.set = menu_set_create();\n\n\t\tstruct MenuList * m = menu_create(); /* File */\n\t\tmenu_insert(m, menu_create_normal(\"exit\",NULL,\"Exit\", _menu_action_exit));\n\t\tmenu_set_insert(menu_bar.set, \"file\", m);\n\n\t\tm = menu_create();\n\t\tmenu_insert(m, menu_create_normal(NULL,NULL,\"Copy\",_menu_action_copy));\n\t\tmenu_insert(m, menu_create_normal(NULL,NULL,\"Paste\",_menu_action_paste));\n\t\tmenu_insert(m, menu_create_separator());\n\t\tmenu_insert(m, menu_create_normal(NULL,NULL,\"Select all\",_menu_action_select_all));\n\t\tmenu_set_insert(menu_bar.set, \"edit\", m);\n\n\t\tm = menu_create();\n\t\tmenu_insert(m, menu_create_normal(\"refresh\",NULL,\"Refresh\", _menu_action_refresh));\n\t\tmenu_insert(m, menu_create_separator());\n\t\tmenu_insert(m, (_menu_entry_show_icons = menu_create_toggle(\"icons\",\"Show Icons\", view_mode == VIEW_MODE_ICONS, _menu_action_view_mode)));\n\t\tmenu_insert(m, (_menu_entry_show_tiles = menu_create_toggle(\"tiles\",\"Show Tiles\", view_mode == VIEW_MODE_TILES, _menu_action_view_mode)));\n\t\tmenu_insert(m, (_menu_entry_show_list  = menu_create_toggle(\"list\", \"Show List\",  view_mode == VIEW_MODE_LIST,  _menu_action_view_mode)));\n\t\tmenu_insert(m, menu_create_separator());\n\t\tmenu_insert(m, menu_create_toggle(NULL,\"Show Hidden Files\", 0, _menu_action_toggle_hidden));\n\t\tmenu_set_insert(menu_bar.set, \"view\", m);\n\n\t\tm = menu_create(); /* Go */\n\t\tmenu_insert(m, menu_create_normal(\"home\",getenv(\"HOME\"),\"Home\",_menu_action_navigate));\n\t\tmenu_insert(m, menu_create_normal(NULL,\"/\",\"File System\",_menu_action_navigate));\n\t\tmenu_insert(m, (_menu_entry_up = menu_create_normal(\"up\",NULL,\"Up\",_menu_action_up)));\n\t\tmenu_set_insert(menu_bar.set, \"go\", m);\n\n\t\tm = menu_create();\n\t\tmenu_insert(m, menu_create_normal(\"help\",NULL,\"Contents\",_menu_action_help));\n\t\tmenu_insert(m, menu_create_separator());\n\t\tmenu_insert(m, menu_create_normal(\"star\",NULL,\"About \" APPLICATION_TITLE,_menu_action_about));\n\t\tmenu_set_insert(menu_bar.set, \"help\", m);\n\t}\n\n\tavailable_height = ctx->height - menu_bar_height - nav_bar_height - bounds.height - (is_desktop_background ? 0 : STATUS_HEIGHT);\n\n\tcontext_menu = menu_create(); /* Right-click menu */\n\tmenu_insert(context_menu, menu_create_normal(NULL,NULL,\"Open\",_menu_action_open));\n\tmenu_insert(context_menu, menu_create_normal(NULL,NULL,\"Edit in Bim\",_menu_action_edit));\n\tmenu_insert(context_menu, menu_create_separator());\n\tmenu_insert(context_menu, menu_create_normal(NULL,NULL,\"Copy\",_menu_action_copy));\n\tmenu_insert(context_menu, menu_create_normal(NULL,NULL,\"Paste\",_menu_action_paste));\n\tmenu_insert(context_menu, menu_create_separator());\n\tmenu_insert(context_menu, menu_create_normal(NULL,NULL,\"Delete\",_menu_action_delete));\n\tif (!is_desktop_background) {\n\t\tmenu_insert(context_menu, menu_create_separator());\n\t\tmenu_insert(context_menu, (_menu_entry_up_ctx_a = menu_create_normal(\"up\",NULL,\"Up\",_menu_action_up)));\n\t}\n\tmenu_insert(context_menu, menu_create_normal(\"refresh\",NULL,\"Refresh\",_menu_action_refresh));\n\tmenu_insert(context_menu, menu_create_normal(\"utilities-terminal\",\"terminal\",\"Open Terminal\",launch_application_menu));\n\n\tdirectory_context_menu = menu_create(); /* the other right click menu */\n\tmenu_insert(directory_context_menu, menu_create_normal(NULL,NULL,\"Paste\",_menu_action_paste));\n\tmenu_insert(directory_context_menu, menu_create_separator());\n\tif (!is_desktop_background) {\n\t\tmenu_insert(directory_context_menu, (_menu_entry_up_ctx_b = menu_create_normal(\"up\",NULL,\"Up\",_menu_action_up)));\n\t}\n\tmenu_insert(directory_context_menu, menu_create_normal(\"refresh\",NULL,\"Refresh\",_menu_action_refresh));\n\tmenu_insert(directory_context_menu, menu_create_normal(\"utilities-terminal\",\"terminal\",\"Open Terminal\",launch_application_menu));\n\n\n\thistory_back = list_create();\n\thistory_forward = list_create();\n\n\n\t/* Load the current working directory */\n\tchar tmp[1024];\n\tgetcwd(tmp, 1024);\n\tload_directory(tmp, 1);\n\n\t/* Draw files */\n\treinitialize_contents();\n\tredraw_window();\n\n\twhile (application_running) {\n\t\twaitpid(-1, NULL, WNOHANG);\n\t\tint fds[1] = {fileno(yctx->sock)};\n\t\tint index = fswait2(1,fds,wallpaper_old ? 10 : 200);\n\n\t\tif (index < 0 && signalResponse) {\n\t\t\tif (signalResponse & 1) {\n\t\t\t\tyutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM);\n\t\t\t\t_menu_action_refresh(NULL);\n\t\t\t} else if (signalResponse & 2) {\n\t\t\t\tyutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height);\n\t\t\t} else if (signalResponse & 4) {\n\t\t\t\t_menu_action_refresh(NULL);\n\t\t\t}\n\t\t\tsignalResponse = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tmaybe_blink_cursor();\n\n\t\tif (index == 1) {\n\t\t\tif (wallpaper_old) {\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tint redraw = 0;\n\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw = 1;\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_WELCOME:\n\t\t\t\t\tif (is_desktop_background) {\n\t\t\t\t\t\tyutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->wid == main_window->wid) {\n\t\t\t\t\t\t\tif (nav_bar_focused) {\n\t\t\t\t\t\t\t\tswitch (ke->event.key) {\n\t\t\t\t\t\t\t\t\tcase KEY_ESCAPE:\n\t\t\t\t\t\t\t\t\t\tnav_bar_focused = 0;\n\t\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_BACKSPACE:\n\t\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) {\n\t\t\t\t\t\t\t\t\t\t\tnav_bar_backspace_word();\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tnav_bar_backspace();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_CTRL_W:\n\t\t\t\t\t\t\t\t\t\tnav_bar_backspace_word();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\t\t\tnav_bar_focused = 0;\n\t\t\t\t\t\t\t\t\t\tchar * tmp = strdup(nav_bar);\n\t\t\t\t\t\t\t\t\t\tload_directory(tmp, 1);\n\t\t\t\t\t\t\t\t\t\treinitialize_contents();\n\t\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tif (isgraph(ke->event.key)) {\n\t\t\t\t\t\t\t\t\t\t\tnav_bar_insert_char(ke->event.key);\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\t\t\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\t\t\t\t\t\t\t\t\t\tnav_bar_cursor_left(ke->event.modifiers);\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\t\t\t\t\t\t\t\t\t\tnav_bar_cursor_right(ke->event.modifiers);\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\tcase KEY_DEL:\n\t\t\t\t\t\t\t\t\t\t\t\t\tnav_bar_delete();\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\tcase KEY_PAGE_UP:\n\t\t\t\t\t\t\t\t\t_scroll_up();\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_PAGE_DOWN:\n\t\t\t\t\t\t\t\t\t_scroll_down();\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t/* if not focused on anything focusable */\n\t\t\t\t\t\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\t\t\t\t\t\tarrow_select(0,1);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_ARROW_UP:\n\t\t\t\t\t\t\t\t\tarrow_select(0,-1);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\t\t\t\t\t\tarrow_select(-1,0);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\t\t\t\t\t\tarrow_select(1,0);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_BACKSPACE:\n\t\t\t\t\t\t\t\t\tif (!is_desktop_background) {\n\t\t\t\t\t\t\t\t\t\t_menu_action_up(NULL);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\t\t_menu_action_open(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'l':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_CTRL && !is_desktop_background) {\n\t\t\t\t\t\t\t\t\t\tnav_bar_set_focused();\n\t\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'v':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'g':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[3]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'h':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[4]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\t\t\tif (!is_desktop_background) {\n\t\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw_files();\n\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == main_window->wid) {\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_CLIPBOARD:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_clipboard * cb = (void *)m->data;\n\t\t\t\t\t\tchar * selection_text;\n\t\t\t\t\t\tif (*cb->content == '\\002') {\n\t\t\t\t\t\t\tint size = atoi(&cb->content[2]);\n\t\t\t\t\t\t\tFILE * clipboard = yutani_open_clipboard(yctx);\n\t\t\t\t\t\t\tselection_text = malloc(size + 1);\n\t\t\t\t\t\t\tfread(selection_text, 1, size, clipboard);\n\t\t\t\t\t\t\tselection_text[size] = '\\0';\n\t\t\t\t\t\t\tfclose(clipboard);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tselection_text = malloc(cb->size+1);\n\t\t\t\t\t\t\tmemcpy(selection_text, cb->content, cb->size);\n\t\t\t\t\t\t\tselection_text[cb->size] = '\\0';\n\t\t\t\t\t\t}\n\t\t\t\t\t\thandle_clipboard(selection_text);\n\t\t\t\t\t\tfree(selection_text);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid);\n\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t_decor_get_bounds(win, &bounds);\n\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/* Menu bar */\n\t\t\t\t\t\t\tif (menu_bar_height) menu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y);\n\n\t\t\t\t\t\t\tif (nav_bar_height &&\n\t\t\t\t\t\t\t\tme->new_y > (int)(bounds.top_height + menu_bar_height) &&\n\t\t\t\t\t\t\t\tme->new_y < (int)(bounds.top_height + menu_bar_height + nav_bar_height) &&\n\t\t\t\t\t\t\t\tme->new_x > (int)(bounds.left_width) &&\n\t\t\t\t\t\t\t\tme->new_x < (int)(main_window->width - bounds.right_width)) {\n\n\t\t\t\t\t\t\t\tint x = me->new_x - bounds.left_width - 2;\n\t\t\t\t\t\t\t\tif (x >= 0) {\n\t\t\t\t\t\t\t\t\tint i = x / 34;\n\t\t\t\t\t\t\t\t\tif (i < 4) {\n\t\t\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\t\t\t_set_hilight(i, 2);\n\t\t\t\t\t\t\t\t\t\t\tnav_bar_focused = 0;\n\t\t\t\t\t\t\t\t\t\t\t_down_button = i;\n\t\t\t\t\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\t\t\t\t\tif (_down_button != -1 && _down_button == i) {\n\t\t\t\t\t\t\t\t\t\t\t\t_handle_button_press(i);\n\t\t\t\t\t\t\t\t\t\t\t\t_set_hilight(i, 1);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t_down_button = -1;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tif (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT)) {\n\t\t\t\t\t\t\t\t\t\t\t\t_set_hilight(i, 1);\n\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\tif (_down_button == i) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t_set_hilight(i, 2);\n\t\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button != -1) {\n\t\t\t\t\t\t\t\t\t\t\t\t\t_set_hilight(_down_button, 3);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (main_window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) {\n\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t_set_hilight(-1,0);\n\t\t\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\t\t\tnav_bar_set_focused();\n\t\t\t\t\t\t\t\t\t\t\t_figure_out_navbar_cursor(me->new_x, bounds);\n\t\t\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tif (main_window->mouse_state == YUTANI_CURSOR_TYPE_RESET) {\n\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_IBEAM);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (main_window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) {\n\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\tif (nav_bar_focused) {\n\t\t\t\t\t\t\t\t\t\tnav_bar_focused = 0;\n\t\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (_button_hover != -1) {\n\t\t\t\t\t\t\t\t\t_button_hilights[_button_hover] = 3;\n\t\t\t\t\t\t\t\t\t_button_hover = -1;\n\t\t\t\t\t\t\t\t\tredraw = 1; /* Double redraw ??? */\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!is_desktop_background && me->new_y > (int)(main_window->height - bounds.bottom_height - STATUS_HEIGHT)) {\n\n\t\t\t\t\t\t\t} else if (me->new_y > (int)(bounds.top_height + menu_bar_height + nav_bar_height) &&\n\t\t\t\t\t\t\t\tme->new_y < (int)(main_window->height - bounds.bottom_height) &&\n\t\t\t\t\t\t\t\tme->new_x > (int)(bounds.left_width) &&\n\t\t\t\t\t\t\t\tme->new_x < (int)(main_window->width - bounds.right_width) &&\n\t\t\t\t\t\t\t\tme->command != YUTANI_MOUSE_EVENT_LEAVE) {\n\t\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\t\t\t/* Scroll up */\n\t\t\t\t\t\t\t\t\t_scroll_up();\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\t\t\t_scroll_down();\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t/* Get offset into contents */\n\t\t\t\t\t\t\t\tint y_into = me->new_y - bounds.top_height - menu_bar_height - nav_bar_height + scroll_offset;\n\t\t\t\t\t\t\t\tint x_into = me->new_x - bounds.left_width;\n\t\t\t\t\t\t\t\tint offset = (y_into / FILE_HEIGHT) * FILE_PTR_WIDTH + x_into / FILE_WIDTH;\n\t\t\t\t\t\t\t\tif (x_into > FILE_PTR_WIDTH * FILE_WIDTH) {\n\t\t\t\t\t\t\t\t\toffset = -1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (offset != hilighted_offset) {\n\t\t\t\t\t\t\t\t\tint old_offset = hilighted_offset;\n\t\t\t\t\t\t\t\t\thilighted_offset = offset;\n\t\t\t\t\t\t\t\t\tif (old_offset != -1) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tstruct File * f = get_file_at_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\t\tdraw_file(f, old_offset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tstruct File * f = get_file_at_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\t\tdraw_file(f, hilighted_offset);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) {\n\t\t\t\t\t\t\t\t\tstruct File * f = get_file_at_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tif (last_click_offset == hilighted_offset && precise_time_since(last_click) < 400) {\n\t\t\t\t\t\t\t\t\t\t\topen_file(f);\n\t\t\t\t\t\t\t\t\t\t\tlast_click = 0;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tlast_click = precise_current_time();\n\t\t\t\t\t\t\t\t\t\t\tlast_click_offset = hilighted_offset;\n\t\t\t\t\t\t\t\t\t\t\ttoggle_selected(hilighted_offset, me->modifiers);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!(me->modifiers & YUTANI_KEY_MODIFIER_CTRL)) {\n\t\t\t\t\t\t\t\t\t\t\tfor (int i = 0; i < file_pointers_len; ++i) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (file_pointers[i]->selected) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfile_pointers[i]->selected = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\tupdate_status();\n\t\t\t\t\t\t\t\t\t\t\t\t\tclear_offset(i);\n\t\t\t\t\t\t\t\t\t\t\t\t\tdraw_file(file_pointers[i], i);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {\n\t\t\t\t\t\t\t\t\tshow_context_menu(me);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tint old_offset = hilighted_offset;\n\t\t\t\t\t\t\t\thilighted_offset = -1;\n\t\t\t\t\t\t\t\tif (old_offset != -1) {\n\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\tstruct File * f = get_file_at_offset(old_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tdraw_file(f, old_offset);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tredraw = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\n\t\tif (redraw || wallpaper_old) {\n\t\t\tredraw_window();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/find-timezone.c",
    "content": "/**\n * @brief Query a remote API to get timezone information based geoip lookup.\n *\n * We ask @see ip-api.com for geo-ip data, which includes a timezone offset.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/time.h>\n#include <toaru/json.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n\ntypedef struct JSON_Value Value;\n\n#define LOCATION_DATA_PATH \"/tmp/location-data.json\"\n\nint main(int argc, char * argv[]) {\n\t/* See if the location data already exists... */\n\tchar cmdline[1024];\n\tValue * locationData = json_parse_file(LOCATION_DATA_PATH);\n\tif (!locationData) {\n\t\tsprintf(cmdline, \"fetch -o \\\"\" LOCATION_DATA_PATH \"\\\" \\\"http://ip-api.com/json/?fields=lat,lon,city,offset\\\"\");\n\t\tsystem(cmdline);\n\t\tlocationData = json_parse_file(LOCATION_DATA_PATH);\n\t}\n\t/* If we still failed to load it, then bail. */\n\tif (!locationData) {\n\t\treturn 1;\n\t}\n\tdouble offset = JSON_KEY(locationData, \"offset\")->number;\n\n\tprintf(\"%d\\n\", (int)offset);\n\treturn 0;\n}\n\n\n"
  },
  {
    "path": "apps/font-preview.c",
    "content": "/**\n * @brief TrueType font previewer\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n\n/* Pointer to graphics memory */\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\nstatic struct TT_Font * tt_font = NULL;\n\nstatic int decor_left_width = 0;\nstatic int decor_top_height = 0;\nstatic int decor_right_width = 0;\nstatic int decor_bottom_height = 0;\nstatic int decor_width = 0;\nstatic int decor_height = 0;\n\nstatic int width = 640;\nstatic int height = 480;\n\nchar * tt_get_name_string(struct TT_Font * font, int identifier);\nchar * preview_string = \"The quick brown fox jumps over the lazy dog.\";\nchar * tt_font_name = NULL;\nchar window_title[1024] = \"Font Preview\";\n\nvoid redraw(void) {\n\tdraw_fill(ctx, rgb(255,255,255));\n\n\tint y = 10;\n\n\tif (tt_font_name) {\n\t\ttt_set_size(tt_font, 48);\n\t\ty += 48;\n\t\ttt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, tt_font_name, rgb(0,0,0));\n\t\ty += 10;\n\t}\n\n\ttt_set_size(tt_font, 22);\n\ty += 26;\n\ttt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, \"abcdefghijklmnopqrstuvwxyz\", rgb(0,0,0));\n\ty += 26;\n\ttt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", rgb(0,0,0));\n\ty += 26;\n\ttt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, \"0123456789.:,;(*!?')\", rgb(0,0,0));\n\ty += 10;\n\n\tint sizes[] = {7,10,13,16,19,22,25,48,64,92,0};\n\tfor (int * s = sizes; *s; ++s) {\n\t\ttt_set_size(tt_font, *s);\n\t\ty += *s + 4;\n\t\ttt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, preview_string, rgb(0,0,0));\n\t}\n\n\trender_decorations(window, ctx, window_title);\n\n\tflip(ctx);\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\twidth  = w - decor_left_width - decor_right_width;\n\theight = h - decor_top_height - decor_bottom_height;\n\n\tredraw();\n\n\tyutani_window_resize_done(yctx, window);\n\tyutani_flip(yctx, window);\n}\n\nint main(int argc, char * argv[]) {\n\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"usage: %s FONT\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\ttt_font = tt_font_from_file(argv[1]);\n\n\tif (!tt_font) {\n\t\tfprintf(stderr, \"%s: failed to load font\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\tif (argc > 2) {\n\t\tpreview_string = argv[2];\n\t} else {\n\t\tchar * maybe = tt_get_name_string(tt_font, 19);\n\t\tif (maybe) preview_string = maybe;\n\t}\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\twindow = yutani_window_create(yctx, width + decor_width, height + decor_height);\n\tyutani_window_move(yctx, window, 100, 100);\n\n\ttt_font_name = tt_get_name_string(tt_font, 4);\n\n\tif (tt_font_name) {\n\t\tsprintf(window_title, \"%s - Font Preview\", tt_font_name);\n\t}\n\n\tyutani_window_advertise_icon(yctx, window, window_title, \"font\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\tredraw();\n\tyutani_flip(yctx, window);\n\n\tint playing = 1;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t\tyutani_flip(yctx, window);\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win && win == window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\tyutani_flip(yctx, window);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/font-tool.c",
    "content": "/**\n * @file apps/font-tool.c\n * @brief Print information about TrueType fonts.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <getopt.h>\n\n#include <toaru/graphics.h>\n#include <toaru/text.h>\n\nstatic void usage(char * argv[]) {\n\tprintf(\n\t\t\t\"usage: %s [-n] [FONT]\\n\"\n\t\t\t\"Print information about TrueType fonts. If FONT is not specified,\\n\"\n\t\t\t\"the system monospace font will be used.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -n --name       \\033[3mPrint the stored name of the font.\\033[0m\\n\"\n\t\t\t\" -s --strings    \\033[3mPrint all supported entries in the names table.\\033[0m\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\"\n\t\t\t\"\\n\",\n\t\t\targv[0]);\n}\n\n#define SHOW_NAME    (1 << 0)\n#define SHOW_STRINGS (1 << 1)\nextern char * tt_get_name_string(struct TT_Font * font, int identifier);\n\nint main(int argc, char * argv[]) {\n\tstatic struct option long_opts[] = {\n\t\t{\"name\", no_argument, 0, 'n'},\n\t\t{\"help\", no_argument, 0, 'h'},\n\t\t{\"strings\", no_argument, 0, 's'},\n\t\t{0,0,0,0}\n\t};\n\n\tint flags = 0;\n\n\t/* Read some arguments */\n\tint index, c;\n\twhile ((c = getopt_long(argc, argv, \"nhs\", long_opts, &index)) != -1) {\n\t\tif (!c) {\n\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\tc = long_opts[index].val;\n\t\t\t}\n\t\t}\n\t\tswitch (c) {\n\t\t\tcase 'h':\n\t\t\t\tusage(argv);\n\t\t\t\treturn 0;\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tflags |= SHOW_NAME;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tflags |= SHOW_STRINGS;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tstruct TT_Font * my_font;\n\n\tif (optind < argc) {\n\t\tmy_font = tt_font_from_file(argv[optind]);\n\t\tif (!my_font) {\n\t\t\tfprintf(stderr, \"%s: %s: Could not load font.\\n\", argv[0], argv[optind]);\n\t\t\treturn 1;\n\t\t}\n\t} else {\n\t\tmy_font = tt_font_from_shm(\"monospace\");\n\t\tif (!my_font) {\n\t\t\tfprintf(stderr, \"Unknown.\\n\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (flags & SHOW_NAME) {\n\t\tfprintf(stdout, \"%s\\n\", tt_get_name_string(my_font, 4));\n\t}\n\n\tif (flags & SHOW_STRINGS) {\n\t\tstruct IDName {\n\t\t\tint identifier;\n\t\t\tconst char * description;\n\t\t} names[] = {\n\t\t\t{0, \"Copyright\"},\n\t\t\t{1, \"Font family\"},\n\t\t\t{2, \"Font style\"},\n\t\t\t{3, \"Subfamily identification\"},\n\t\t\t{4, \"Full name\"},\n\t\t\t{5, \"Version\"},\n\t\t\t{6, \"PostScript name\"},\n\t\t\t{7, \"Trademark notice\"},\n\t\t\t{8, \"Manufacturer\"},\n\t\t\t{9, \"Designer\"},\n\t\t\t{10, \"Description\"},\n\t\t\t{11, \"Vendor URL\"},\n\t\t\t{12, \"Designer URL\"},\n\t\t\t{13, \"License description\"},\n\t\t\t{14, \"License URL\"},\n\t\t\t/* 15 is reserved */\n\t\t\t{16, \"Preferred family\"},\n\t\t\t{17, \"Preferred subfamily\"},\n\t\t\t{18, \"macOS name\"},\n\t\t\t{19, \"Sample text\"},\n\t\t\t/* Other stuff */\n\t\t};\n\n\t\tfor (size_t i = 0; i < sizeof(names)/sizeof(*names); ++i) {\n\t\t\tchar * value = tt_get_name_string(my_font, names[i].identifier);\n\t\t\tif (value) {\n\t\t\t\tfprintf(stdout, \"%s: %s\\n\", names[i].description, value);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/free.c",
    "content": "/**\n * @brief free - Show free / used / total RAM\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"free - show available memory\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-utk?]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -u     \\033[3mshow used instead of free\\033[0m\\n\"\n\t\t\t\" -t     \\033[3minclude a total\\033[0m\\n\"\n\t\t\t\" -k     \\033[3muse kilobytes instead of megabytes\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\n\nint main(int argc, char * argv[]) {\n\tint show_used = 0;\n\tint use_kilobytes = 0;\n\tint show_total = 0;\n\n\tint c;\n\twhile ((c = getopt(argc, argv, \"utk?\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'u':\n\t\t\t\tshow_used = 1;\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tshow_total = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'k':\n\t\t\t\tuse_kilobytes = 1;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tconst char * unit = \"kB\";\n\n\tFILE * f = fopen(\"/proc/meminfo\", \"r\");\n\tif (!f) return 1;\n\n\tint total, free, used;\n\tchar buf[1024] = {0};\n\tfgets(buf, 1024, f);\n\tchar * a, * b;\n\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\ttotal = atoi(a);\n\n\tfgets(buf, 1024, f);\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\tfree = atoi(a);\n\n\t//fscanf(f, \"MemTotal: %d kB\\nMemFree: %d kB\\n\", &total, &free);\n\tused = total - free;\n\n\tif (!use_kilobytes) {\n\t\tunit = \"MB\";\n\t\tfree /= 1024;\n\t\tused /= 1024;\n\t\ttotal /= 1024;\n\t}\n\n\tif (show_used) {\n\t\tprintf(\"%d %s\", used, unit);\n\t} else {\n\t\tprintf(\"%d %s\", free, unit);\n\t}\n\n\tif (show_total) {\n\t\tprintf(\" / %d %s\", total, unit);\n\t}\n\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/getty.c",
    "content": "/**\n * @brief getty - Manage a TTY.\n *\n * Wraps a serial port (or other dumb connection) with a pty\n * and manages a login for it.\n *\n * This is not really what 'getty' should do - see @ref login-loop.c\n * for something more akin to the \"getty\" on other platforms.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <pty.h>\n#include <sys/wait.h>\n#include <sys/fswait.h>\n\nint main(int argc, char * argv[]) {\n\tint fd_serial;\n\tchar * file = \"/dev/ttyS0\";\n\tchar * user = NULL;\n\n\tif (getuid() != 0) {\n\t\tfprintf(stderr, \"%s: only root can do that\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"a:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'a':\n\t\t\t\tuser = optarg;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\tfile = argv[optind];\n\t\toptind++;\n\t}\n\n\tfd_serial = open(file, O_RDWR);\n\n\tif (fd_serial < 0) {\n\t\tperror(\"open\");\n\t\treturn 1;\n\t}\n\n\tsetsid();\n\tdup2(fd_serial, 0);\n\tdup2(fd_serial, 1);\n\tdup2(fd_serial, 2);\n\tioctl(STDIN_FILENO, TIOCSCTTY, &(int){1});\n\ttcsetpgrp(STDIN_FILENO, getpid());\n\n\tsystem(\"stty sane\");\n\n\tif (optind < argc) {\n\t\tif (*argv[optind] >= '0' && *argv[optind] <= '9' && strlen(argv[optind]) < 30) {\n\t\t\tchar tmp[100];\n\t\t\tsnprintf(tmp, 100, \"stty %s\", argv[optind]);\n\t\t\tsystem(tmp);\n\t\t\toptind++;\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\t/* If there's still arguments, assume TERM value */\n\t\tsetenv(\"TERM\", argv[optind], 1);\n\t\toptind++;\n\t}\n\n\tsystem(\"ttysize -q\");\n\n\tchar * tokens[] = {\"/bin/login\",NULL,NULL,NULL};\n\n\tif (user) {\n\t\ttokens[1] = \"-f\";\n\t\ttokens[2] = user;\n\t}\n\n\texecvp(tokens[0], tokens);\n\texit(1);\n}\n"
  },
  {
    "path": "apps/glogin-provider.c",
    "content": "/**\n * @brief Graphical login display.\n *\n * Called by @ref glogin to show a graphical login prompt.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2015 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n#include <math.h>\n#include <string.h>\n#include <time.h>\n\n#include <sys/utsname.h>\n#include <sys/wait.h>\n#include <sys/time.h>\n\n#include <toaru/graphics.h>\n#include <toaru/kbd.h>\n#include <toaru/yutani.h>\n#include <toaru/auth.h>\n#include <toaru/confreader.h>\n#include <toaru/text.h>\n\n#include <toaru/trace.h>\n#define TRACE_APP_NAME \"glogin-provider\"\n\nstatic sprite_t logo;\n\nstatic gfx_context_t * ctx;\n\nstatic uint16_t win_width;\nstatic uint16_t win_height;\n\nstatic int uid = 0;\n\n#define USERNAME_BOX 1\n#define PASSWORD_BOX 2\n\nstatic int LOGO_FINAL_OFFSET = 100;\nstatic int BOX_WIDTH = 272;\nstatic int BOX_HEIGHT = 104;\nstatic int BOX_ROUNDNESS = 8;\nstatic int CENTER_BOX_X=1;\nstatic int CENTER_BOX_Y=1;\nstatic int BOX_LEFT=-1;\nstatic int BOX_RIGHT=-1;\nstatic int BOX_TOP=-1;\nstatic int BOX_BOTTOM=-1;\nstatic int BOX_COLOR_R=0;\nstatic int BOX_COLOR_G=0;\nstatic int BOX_COLOR_B=0;\nstatic int BOX_COLOR_A=127;\nstatic char * WALLPAPER = \"/usr/share/wallpaper.jpg\";\nstatic char * LOGO = \"/usr/share/logo_login.png\";\nstatic struct TT_Font * tt_font_thin = NULL;\nstatic struct TT_Font * tt_font_bold = NULL;\n\n#define TEXTBOX_INTERIOR_LEFT 4\n#define EXTRA_TEXT_OFFSET 15\n\nint center_x(int x) {\n\treturn (win_width - x) / 2;\n}\n\nint center_y(int y) {\n\treturn (win_height - y) / 2;\n}\n\n#define INPUT_SIZE 1024\nint buffer_put(char * input_buffer, char c) {\n\tint input_collected = strlen(input_buffer);\n\tif (c == 8) {\n\t\t/* Backspace */\n\t\tif (input_collected > 0) {\n\t\t\tinput_collected--;\n\t\t\tinput_buffer[input_collected] = '\\0';\n\t\t}\n\t\treturn 0;\n\t}\n\tif (c < 10 || (c > 10 && c < 32) || c > 126) {\n\t\treturn 0;\n\t}\n\tinput_buffer[input_collected] = c;\n\tinput_collected++;\n\tinput_buffer[input_collected] = '\\0';\n\tif (input_collected == INPUT_SIZE - 1) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstruct text_box {\n\tint x;\n\tint y;\n\tunsigned int width;\n\tunsigned int height;\n\n\tuint32_t text_color;\n\n\tstruct login_container * parent;\n\n\tint is_focused:1;\n\tint is_password:1;\n\n\tunsigned int cursor;\n\tchar * buffer;\n\n\tchar * placeholder;\n};\n\nstruct login_container {\n\tint x;\n\tint y;\n\tunsigned int width;\n\tunsigned int height;\n\n\tstruct text_box * username_box;\n\tstruct text_box * password_box;\n\n\tint show_error:1;\n};\n\nvoid draw_text_box(gfx_context_t * ctx, struct text_box * tb) {\n\tint x = tb->parent->x + tb->x;\n\tint y = tb->parent->y + tb->y;\n\n\tif (tb->is_focused) {\n\t\tdraw_rounded_rectangle(ctx, 1 + x, 1 + y, tb->width - 2, tb->height - 2, 4, rgb(8,193,236));\n\t\tdraw_rounded_rectangle(ctx, 2 + x, 2 + y, tb->width - 4, tb->height - 4, 4, rgb(244,244,244));\n\t} else {\n\t\tdraw_rounded_rectangle(ctx, 1 + x, 1 + y, tb->width - 2, tb->height - 2, 4, rgb(158,169,177));\n\t}\n\t/* Line width 2? */\n\tchar * text = tb->buffer;\n\tchar password_circles[512];\n\tuint32_t color = tb->text_color;\n\n\tif (strlen(tb->buffer) == 0 && !tb->is_focused) {\n\t\ttext = tb->placeholder;\n\t\tcolor = rgba(0,0,0,127);\n\t} else if (tb->is_password) {\n\t\tstrcpy(password_circles, \"\");\n\t\tfor (unsigned int i = 0; i < strlen(tb->buffer); ++i) {\n\t\t\tstrcat(password_circles, \"●\");\n\t\t}\n\t\ttext = password_circles;\n\t}\n\n\ttt_set_size(tt_font_thin, 13);\n\n\tgfx_context_t * clipped = init_graphics_subregion(ctx, x + 2, y + 2, tb->width - 4, tb->height - 4);\n\ttt_draw_string(clipped, tt_font_thin, 2, 13, text, color);\n\n\tif (tb->is_focused) {\n\t\tint width = tt_string_width(tt_font_thin, text);\n\t\tdraw_line(clipped, width + 2, width + 2, 0, tb->height - 4, tb->text_color);\n\t}\n\n\tfree(clipped);\n\n}\n\nvoid draw_login_container(gfx_context_t * ctx, struct login_container * lc) {\n\n\n\t/* Draw rounded rectangle */\n\tdraw_rounded_rectangle(ctx, lc->x, lc->y, lc->width, lc->height, BOX_ROUNDNESS, rgba(BOX_COLOR_R,BOX_COLOR_G,BOX_COLOR_B,BOX_COLOR_A));\n\n\t/* Draw labels */\n\tif (lc->show_error) {\n\t\tchar * error_message = \"Incorrect username or password.\";\n\t\ttt_set_size(tt_font_thin, 13);\n\t\ttt_draw_string(ctx, tt_font_thin, lc->x + (lc->width - tt_string_width(tt_font_thin, error_message)) / 2, lc->y + 6 + EXTRA_TEXT_OFFSET - 1, error_message, rgb(240,20,20));\n\t}\n\n\tdraw_text_box(ctx, lc->username_box);\n\tdraw_text_box(ctx, lc->password_box);\n\n}\n\n/**\n * Get hostname information updated with the current time.\n *\n * @param hostname\n */\nstatic void get_updated_hostname_with_time_info(char hostname[]) {\n\t// get hostname\n\tchar _hostname[256];\n\tgethostname(_hostname, 255);\n\n\t// get current time\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL); //time(NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\t// format the hostname info\n\tchar _date[256];\n\tstrftime(_date, 256, \"%a %B %d %Y\", timeinfo);\n\tsprintf(hostname, \"%s // %s\", _hostname, _date);\n}\n\nint main (int argc, char ** argv) {\n\tif (getuid() != 0) {\n\t\treturn 1;\n\t}\n\n\tfprintf(stdout, \"Hello\\n\");\n\n\tyutani_t * y = yutani_init();\n\n\tif (!y) {\n\t\tfprintf(stderr, \"[glogin] Connection to server failed.\\n\");\n\t\treturn 1;\n\t}\n\n\t/* Load config */\n\t{\n\t\tconfreader_t * conf = confreader_load(\"/etc/glogin.conf\");\n\n\t\tif (conf) {\n\n\t\t\tLOGO_FINAL_OFFSET = confreader_intd(conf, \"style\", \"logo_padding\", LOGO_FINAL_OFFSET);\n\t\t\tBOX_WIDTH = confreader_intd(conf, \"style\", \"box_width\", BOX_WIDTH);\n\t\t\tBOX_HEIGHT = confreader_intd(conf, \"style\", \"box_height\", BOX_HEIGHT);\n\t\t\tBOX_ROUNDNESS = confreader_intd(conf, \"style\", \"box_roundness\", BOX_ROUNDNESS);\n\t\t\tCENTER_BOX_X = confreader_intd(conf, \"style\", \"center_box_x\", CENTER_BOX_X);\n\t\t\tCENTER_BOX_Y = confreader_intd(conf, \"style\", \"center_box_y\", CENTER_BOX_Y);\n\t\t\tBOX_LEFT = confreader_intd(conf, \"style\", \"box_left\", BOX_LEFT);\n\t\t\tBOX_RIGHT = confreader_intd(conf, \"style\", \"box_right\", BOX_RIGHT);\n\t\t\tBOX_TOP = confreader_intd(conf, \"style\", \"box_top\", BOX_TOP);\n\t\t\tBOX_BOTTOM = confreader_intd(conf, \"style\", \"box_bottom\", BOX_BOTTOM);\n\t\t\tBOX_COLOR_R = confreader_intd(conf, \"style\", \"box_color_r\", BOX_COLOR_R);\n\t\t\tBOX_COLOR_G = confreader_intd(conf, \"style\", \"box_color_g\", BOX_COLOR_G);\n\t\t\tBOX_COLOR_B = confreader_intd(conf, \"style\", \"box_color_b\", BOX_COLOR_B);\n\t\t\tBOX_COLOR_A = confreader_intd(conf, \"style\", \"box_color_a\", BOX_COLOR_A);\n\n\t\t\tWALLPAPER = confreader_getd(conf, \"image\", \"wallpaper\", WALLPAPER);\n\t\t\tLOGO = confreader_getd(conf, \"image\", \"logo\", LOGO);\n\n\t\t\tconfreader_free(conf);\n\t\t}\n\n\t\tTRACE(\"Loading complete\");\n\t}\n\n\tTRACE(\"Loading logo...\");\n\tload_sprite(&logo, LOGO);\n\tTRACE(\"... done.\");\n\n\t/* Generate surface for background */\n\tsprite_t * bg_sprite;\n\n\tint width  = y->display_width;\n\tint height = y->display_height;\n\tchar * bg_cache = NULL;\n\n\t/* Do something with a window */\n\tTRACE(\"Connecting to window server...\");\n\tyutani_window_t * wina = yutani_window_create_flags(y, width, height,\n\t\t\tYUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG);\n\tassert(wina);\n\tctx = init_graphics_yutani_double_buffer(wina);\n\tdraw_fill(ctx, rgba(0,0,0,255));\n\tTRACE(\"... done.\");\n\n\ttt_font_thin = tt_font_from_shm(\"sans-serif\");\n\ttt_font_bold = tt_font_from_shm(\"sans-serif.bold\");\n\nredo_everything:\n\twin_width = width;\n\twin_height = height;\n\n\tTRACE(\"Loading wallpaper...\");\n\t{\n\t\tsprite_t * wallpaper = malloc(sizeof(sprite_t));\n\t\tload_sprite(wallpaper, WALLPAPER);\n\n\t\tfloat x = (float)width  / (float)wallpaper->width;\n\t\tfloat y = (float)height / (float)wallpaper->height;\n\n\t\tint nh = (int)(x * (float)wallpaper->height);\n\t\tint nw = (int)(y * (float)wallpaper->width);;\n\n\t\tbg_sprite = create_sprite(width, height, ALPHA_OPAQUE);\n\t\tgfx_context_t * bg = init_graphics_sprite(bg_sprite);\n\n\t\tif (nw > width) {\n\t\t\tdraw_sprite_scaled(bg, wallpaper, (width - nw) / 2, 0, nw, height);\n\t\t} else {\n\t\t\tdraw_sprite_scaled(bg, wallpaper, 0, (height - nh) / 2, width, nh);\n\t\t}\n\n\t\t/* Three box blurs = good enough approximation of a guassian, but faster*/\n\t\tblur_context_box(bg, 20);\n\t\tblur_context_box(bg, 20);\n\t\tblur_context_box(bg, 20);\n\n\t\tfree(bg);\n\t\tfree(wallpaper);\n\t}\n\tTRACE(\"... done.\");\n\n\tdraw_fill(ctx, rgb(0,0,0));\n\tdraw_sprite(ctx, bg_sprite, center_x(width), center_y(height));\n\n\tbg_cache = malloc(sizeof(uint32_t) * width * height);\n\tmemcpy(bg_cache, ctx->backbuffer, sizeof(uint32_t) * width * height);\n\n\twhile (1) {\n\n\t\t#if 0\n\t\tflip(ctx);\n\t\tyutani_flip(y, wina);\n\t\t#endif\n\n\t\t//yutani_set_stack(y, wina, 0);\n\t\tyutani_focus_window(y, wina->wid);\n\n\t\tchar username[INPUT_SIZE] = {0};\n\t\tchar password[INPUT_SIZE] = {0};\n\t\tchar hostname[512];\n\n\t\t// we do it here to calculate the final string position\n\t\tget_updated_hostname_with_time_info(hostname);\n\n\t\tchar kernel_v[512];\n\n\t\t{\n\t\t\tstruct utsname u;\n\t\t\tuname(&u);\n\t\t\tchar * os_name_ = \"ToaruOS\";\n\t\t\tsnprintf(kernel_v, 512, \"%s %s\", os_name_, u.release);\n\t\t}\n\n\t\tuid = 0;\n\n\t\tint box_x, box_y;\n\n\t\tif (CENTER_BOX_X) {\n\t\t\tbox_x = center_x(BOX_WIDTH);\n\t\t} else if (BOX_LEFT == -1) {\n\t\t\tbox_x = win_width - BOX_RIGHT - BOX_WIDTH;\n\t\t} else {\n\t\t\tbox_x = BOX_LEFT;\n\t\t}\n\t\tif (CENTER_BOX_Y) {\n\t\t\tbox_y = center_y(0) + 8;\n\t\t} else if (BOX_TOP == -1) {\n\t\t\tbox_y = win_width - BOX_BOTTOM - BOX_HEIGHT;\n\t\t} else {\n\t\t\tbox_y = BOX_TOP;\n\t\t}\n\n\t\tint focus = 0;\n\n\t\ttt_set_size(tt_font_bold, 12);\n\t\tint hostname_label_left = width - 10 - tt_string_width(tt_font_bold, hostname);\n\t\tint kernel_v_label_left = 10;\n\n\t\tstruct text_box username_box = { (BOX_WIDTH - 170) / 2, 30, 170, 20, rgb(0,0,0), NULL, 0, 0, 0, username, \"Username\" };\n\t\tstruct text_box password_box = { (BOX_WIDTH - 170) / 2, 58, 170, 20, rgb(0,0,0), NULL, 0, 1, 0, password, \"Password\" };\n\n\t\tstruct login_container lc = { box_x, box_y, BOX_WIDTH, BOX_HEIGHT, &username_box, &password_box, 0 };\n\n\t\tusername_box.parent = &lc;\n\t\tpassword_box.parent = &lc;\n\n\t\twhile (1) {\n\t\t\tfocus = 0;\n\t\t\tmemset(username, 0x0, INPUT_SIZE);\n\t\t\tmemset(password, 0x0, INPUT_SIZE);\n\n\t\t\twhile (1) {\n\n\t\t\t\t// update time info\n\t\t\t\tget_updated_hostname_with_time_info(hostname);\n\n\t\t\t\tmemcpy(ctx->backbuffer, bg_cache, sizeof(uint32_t) * width * height);\n\t\t\t\tdraw_sprite(ctx, &logo, center_x(logo.width), center_y(logo.height) - LOGO_FINAL_OFFSET);\n\n\t\t\t\ttt_draw_string_shadow(ctx, tt_font_bold, hostname, 12, hostname_label_left, height - 22, rgb(255,255,255), rgb(0,0,0), 4);\n\t\t\t\ttt_draw_string_shadow(ctx, tt_font_bold, kernel_v, 12, kernel_v_label_left, height - 22, rgb(255,255,255), rgb(0,0,0), 4);\n\n\t\t\t\tif (focus == USERNAME_BOX) {\n\t\t\t\t\tusername_box.is_focused = 1;\n\t\t\t\t\tpassword_box.is_focused = 0;\n\t\t\t\t} else if (focus == PASSWORD_BOX) {\n\t\t\t\t\tusername_box.is_focused = 0;\n\t\t\t\t\tpassword_box.is_focused = 1;\n\t\t\t\t} else {\n\t\t\t\t\tusername_box.is_focused = 0;\n\t\t\t\t\tpassword_box.is_focused = 0;\n\t\t\t\t}\n\n\t\t\t\tdraw_login_container(ctx, &lc);\n\n\t\t\t\tflip(ctx);\n\t\t\t\tyutani_flip(y, wina);\n\n\t\t\t\tstruct yutani_msg_key_event kbd;\n\t\t\t\tstruct yutani_msg_window_mouse_event mou;\n\t\t\t\tint msg_type = 0;\ncollect_events:\n\t\t\t\tdo {\n\t\t\t\t\tyutani_msg_t * msg = yutani_poll(y);\n\t\t\t\t\tswitch (msg->type) {\n\t\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)msg->data;\n\t\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\t\t\t\tmemcpy(&kbd, ke, sizeof(struct yutani_msg_key_event));\n\t\t\t\t\t\t\t\t\tmsg_type = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)msg->data;\n\t\t\t\t\t\t\t\tmemcpy(&mou, me, sizeof(struct yutani_msg_mouse_event));\n\t\t\t\t\t\t\t\tmsg_type = 2;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_WELCOME:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_welcome * mw = (void*)msg->data;\n\t\t\t\t\t\t\t\tyutani_window_resize(y, wina, mw->display_width, mw->display_height);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)msg->data;\n\t\t\t\t\t\t\t\twidth = wr->width;\n\t\t\t\t\t\t\t\theight = wr->height;\n\t\t\t\t\t\t\t\tyutani_window_resize_accept(y, wina, width, height);\n\t\t\t\t\t\t\t\treinit_graphics_yutani(ctx, wina);\n\t\t\t\t\t\t\t\tyutani_window_resize_done(y, wina);\n\t\t\t\t\t\t\t\tsprite_free(bg_sprite);\n\t\t\t\t\t\t\t\tfree(bg_cache);\n\n\t\t\t\t\t\t\t\tgoto redo_everything;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tfree(msg);\n\t\t\t\t} while (!msg_type);\n\n\t\t\t\tif (msg_type == 1) {\n\n\t\t\t\t\tif (kbd.event.keycode == '\\n') {\n\t\t\t\t\t\tif (focus == USERNAME_BOX) {\n\t\t\t\t\t\t\tfocus = PASSWORD_BOX;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t} else if (focus == PASSWORD_BOX) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfocus = USERNAME_BOX;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (kbd.event.keycode == '\\t') {\n\t\t\t\t\t\tif (focus == USERNAME_BOX) {\n\t\t\t\t\t\t\tfocus = PASSWORD_BOX;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfocus = USERNAME_BOX;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (kbd.event.key) {\n\n\t\t\t\t\t\tif (!focus) {\n\t\t\t\t\t\t\tfocus = USERNAME_BOX;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (focus == USERNAME_BOX) {\n\t\t\t\t\t\t\tbuffer_put(username, kbd.event.key);\n\t\t\t\t\t\t} else if (focus == PASSWORD_BOX) {\n\t\t\t\t\t\t\tbuffer_put(password, kbd.event.key);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\n\t\t\t\t} else if (msg_type == 2) {\n\n\t\t\t\t\tif ((mou.command == YUTANI_MOUSE_EVENT_DOWN\n\t\t\t\t\t     && mou.buttons & YUTANI_MOUSE_BUTTON_LEFT)\n\t\t\t\t\t    || (mou.command == YUTANI_MOUSE_EVENT_CLICK)) {\n\t\t\t\t\t\t/* Determine if we were inside of a text box */\n\n\t\t\t\t\t\tif (mou.new_x >= (int)lc.x + (int)username_box.x &&\n\t\t\t\t\t\t    mou.new_x <= (int)lc.x + (int)username_box.x + (int)username_box.width &&\n\t\t\t\t\t\t    mou.new_y >= (int)lc.y + (int)username_box.y &&\n\t\t\t\t\t\t    mou.new_y <= (int)lc.y + (int)username_box.y + (int)username_box.height) {\n\t\t\t\t\t\t\t/* Ensure this box is focused. */\n\t\t\t\t\t\t\tfocus = USERNAME_BOX;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t    (int)mou.new_x >= (int)lc.x + (int)password_box.x &&\n\t\t\t\t\t\t    (int)mou.new_x <= (int)lc.x + (int)password_box.x + (int)password_box.width &&\n\t\t\t\t\t\t    (int)mou.new_y >= (int)lc.y + (int)password_box.y &&\n\t\t\t\t\t\t    (int)mou.new_y <= (int)lc.y + (int)password_box.y + (int)password_box.height) {\n\t\t\t\t\t\t\t/* Ensure this box is focused. */\n\t\t\t\t\t\t\tfocus = PASSWORD_BOX;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfocus = 0;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tgoto collect_events;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tfprintf(stdout, \"USER %s\\n\", username);\n\t\t\tfprintf(stdout, \"PASS %s\\n\", password);\n\t\t\tfprintf(stdout, \"AUTH\\n\");\n\n\t\t\tchar tmp[1024];\n\t\t\tfgets(tmp, 1024, stdin);\n\t\t\tif (!strcmp(tmp,\"FAIL\\n\")) {\n\t\t\t\tlc.show_error = 1;\n\t\t\t\tcontinue;\n\t\t\t} else if (!strcmp(tmp,\"SUCC\\n\")) {\n\t\t\t\tfprintf(stderr,\"Success!\\n\");\n\t\t\t\tgoto _success;\n\t\t\t}\n\t\t}\n\t}\n\n_success:\n\tyutani_close(y, wina);\n\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/glogin.c",
    "content": "/**\n * @brief Graphical login daemon.\n *\n * Launches graphical login windows and manages login sessions.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2015 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n#include <math.h>\n#include <string.h>\n#include <time.h>\n\n#include <sys/wait.h>\n\n#include <toaru/yutani.h>\n#include <toaru/auth.h>\n#include <toaru/trace.h>\n#define TRACE_APP_NAME \"glogin\"\n\nint main (int argc, char ** argv) {\n\tif (getuid() != 0) {\n\t\treturn 1;\n\t}\n\n\t/* Ensure a somewhat sane environment going in */\n\tTRACE(\"Graphical login starting.\");\n\tyutani_init();\n\n\tsetenv(\"USER\", \"root\", 1);\n\tsetenv(\"HOME\", \"/\", 1);\n\tsetenv(\"SHELL\", \"/bin/sh\", 1);\n\tsetenv(\"PATH\", \"/usr/bin:/bin\", 0);\n\tsetenv(\"WM_THEME\", \"fancy\", 0);\n\n\twhile (1) {\n\n\t\tchar * username = NULL;\n\t\tchar * password = NULL;\n\t\tint uid = -1;\n\n\t\tint com_pipe[2], rep_pipe[2];\n\t\tpipe(com_pipe);\n\t\tpipe(rep_pipe);\n\t\tTRACE(\"Starting login client...\");\n\n\t\tpid_t _gui_login = fork();\n\t\tif (!_gui_login) {\n\t\t\tdup2(com_pipe[1], STDOUT_FILENO);\n\t\t\tdup2(rep_pipe[0], STDIN_FILENO);\n\t\t\tclose(com_pipe[0]);\n\t\t\tclose(rep_pipe[1]);\n\t\t\tTRACE(\"In client...\");\n\t\t\tchar * args[] = {\"/bin/glogin-provider\", NULL};\n\t\t\texecvp(args[0], args);\n\t\t\tTRACE(\"Exec failure?\");\n\t\t\texit(1);\n\t\t}\n\n\t\tclose(com_pipe[1]);\n\t\tclose(rep_pipe[0]);\n\n\t\tFILE * com = fdopen(com_pipe[0], \"r\");\n\t\tFILE * rep = fdopen(rep_pipe[1], \"w\");\n\n\t\twhile (!feof(com)) {\n\t\t\tchar buf[1024]; /* line length? */\n\t\t\tchar * cmd = fgets(buf, sizeof(buf), com);\n\t\t\tsize_t r = strlen(cmd);\n\t\t\tif (cmd && r) {\n\t\t\t\tif (cmd[r-1] == '\\n') {\n\t\t\t\t\tcmd[r-1] = '\\0';\n\t\t\t\t\tr--;\n\t\t\t\t}\n\t\t\t\tif (!strcmp(buf,\"RESTART\")) {\n\t\t\t\t\tTRACE(\"Client requested system restart, rebooting.\");\n\t\t\t\t\tsystem(\"reboot\");\n\t\t\t\t} else if (!strcmp(buf,\"Hello\")) {\n\t\t\t\t\tTRACE(\"Hello received from client.\");\n\t\t\t\t} else if (!strncmp(buf,\"USER \",5)) {\n\t\t\t\t\tTRACE(\"Username received.\");\n\t\t\t\t\tif (username) free(username);\n\t\t\t\t\tusername = strdup(buf + 5);\n\t\t\t\t} else if (!strncmp(buf,\"PASS \",5)) {\n\t\t\t\t\tTRACE(\"Password received.\");\n\t\t\t\t\tif (password) free(password);\n\t\t\t\t\tpassword = strdup(buf + 5);\n\t\t\t\t} else if (!strcmp(buf,\"AUTH\")) {\n\t\t\t\t\tTRACE(\"Perform auth request, client wants answer.\");\n\t\t\t\t\tif (!username || !password) {\n\t\t\t\t\t\tfprintf(rep, \"FAIL\\n\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\tuid = toaru_auth_check_pass(username, password);\n\t\t\t\t\t\tif (uid < 0) {\n\t\t\t\t\t\t\tfprintf(rep, \"FAIL\\n\");\n\t\t\t\t\t\t\tfflush(rep);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tfprintf(rep, \"SUCC\\n\");\n\t\t\t\t\t\t\tfflush(rep);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\twaitpid(_gui_login, NULL, 0);\n\n\t\tif (uid == -1) {\n\t\t\tTRACE(\"Not a valid session, returning login manager...\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tTRACE(\"Starting session...\");\n\n\t\tpid_t _session_pid = fork();\n\t\tif (!_session_pid) {\n\t\t\ttoaru_set_credentials(uid);\n\t\t\tchar * args[] = {\"/bin/session\", NULL};\n\t\t\texecvp(args[0], args);\n\t\t\texit(1);\n\t\t}\n\n\t\twaitpid(_session_pid, NULL, 0);\n\t\tTRACE(\"Session ended.\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/grep.c",
    "content": "/**\n * @brief grep - almost acceptable grep\n *\n * Based on the regex search matcher in bim.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022-2026 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <string.h>\n#include <getopt.h>\n#include <ctype.h>\n#include <errno.h>\n#include <libgen.h>\n\nstatic int invert = 0;\nstatic int ignorecase = 0;\nstatic int quiet = 0;\nstatic int only_matching = 0;\nstatic int counts = 0;\nstatic int is_fgrep = 0;\nstatic int whole_lines = 0;\nstatic int list_files = 0;\nstatic int line_numbers = 0;\nstatic int use_color = 0;\nstatic int suppress_errors = 0;\n\n/**\n * This regex engine is adapted from bim.\n *\n * It has been modified to be more like POSIX BREs, but it's still pretty bad.\n *\n * While we can support * matches for square brackets, we don't support them\n * for groups. We don't support character classes (colons), repetition ranges,\n * and definitely not backrefs.\n */\nstruct MatchQualifier {\n\tint (*matchFunc)(struct MatchQualifier*,char,int);\n\tunion {\n\t\tchar matchChar;\n\t\tstruct {\n\t\t\tchar * start;\n\t\t\tchar * end;\n\t\t} matchSquares;\n\t};\n};\n\ntypedef enum {\n\tMATCH_OP_EQ,\n\tMATCH_OP_GE,\n\tMATCH_OP_LE,\n} match_op;\n\n/**\n * Helper for handling smart case sensitivity.\n */\nstatic inline int match_char_internal(char a, char b, int ignorecase, match_op op) {\n\tif (ignorecase) {\n\t\ta = tolower(a);\n\t\tb = tolower(b);\n\t}\n\tswitch (op) {\n\t\t/* Equality */\n\t\tcase MATCH_OP_EQ: return a == b;\n\t\tcase MATCH_OP_GE: return a >= b;\n\t\tcase MATCH_OP_LE: return a <= b;\n\t}\n\treturn 0;\n}\n\n/*\n * Basic single-character matcher.\n */\nint match_char(struct MatchQualifier * self, char b, int mode) {\n\treturn match_char_internal(self->matchChar, b, mode, MATCH_OP_EQ);\n}\n\n/*\n * Match collections of characters.\n */\nint match_squares(struct MatchQualifier * self, char c, int mode) {\n\tchar * start = self->matchSquares.start;\n\tchar * end = self->matchSquares.end;\n\tchar * t = start;\n\tint good = 1;\n\tif (*t == '^') { t++; good = 0; }\n\twhile (t != end) {\n\t\tchar test = *t++;\n\t\tif (test == '\\\\' && *t && strchr(\"\\\\]\",*t)) {\n\t\t\ttest = *t++;\n\t\t} else if (test == '\\\\' && *t == 't') {\n\t\t\ttest = '\\t'; t++;\n\t\t}\n\n\t\tif (*t == '-') {\n\t\t\tt++;\n\t\t\tif (t == end) return 0;\n\t\t\tchar right = *t++;\n\t\t\tif (right == '\\\\' && *t && strchr(\"\\\\]\",*t)) {\n\t\t\t\tright = *t++;\n\t\t\t} else if (right == '\\\\' && *t == 't') {\n\t\t\t\tright = '\\t'; t++;\n\t\t\t}\n\t\t\tif (match_char_internal(c,test,mode,MATCH_OP_GE) && match_char_internal(c,right,mode,MATCH_OP_LE)) return good;\n\t\t} else {\n\t\t\tif (match_char_internal(c,test,mode,MATCH_OP_EQ)) return good;\n\t\t}\n\t}\n\treturn !good;\n}\n\n/*\n * Match any single character.\n */\nint match_dot(struct MatchQualifier * self, char c, int mode) {\n\treturn 1;\n}\n\nstruct Line {\n\tint actual;\n\tchar * text;\n};\n\nint regex_matches(struct Line * line, int j, char * needle, int ignorecase, int *len, char **needleout) {\n\tint k = j;\n\tchar * match = needle;\n\tif (*match == '^') {\n\t\tif (j != 0) return 0;\n\t\tmatch++;\n\t}\n\twhile (k < line->actual + 1) {\n\t\tif (needleout && *match == ')') {\n\t\t\t*needleout = match + 1;\n\t\t\tif (len) *len = k - j;\n\t\t\treturn 1;\n\t\t}\n\t\tif (*match == '\\0') {\n\t\t\tif (needleout) return 0;\n\t\t\tif (len) *len = k - j;\n\t\t\treturn 1;\n\t\t}\n\t\tif (*match == '$') {\n\t\t\tif (k != line->actual) return 0;\n\t\t\tmatch++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tstruct MatchQualifier matcher = {match_char, .matchChar=*match};\n\t\tif (*match == '.') {\n\t\t\tmatcher.matchFunc = match_dot;\n\t\t\tmatch++;\n\t\t} else if (*match == '\\\\' && strchr(\"$^/\\\\.[]*()\",match[1]) != NULL) {\n\t\t\tmatcher.matchChar = match[1];\n\t\t\tmatch += 2;\n\t\t} else if (*match == '\\\\' && match[1] == 't') {\n\t\t\tmatcher.matchChar = '\\t';\n\t\t\tmatch += 2;\n\t\t} else if (*match == '[') {\n\t\t\tchar * s = match+1;\n\t\t\tchar * e = s;\n\t\t\twhile (*e && *e != ']') {\n\t\t\t\tif (*e == '\\\\' && e[1] == ']') e++;\n\t\t\t\te++;\n\t\t\t}\n\t\t\tif (!*e) break; /* fail match on unterminated [] sequence */\n\t\t\tmatch = e + 1;\n\t\t\tmatcher.matchFunc = match_squares;\n\t\t\tmatcher.matchSquares.start = s;\n\t\t\tmatcher.matchSquares.end = e;\n\t\t} else if (*match == '(') {\n\t\t\tmatch++;\n\t\t\tint _len;\n\t\t\tchar * newmatch;\n\t\t\tif (!regex_matches(line, k, match, ignorecase, &_len, &newmatch)) break;\n\t\t\tmatch = newmatch;\n\t\t\tk += _len;\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tmatch++;\n\t\t}\n\t\tif (*match == '\\\\' && match[1] == '?') {\n\t\t\t/* Optional */\n\t\t\tmatch += 2;\n\t\t\tif (matcher.matchFunc(&matcher, line->text[k], ignorecase)) {\n\t\t\t\tint _len;\n\t\t\t\tif (regex_matches(line,k+1,match,ignorecase,&_len, needleout)) {\n\t\t\t\t\tif (len) *len = _len + k + 1 - j;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue;\n\t\t} else if ((*match == '\\\\' && match[1] == '+') || *match == '*') {\n\t\t\tif (*match == '\\\\' /* && match[1] == '+' */) {\n\t\t\t\t/* Must match at least one */\n\t\t\t\tmatch += 2;\n\t\t\t\tif (!matcher.matchFunc(&matcher, line->text[k], ignorecase)) break;\n\t\t\t\tk++;\n\t\t\t} else {\n\t\t\t\tmatch++;\n\t\t\t}\n\n\t\t\tint _j = k;\n\t\t\twhile (_j < line->actual + 1) {\n\t\t\t\tif (_j < line->actual && !matcher.matchFunc(&matcher, line->text[_j], ignorecase)) break;\n\t\t\t\t_j++;\n\t\t\t}\n\t\t\twhile (_j >= k) {\n\t\t\t\tint _len;\n\t\t\t\tif (regex_matches(line, _j, match, ignorecase, &_len, needleout)) {\n\t\t\t\t\tif (len) *len = _len + _j - j;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\t_j--;\n\t\t\t}\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tif (!matcher.matchFunc(&matcher, line->text[k], ignorecase)) break;\n\t\t\tk++;\n\t\t}\n\t}\n\treturn 0;\n}\n\n/**\n * Determine if 'line' matches a particular pattern 'needle' using the current\n * global mode settings (is_fgrep, ignorecase), starting from a given line, and\n * store the resulting length of a match in 'len'.\n */\nstatic int subsearch_matches(struct Line * line, int j, char * needle, int *len) {\n\tif (is_fgrep) {\n\t\t/* Does 'line' starting at 'j' match 'needle' */\n\t\tconst char *n = needle;\n\t\tfor (; *n; ++n, ++j) if (j >= line->actual || !match_char_internal(*n, line->text[j], ignorecase, MATCH_OP_EQ)) return 0;\n\t\t*len = n - needle;\n\t\treturn 1;\n\t}\n\treturn regex_matches(line, j, needle, ignorecase, len, NULL);\n}\n\nint usage(char ** argv) {\n#define _I \"\\033[3m\"\n#define _E \"\\033[0m\\n\"\n\tfprintf(stderr, \"usage: %s [-cilnoqsvxF] PATTERN [FILE...]\\n\"\n\t\t\"\\n\"\n\t\t\"Search for PATTERN in each file.\\n\"\n\t\t\"Take care that this grep's pattern engine is limited and not POSIX-compliant.\\n\"\n\t\t\"\\n\"\n\t\t\" Supported options:\\n\"\n\t\t\"  -c      \" _I \"Instead of printing matches, print counts of matched lines.\" _E\n\t\t\"  -i      \" _I \"Ignore case in input and pattern.\" _E\n\t\t\"  -l      \" _I \"Instead of printing matches, print the names of files that.\\n\"\n\t\t\"          match. Takes precedence over most other options.\" _E\n\t\t\"  -n      \" _I \"Prefix each printed match with the line number it appeared on.\" _E\n\t\t\"  -o      \" _I \"Print only the matching parts of each line, separating\\n\"\n\t\t\"          each match with a line feed.\" _E\n\t\t\"  -q      \" _I \"Exit immediately with 0 when a match (or, with -v,\\n\"\n\t\t\"          non-match) is found; do not print matches.\" _E\n\t\t\"  -s      \" _I \"Suppress the output of errors that would normally go to stderr.\" _E\n\t\t\"  -v      \" _I \"Invert match - print lines that do not match pattern.\" _E\n\t\t\"  -x      \" _I \"PATTERN must match a whole line.\" _E\n\t\t\"  -F      \" _I \"Treat PATTERN as a fixed string (acts as 'fgrep').\" _E\n\t\t\"  --help  \" _I \"Show this help message.\" _E\n\t\t\"  --color \" _I \"Wrap matches with escapes to highlight them in red.\\n\"\n\t\t\"          The use of color may be controlled as follows:\" _E\n\t\t\"  --color=auto   \" _I \"When the output is a TTY. Same as above.\" _E\n\t\t\"  --color=never  \" _I \"Never. Overrides a previous option.\" _E\n\t\t\"  --color-always \" _I \"Regardless of whether the output is a TTY.\" _E\n\t\t\"\\n\"\n\t\t\" Supported regex syntax:\\n\"\n\t\t\"  [abc]   \" _I \"Match one of a set of characters.\" _E\n\t\t\"  [a-z]   \" _I \"Match one from a range of characters.\" _E\n\t\t\"  (abc)   \" _I \"Match a group.\\n\"\n\t\t\"          This implementation does not support repeating a group match,\\n\"\n\t\t\"          so groups do not really do anything.\" _E\n\t\t\"  .       \" _I \"Match any single character.\" _E\n\t\t\"  ^       \" _I \"Match the start of the line.\" _E\n\t\t\"  $       \" _I \"Match the end of the line.\" _E\n\t\t\"\\n\"\n\t\t\" Modifiers (can be combined with [], ., and single characters):\\n\"\n\t\t\"  *       \" _I \"Match any number of occurances\" _E\n\t\t\"  \\\\?      \" _I \"Match zero or one time\" _E\n\t\t\"  \\\\+      \" _I \"Match at least one occurance\" _E\n\t\t\"\\n\"\n\t\t\" Some characters can be escaped in the pattern with \\\\.\\n\"\n\t\t\" The regex engine is not Unicode-aware.\\n\",\n\t\targv[0]);\n#undef _I\n#undef _E\n\treturn 1;\n}\n\n/*\n * POSIX says \"The basename() function may modify the string pointed to by path\",\n * and ours definitely does in order to handle trailing slashes. We don't want that,\n * and we're probably not going to be dealing with trailing slashes because 'path'\n * here is our argv[0] which should name a binary and trailing slashes would be\n * wrong there. This \"simple_basename\" doesn't muck things up.\n */\nstatic char * simple_basename(char * path) {\n\tchar * s = path;\n\tchar * c = path;\n\tdo {\n\t\twhile (*s == '/') {\n\t\t\ts++;\n\t\t\tif (!*s) return c; /* Ends in trailing slashes, shouldn't happen... */\n\t\t}\n\t\tc = s;\n\t\ts = strchr(c,'/');\n\t} while (s);\n\treturn c;\n}\n\nint main(int argc, char ** argv) {\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?hivqocFxlns-:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'h':\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'i':\n\t\t\t\tignorecase = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tinvert = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tquiet = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\tonly_matching = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tcounts = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'F':\n\t\t\t\tis_fgrep = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'x':\n\t\t\t\twhole_lines = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tlist_files = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tline_numbers = 1;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tsuppress_errors = 1;\n\t\t\t\tbreak;\n\t\t\tcase '-':\n\t\t\t\tif (!strcmp(optarg,\"help\")) {\n\t\t\t\t\treturn usage(argv);\n\t\t\t\t} else if (!strcmp(optarg,\"color=never\")) {\n\t\t\t\t\tuse_color = 0; /* Overrides previous instances of conflicting option */\n\t\t\t\t} else if (!strcmp(optarg,\"color\") || !strcmp(optarg,\"-color=auto\")) {\n\t\t\t\t\tuse_color = 1; /* treat 1 as maybe */\n\t\t\t\t} else if (!strcmp(optarg,\"color=always\")) {\n\t\t\t\t\tuse_color = 2;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Detect if we were invoke as 'fgrep'. */\n\tif (!strcmp(simple_basename(argv[0]),\"fgrep\")) {\n\t\tis_fgrep = 1;\n\t}\n\n\t/* Require at least a PATTERN argument. */\n\tif (optind == argc) return usage(argv);\n\tchar * needle = argv[optind++];\n\n\tint ret = 1; /* Normal exit status: 0 if something matched, 1 if not. */\n\tint err = 0; /* Whether an error was encountered that should override the exit status to 2. */\n\n\t/* We show additional messages for detected binaries only if we are on a TTY,\n\t * and \"auto\" color mode should only activate if we are on a TTY. */\n\tint is_tty = isatty(STDOUT_FILENO);\n\tif (!is_tty && use_color == 1) use_color = 0;\n\n\t/* When multiple file arguments are provided, include the names of files as a prefix to matches. */\n\tint showFilenames = (optind + 1 < argc);\n\n\t/* This buffer is allocated by 'getline' and reused between files. */\n\tchar * buf = NULL;\n\tsize_t avail = 0;\n\n\tdo {\n\t\tFILE * input = stdin;\n\t\tssize_t lineLength = 0;\n\t\tint count = 0;    /* Count of matching lines. */\n\t\tint isbinary = 0; /* If we have detected any NUL characters in the input. */\n\t\tint ln = 0;       /* Current line number. */\n\n\t\t/* If there are file arguments, use them, but treat - as standard input. */\n\t\tif (optind < argc && strcmp(argv[optind],\"-\")) {\n\t\t\tinput = fopen(argv[optind], \"r\");\n\t\t\tif (!input) {\n\t\t\t\tif (!suppress_errors) fprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\t\t/* We continue to read other arguments but note this error to exit with 2 later. */\n\t\t\t\terr = 1;\n\t\t\t\toptind++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t/* POSIX says we should print '(standard input)' instead of -, so make it happy. */\n\t\tconst char * filename = input == stdin ? \"(standard input)\" : argv[optind];\n\n\t\twhile ((lineLength = getline(&buf, &avail, input)) >= 0) {\n\t\t\tln++;\n\n\t\t\t/* Ignore any trailing line feed. */\n\t\t\tif (lineLength && buf[lineLength-1] == '\\n') {\n\t\t\t\tlineLength--;\n\t\t\t}\n\n\t\t\t/* Prepare Line for regex engine. */\n\t\t\tstruct Line line = {\n\t\t\t\tlineLength,\n\t\t\t\tbuf\n\t\t\t};\n\n\t\t\t/* Scan for any NUL characters in this line to see if it is a binary. */\n\t\t\tif (!isbinary) {\n\t\t\t\tfor (ssize_t i = 0; i < lineLength; ++i) {\n\t\t\t\t\tif (buf[i] == '\\0') {\n\t\t\t\t\t\tisbinary = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!invert) {\n\t\t\t\tint lastMatch = 0; /* End of the last match. */\n\t\t\t\tint matched = 0;   /* Whether this line has matched yet. Useful when a degenerate match means lastMatch is still 0. */\n\t\t\t\tfor (int j = 0; j <= lineLength;) {\n\t\t\t\t\tint len;\n\t\t\t\t\tif (subsearch_matches(&line, j, needle, &len)) {\n\t\t\t\t\t\tif (whole_lines && len != lineLength) break;\n\n\t\t\t\t\t\t/* If quiet gets a match at all, ony any file, we immediately exit with 0,\n\t\t\t\t\t\t * even ignoring any errors we may have printed about previously. */\n\t\t\t\t\t\tif (quiet) return 0;\n\n\t\t\t\t\t\t/* Increment count of matching lines only if this is the fist match */\n\t\t\t\t\t\tif (!matched) count++;\n\n\t\t\t\t\t\t/* If anything matched, return code is 0 except for an error. */\n\t\t\t\t\t\tret = 0;\n\t\t\t\t\t\tmatched = 1;\n\n\t\t\t\t\t\t/* If we are just listing matching files, we're done on the first match. */\n\t\t\t\t\t\tif (list_files) goto _done;\n\n\t\t\t\t\t\t/* If we're counting matching lines, we're done with this line. */\n\t\t\t\t\t\tif (counts) break;\n\n\t\t\t\t\t\t/* If this is a binary file, and we are not counting matched lines,\n\t\t\t\t\t\t * we're done here, similar to listing. */\n\t\t\t\t\t\tif (isbinary) goto _done;\n\n\t\t\t\t\t\tif (!len && only_matching) {\n\t\t\t\t\t\t\t/* Don't try to print a degenerate match here. */\n\t\t\t\t\t\t\tlastMatch = j = j + 1;\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (only_matching || !lastMatch) {\n\t\t\t\t\t\t\t/* Prefix this match result as needed. For -o, every match\n\t\t\t\t\t\t\t * is prefixed. Otherwise, we only print the prefix before\n\t\t\t\t\t\t\t * the first match. */\n\t\t\t\t\t\t\tif (showFilenames) fprintf(stdout, \"%s:\", filename);\n\t\t\t\t\t\t\tif (line_numbers) fprintf(stdout, \"%d:\", ln);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (only_matching) {\n\t\t\t\t\t\t\tfprintf(stdout, \"%.*s\\n\", len, buf + j);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t/* Print from the end of the previous match up to the end of\n\t\t\t\t\t\t\t * the current match, with color when enabled. */\n\t\t\t\t\t\t\tfprintf(stdout, \"%.*s%s%.*s%s\",\n\t\t\t\t\t\t\t\tj - lastMatch,\n\t\t\t\t\t\t\t\tbuf + lastMatch,\n\t\t\t\t\t\t\t\tuse_color ? \"\\033[1;31m\" : \"\",\n\t\t\t\t\t\t\t\tlen,\n\t\t\t\t\t\t\t\tbuf + j,\n\t\t\t\t\t\t\t\tuse_color ? \"\\033[0m\" : \"\");\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t/* Update lastMatch to point to the end of this match. */\n\t\t\t\t\t\tlastMatch = j + len;\n\n\t\t\t\t\t\t/* Advance to that point, ensuring we skip to the next character\n\t\t\t\t\t\t * if this match was degenerate. */\n\t\t\t\t\t\tj = lastMatch + !len;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tj++;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* When matching whole lines, we only check from index 0, so break now. */\n\t\t\t\t\tif (whole_lines) break;\n\t\t\t\t}\n\n\t\t\t\t/* If we are counting matches, don't print anything and go to next line. */\n\t\t\t\tif (counts) continue;\n\n\t\t\t\t/* If we weren't printing just the matching text and we had a match, there\n\t\t\t\t * may be more of the line left to print, and we must also print a line feed\n\t\t\t\t * ourselves (we ignore any line feed in the actual line, and we print one\n\t\t\t\t * even if the line didn't actually end in one). */\n\t\t\t\tif (!only_matching && matched) fprintf(stdout, \"%.*s\\n\", (int)(lineLength - lastMatch), buf + lastMatch);\n\t\t\t} else {\n\n\t\t\t\t/* The inverse matching case is a lot simpler as we don't have to deal with\n\t\t\t\t * coloring submatches. Try the pattern at every point in the line, and if\n\t\t\t\t * it ever matches, reject it. When un-matching whole lines, check only\n\t\t\t\t * from index 0 and only reject if the match length is the same as the line. */\n\t\t\t\tint matched = 0;\n\t\t\t\tfor (int j = 0; j <= lineLength; ++j) {\n\t\t\t\t\tint len;\n\t\t\t\t\tif (subsearch_matches(&line, j, needle, &len)) {\n\t\t\t\t\t\tif (whole_lines && len != lineLength) break;\n\t\t\t\t\t\tmatched = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (whole_lines) break;\n\t\t\t\t}\n\n\t\t\t\t/* Do nothing on a matched line. */\n\t\t\t\tif (matched) continue;\n\n\t\t\t\t/* In quiet mode, if anything un-matched, immediately exit with 0,\n\t\t\t\t * ignoring all the other arguments. */\n\t\t\t\tif (quiet) return 0;\n\n\t\t\t\t/* Otherwise any un-matched line sets our return status to 0 like any\n\t\t\t\t * matched line does in the non-inverse case. */\n\t\t\t\tret = 0;\n\n\t\t\t\t/* Count un-matched lines. */\n\t\t\t\tcount++;\n\n\t\t\t\t/* If just listing file names, we're done with this file. */\n\t\t\t\tif (list_files) goto _done;\n\n\t\t\t\t/* If we're counting un-matching lines, we're done with this one. */\n\t\t\t\tif (counts) continue;\n\n\t\t\t\t/* And again, same as above, if this was a binary and we weren't\n\t\t\t\t * counting un-matched 'lines', we're now done with this file. */\n\t\t\t\tif (isbinary) goto _done;\n\n\t\t\t\t/* -ov is a weird combo, but don't print anything. */\n\t\t\t\tif (only_matching) continue;\n\n\t\t\t\t/* Print relevant prefixes. */\n\t\t\t\tif (showFilenames) fprintf(stdout, \"%s:\", filename);\n\t\t\t\tif (line_numbers) fprintf(stdout, \"%d:\", ln);\n\n\t\t\t\t/* And finally, print the un-matched line with a line feed. */\n\t\t\t\tfprintf(stdout, \"%.*s\\n\", (int)lineLength, buf);\n\t\t\t}\n\t\t}\n\n\t\tif (lineLength < 0 && ferror(input)) {\n\t\t\tif (!suppress_errors) fprintf(stderr, \"%s: %s: %s\\n\", argv[0], filename, strerror(errno));\n\t\t\terr = 1;\n\t\t\toptind++;\n\t\t\tcontinue;\n\t\t}\n\n_done: (void)0;\n\t\tif (input != stdin) fclose(input);\n\n\t\tif (!quiet) {\n\t\t\tif (list_files) {\n\t\t\t\tif (count) puts(filename);\n\t\t\t} else if (counts) {\n\t\t\t\tif (showFilenames) fprintf(stdout, \"%s:\", filename);\n\t\t\t\tfprintf(stdout, \"%d\\n\", count);\n\t\t\t} else if (count && isbinary && is_tty) {\n\t\t\t\tfprintf(stdout, \"%s: %s: binary file matches\\n\", argv[0], filename);\n\t\t\t}\n\t\t}\n\n\t\toptind++;\n\t} while (optind < argc);\n\n\treturn err ? 2 : ret;\n}\n\n"
  },
  {
    "path": "apps/groups.c",
    "content": "/**\n * @brief List group memberships.\n * @file apps/groups.c\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <unistd.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <pwd.h>\n\nint main(int argc, char ** argv) {\n\t/* First print our egid group */\n\tstruct passwd * p = getpwuid(getegid());\n\tif (p) {\n\t\tfprintf(stdout, \"%s \", p->pw_name);\n\t}\n\t/* Then get the group list. */\n\tint groupCount = getgroups(0, NULL);\n\tif (groupCount) {\n\t\tgid_t * myGroups = malloc(sizeof(gid_t) * groupCount);\n\t\tgroupCount = getgroups(groupCount, myGroups);\n\t\tfor (int i = 0; i < groupCount; ++i) {\n\t\t\tp = getpwuid(myGroups[i]);\n\t\t\tif (p) {\n\t\t\t\tfprintf(stdout, \"%s \", p->pw_name);\n\t\t\t}\n\t\t}\n\t}\n\tfprintf(stdout,\"\\n\");\n\tendpwent();\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/gsudo.c",
    "content": "/**\n * @brief gsudo - graphical implementation of sudo\n *\n * probably even less secure than the original\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2017 K. Lange\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <errno.h>\n\n#include <toaru/auth.h>\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/text.h>\n#include <toaru/button.h>\n\n#define main __main_unused\n#include \"sudo.c\"\n#undef main\n\n#define FONT_SIZE_TITLE 18\n#define FONT_SIZE_MAIN 13\n#define FONT_SIZE_PASSWD 22\n#define FONT_COLOR (rgb(0,0,0))\n#define FONT_RED (rgb(250,0,0))\n#define BUTTON_HEIGHT 28\n#define BUTTON_WIDTH 120\n#define BUTTON_PADDING 18\n\nstatic yutani_t * yctx;\nstatic gfx_context_t * ctx;\nstatic yutani_window_t * window;\nstatic struct TT_Font * tt_font_thin;\n\nstruct TTKButton _button_cancel = {\n\t0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, \"Cancel\", 0\n};\n\nstruct TTKButton _button_authenticate = {\n\t410 - BUTTON_WIDTH - BUTTON_PADDING, 260, BUTTON_WIDTH, BUTTON_HEIGHT, \"Authenticate\", 0\n};\n\nstruct TTKButton * _down_button = NULL;\n\nstatic int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {\n\tif (me->new_y >= button->y && me->new_y < button->y  + button->height) {\n\t\tif (me->new_x >= button->x && me->new_x < button->x + button->width) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int set_hilight(struct TTKButton * button, int hilight) {\n\tif (!button && (_button_cancel.hilight || _button_authenticate.hilight)) {\n\t\t_button_cancel.hilight = 0;\n\t\t_button_authenticate.hilight = 0;\n\t\treturn 1;\n\t} else if (button && (button->hilight != hilight)) {\n\t\t_button_cancel.hilight = 0;\n\t\t_button_authenticate.hilight = 0;\n\t\tbutton->hilight = hilight;\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void redraw(char * username, char * password, int fails, char * argv[]) {\n\n\tsprite_t * prompt = create_sprite(420, 320, ALPHA_EMBEDDED);\n\tgfx_context_t * myctx = init_graphics_sprite(prompt);\n\tdraw_fill(myctx, rgba(0,0,0,0));\n\n\t/* Draw rounded rectangle */\n\tdraw_rounded_rectangle(myctx, 10, 10, prompt->width - 20, prompt->height - 20, 10, rgba(0,0,0,200));\n\tblur_context_box(myctx, 10);\n\tblur_context_box(myctx, 10);\n\tdraw_rounded_rectangle(myctx, 10, 10, prompt->width - 20, prompt->height - 20, 10, rgb(239,238,232));\n\n\t/* Draw prompt messages */\n\ttt_set_size(tt_font_thin, FONT_SIZE_TITLE);\n\ttt_draw_string(myctx, tt_font_thin, 30, 30 + FONT_SIZE_TITLE, \"Authentication Required\", FONT_COLOR);\n\ttt_set_size(tt_font_thin, FONT_SIZE_MAIN);\n\ttt_draw_string(myctx, tt_font_thin, 30, 54 + FONT_SIZE_MAIN, \"Authentication is required to run the application\", FONT_COLOR);\n\ttt_draw_string(myctx, tt_font_thin, 30, 72 + FONT_SIZE_MAIN, argv[1], FONT_COLOR);\n\n\tchar prompt_message[512];\n\tsprintf(prompt_message, \"Enter password for '%s'\", username);\n\ttt_draw_string(myctx, tt_font_thin, 30, 100 + FONT_SIZE_MAIN, prompt_message, FONT_COLOR);\n\n\tif (fails) {\n\t\tsprintf(prompt_message, \"Try again. %d failures.\", fails);\n\t\ttt_draw_string(myctx, tt_font_thin, 30, 146 + FONT_SIZE_MAIN, prompt_message, FONT_RED);\n\t}\n\n\tstruct gradient_definition edge = {30, 114, rgb(0,120,220), rgb(0,120,220)};\n\tdraw_rounded_rectangle_pattern(myctx, 30, 120, prompt->width - 70, 26, 4, gfx_vertical_gradient_pattern, &edge);\n\tdraw_rounded_rectangle(myctx, 32, 122, prompt->width - 74, 22, 3, rgb(250,250,250));\n\n\tchar password_circles[512] = {0};;\n\tstrcpy(password_circles, \"\");\n\tfor (unsigned int i = 0; i < strlen(password) && i < 512/4; ++i) {\n\t\tstrcat(password_circles, \"●\");\n\t}\n\n\tgfx_context_t * clipped = init_graphics_subregion(myctx, 32, 122, prompt->width - 74, 22);\n\ttt_set_size(tt_font_thin, FONT_SIZE_PASSWD);\n\ttt_draw_string(clipped, tt_font_thin, 1, FONT_SIZE_PASSWD - 5, password_circles, FONT_COLOR);\n\tfree(clipped);\n\n\tdraw_fill(ctx, rgba(0,0,0,200));\n\tdraw_sprite(ctx, prompt, (ctx->width - prompt->width) / 2, (ctx->height - prompt->height) / 2);\n\n\t_button_cancel.x = (410 - 2 * (BUTTON_WIDTH + BUTTON_PADDING)) + (ctx->width - prompt->width) / 2;\n\t_button_cancel.y = 260 + (ctx->height - prompt->height) / 2;\n\t_button_authenticate.x = (410 - (BUTTON_WIDTH + BUTTON_PADDING)) + (ctx->width - prompt->width) / 2;\n\t_button_authenticate.y = 260 + (ctx->height - prompt->height) / 2;\n\tttk_button_draw(ctx, &_button_cancel);\n\tttk_button_draw(ctx, &_button_authenticate);\n\n\tsprite_free(prompt);\n\tfree(myctx);\n\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nstatic int graphical_callback(char * username, char * password, int fails, char * argv[]) {\n\tint i = 0;\n\n\tredraw(username, password, fails, argv);\n\n\twhile (1) {\n\n\t\tyutani_msg_t * msg = yutani_poll(yctx);\n\n\t\tswitch (msg->type) {\n\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)msg->data;\n\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\tif (ke->event.keycode == KEY_ESCAPE) {\n\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (ke->event.keycode == '\\n') {\n\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t} else  if (ke->event.key == 8) {\n\t\t\t\t\t\t\tif (i > 0) i--;\n\t\t\t\t\t\t\tpassword[i] = '\\0';\n\t\t\t\t\t\t} else if (ke->event.key) {\n\t\t\t\t\t\t\tpassword[i] = ke->event.key;\n\t\t\t\t\t\t\tpassword[i+1] = '\\0';\n\t\t\t\t\t\t\ti++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tredraw(username, password, fails, argv);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)msg->data;\n\t\t\t\t\tint r = 0;\n\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\tif (in_button(&_button_cancel, me)) {\n\t\t\t\t\t\t\t\tr |= set_hilight(&_button_cancel, 2);\n\t\t\t\t\t\t\t\t_down_button = &_button_cancel;\n\t\t\t\t\t\t\t} else if (in_button(&_button_authenticate, me)) {\n\t\t\t\t\t\t\t\tr |= set_hilight(&_button_authenticate, 2);\n\t\t\t\t\t\t\t\t_down_button = &_button_authenticate;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\tif (_down_button) {\n\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t/* Handle button presses */\n\t\t\t\t\t\t\t\t\tif (_down_button == &_button_cancel) return 1;\n\t\t\t\t\t\t\t\t\telse if (_down_button == &_button_authenticate) return 0;\n\t\t\t\t\t\t\t\t\t_down_button->hilight = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_down_button = NULL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\tif (in_button(&_button_cancel, me)) {\n\t\t\t\t\t\t\t\tr |= set_hilight(&_button_cancel, 1);\n\t\t\t\t\t\t\t} else if (in_button(&_button_authenticate, me)) {\n\t\t\t\t\t\t\t\tr |= set_hilight(&_button_authenticate, 1);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tr |= set_hilight(NULL, 0);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (_down_button) {\n\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\tr |= set_hilight(_down_button, 2);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tr |= set_hilight(NULL, 0);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (r) redraw(username, password, fails, argv);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t{\n\t\t\t\t\t/* If we ever lose focus, try to get it back. */\n\t\t\t\t\tstruct yutani_msg_window_focus_change * fc = (void*)msg->data;\n\t\t\t\t\tif (fc->wid == window->wid && fc->focused == 0) {\n\t\t\t\t\t\tyutani_focus_window(yctx, window->wid);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n}\n\nint main(int argc, char ** argv) {\n\n\tif (argc < 2) {\n\t\treturn 1;\n\t}\n\n\tyctx = yutani_init();\n\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: could not connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint width = yctx->display_width;\n\tint height = yctx->display_height;\n\n\twindow = yutani_window_create_flags(yctx, width, height,\n\t\tYUTANI_WINDOW_FLAG_DISALLOW_RESIZE |\n\t\tYUTANI_WINDOW_FLAG_DISALLOW_DRAG |\n\t\tYUTANI_WINDOW_FLAG_ALT_ANIMATION);\n\n\tyutani_window_move(yctx, window, 0, 0);\n\n\tyutani_set_stack(yctx, window, YUTANI_ZORDER_OVERLAY);\n\n\ttt_font_thin = tt_font_from_shm(\"sans-serif\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\treturn sudo_loop(graphical_callback, argv);\n}\n"
  },
  {
    "path": "apps/gunzip.c",
    "content": "/**\n * @brief gunzip - decompress gzip-compressed payloads\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n\n#include <toaru/inflate.h>\n\nstatic int to_stdout = 0;\nstatic int keep = 0;\nstatic int force = 0;\n\nstatic uint8_t _get(struct inflate_context * ctx) {\n\treturn fgetc(ctx->input_priv);\n}\n\nstatic void _write(struct inflate_context * ctx, unsigned int sym) {\n\tfputc(sym, ctx->output_priv);\n}\n\nstatic int usage(int argc, char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"gunzip - decompress gzip-compressed payloads\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-ckf] name...\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -c     \\033[3mwrite to stdout; implies -k\\033[0m\\n\"\n\t\t\t\" -k     \\033[3mkeep original files unchanged\\033[0m\\n\"\n\t\t\t\" -f     \\033[3mforce decompression (eg. from tty,\\033[0m\\n\"\n\t\t\t\"        \\033[3mor to an existing file, etc.)\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nstatic int endswith(char * str, char * suffix) {\n\tsize_t len_suffix = strlen(suffix);\n\tsize_t len_str = strlen(str);\n\n\tif (len_str < len_suffix) return 0;\n\treturn !memcmp(str+len_str-len_suffix,suffix,len_suffix);\n}\n\nstatic int decompress_one(char * argv[], char * file) {\n\tFILE * f = strcmp(file, \"-\") ? fopen(file, \"r\") : stdin;\n\n\tif (!f) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], file, strerror(errno));\n\t\treturn 1;\n\t}\n\n\tif (!force && isatty(fileno(f))) {\n\t\tfprintf(stderr, \"%s: not decompressng from terminal; use -f to override\\n\", argv[0]);\n\t\tif (f != stdin) fclose(f);\n\t\treturn 1;\n\t}\n\n\tstruct inflate_context ctx;\n\tctx.get_input = _get;\n\tctx.write_output = _write;\n\tctx.input_priv = f;\n\tctx.ring = NULL;\n\n\tif (f == stdin || to_stdout) {\n\t\tctx.output_priv = stdout;\n\t} else {\n\t\tchar * tmp = strdup(file);\n\t\tif (endswith(file,\".gz\")) {\n\t\t\ttmp[strlen(tmp)-3] = '\\0';\n\t\t} else if (endswith(file,\".z\") || endswith(file,\".Z\")) {\n\t\t\ttmp[strlen(tmp)-2] = '\\0';\n\t\t} else if (endswith(file,\".tgz\")) {\n\t\t\ttmp[strlen(tmp)-2] = 'a';\n\t\t\ttmp[strlen(tmp)-1] = 'r';\n\t\t} else {\n\t\t\tfree(tmp);\n\t\t\tfprintf(stderr, \"%s: %s: unreocognized suffix, ignoring\\n\", argv[0], file);\n\t\t\tfclose(f);\n\t\t\treturn 1;\n\t\t}\n\n\t\tctx.output_priv = fopen(tmp,force ? \"w\" : \"wx\");\n\n\t\tif (!ctx.output_priv) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], tmp, strerror(errno));\n\t\t\tfree(tmp);\n\t\t\tfclose(f);\n\t\t\treturn 1;\n\t\t}\n\n\t\tfree(tmp);\n\t}\n\n\tif (gzip_decompress(&ctx)) {\n\t\tfprintf(stderr, \"%s: %s: unspecified error from inflate\\n\", argv[0], file);\n\t\tif (ctx.output_priv != stdout) fclose(ctx.output_priv);\n\t\tif (f != stdin) fclose(f);\n\t\treturn 1;\n\t}\n\n\tfflush(ctx.output_priv);\n\tif (f != stdin) fclose(f);\n\n\tif (ctx.output_priv != stdout) fclose(ctx.output_priv);\n\tif (f != stdin && !keep) unlink(file); /* Just ignore errors, whatever. */\n\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?ckf\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'c':\n\t\t\t\tto_stdout = 1;\n\t\t\t\tkeep = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'k':\n\t\t\t\tkeep = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tforce = 1;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\tcase '?':\n\t\t\t\treturn usage(argc, argv);\n\t\t}\n\t}\n\n\t/* No argument, read from stdin */\n\tif (optind >= argc) {\n\t\treturn decompress_one(argv, \"-\");\n\t} else {\n\t\tint ret = 0;\n\t\tfor (int i = optind; i < argc; ++i) {\n\t\t\tret |= decompress_one(argv, argv[i]);\n\t\t}\n\t\treturn ret;\n\t}\n}\n\n"
  },
  {
    "path": "apps/head.c",
    "content": "/**\n * @brief head - Print the first `n` lines of a file.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <errno.h>\n\nint main(int argc, char * argv[]) {\n\tint n = 10;\n\tint opt;\n\tint print_names = 0;\n\tint retval = 0;\n\n\twhile ((opt = getopt(argc, argv, \"n:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'n':\n\t\t\t\tn = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (argc > optind + 1) {\n\t\t/* Multiple files */\n\t\tprint_names = 1;\n\t}\n\n\tif (argc == optind) {\n\t\t/* This is silly, but should work due to reasons. */\n\t\targv[optind] = \"-\";\n\t\targc++;\n\t}\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tFILE * f = (!strcmp(argv[i],\"-\")) ? stdin : fopen(argv[i],\"r\");\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tretval = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (print_names) {\n\t\t\tfprintf(stdout, \"==> %s <==\\n\", (f == stdin) ? \"standard input\" : argv[i]);\n\t\t}\n\n\t\tint line = 1;\n\n\t\twhile (!feof(f)) {\n\t\t\tint c = fgetc(f);\n\t\t\tif (c >= 0) {\n\t\t\t\tfputc(c, stdout);\n\n\t\t\t\tif (c == '\\n') {\n\t\t\t\t\tline++;\n\t\t\t\t\tif (line > n) break;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (f != stdin) {\n\t\t\tfclose(f);\n\t\t}\n\t}\n\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/hello.c",
    "content": "/**\n * @brief hello - Prints \"Hello, world.\"\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011~2018 K. Lange\n */\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\tputs(\"Hello, world.\");\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/help-browser.c",
    "content": "/**\n * @brief help-browser - Display documentation.\n *\n * This is a work-in-progress reimplementation of the help browser\n * from mainline ToaruOS. It is currently incomplete.\n *\n * Eventually, this should be a rich text document browser, almost\n * akin to a web browser. Right now it just says \"Hello, world.\"\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2020 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <dirent.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/markup.h>\n\n#define APPLICATION_TITLE \"Help Browser\"\n#define HELP_DIR \"/usr/share/help\"\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * main_window;\nstatic gfx_context_t * ctx;\n\nstatic int application_running = 1;\n\nstatic gfx_context_t * contents = NULL;\nstatic sprite_t * contents_sprite = NULL;\nstatic int contents_width = 0;\n\nstatic char * current_topic = NULL;\nstatic int scroll_offset;\n\n/* Markup Renderer { */\n#define BASE_X 2\n#define BASE_Y 2\n#define LINE_HEIGHT 20\n#define HEAD_HEIGHT 28\n\nstatic int cursor_y = 0;\nstatic int cursor_x = 0;\nstatic list_t * state = NULL;\nstatic int current_state = 0;\n\nstatic struct TT_Font * tt_font_thin = NULL;\nstatic struct TT_Font * tt_font_bold = NULL;\nstatic struct TT_Font * tt_font_oblique = NULL;\nstatic struct TT_Font * tt_font_bold_oblique = NULL;\nstatic struct TT_Font * tt_font_mono = NULL;\n\nstruct Char {\n\tchar c; /* TODO: unicode */\n\tchar state;\n};\n\n//static list_t * lines = NULL;\nstatic list_t * buffer = NULL;\n\nstatic struct TT_Font * state_to_font(int current_state) {\n\tif (current_state & (1 << 3)) {\n\t\treturn tt_font_mono;\n\t}\n\tif (current_state & (1 << 0 | 1 << 2) ) {\n\t\tif (current_state & (1 << 1)) {\n\t\t\treturn tt_font_bold_oblique;\n\t\t}\n\t\treturn tt_font_bold;\n\t} else if (current_state & (1 << 1)) {\n\t\treturn tt_font_oblique;\n\t}\n\treturn tt_font_thin;\n}\n\nstatic int current_size(void) {\n\tif (current_state & (1 << 2)) {\n\t\treturn 22;\n\t}\n\treturn 13;\n}\n\nstatic int buffer_width(list_t * buffer) {\n\tint out = 0;\n\tforeach(node, buffer) {\n\t\tstruct Char * c = node->value;\n\n\t\tchar tmp[2] = {c->c, '\\0'};\n\n\t\ttt_set_size(state_to_font(c->state), current_size());\n\t\tout += tt_string_width(state_to_font(c->state), tmp);\n\t}\n\treturn out;\n}\n\nstatic int draw_buffer(list_t * buffer) {\n\tint x = 0;\n\twhile (buffer->length) {\n\t\tnode_t * node = list_dequeue(buffer);\n\t\tstruct Char * c = node->value;\n\t\tchar tmp[2] = { c->c, '\\0' };\n\t\ttt_set_size(state_to_font(c->state), current_size());\n\t\tif (contents) {\n\t\t\tx += tt_draw_string(contents, state_to_font(c->state), cursor_x + x, cursor_y + current_size(), tmp, 0xFF000000);\n\t\t} else {\n\t\t\tx += tt_string_width(state_to_font(c->state), tmp);\n\t\t}\n\t\tfree(c);\n\t\tfree(node);\n\t}\n\tx += 4;\n\treturn x;\n}\n\nstatic int current_line_height(void) {\n\tif (current_state & (1 << 2)) {\n\t\treturn HEAD_HEIGHT;\n\t} else {\n\t\treturn LINE_HEIGHT;\n\t}\n}\n\nstatic void write_buffer(void) {\n\tif (buffer_width(buffer) + cursor_x > contents_width) {\n\t\tcursor_x = BASE_X;\n\t\tcursor_y += current_line_height();\n\t}\n\tcursor_x += draw_buffer(buffer);\n}\n\nstatic int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) {\n\tif (!strcmp(tag->name, \"b\")) {\n\t\tlist_insert(state, (void*)(uintptr_t)current_state);\n\t\tcurrent_state |= (1 << 0);\n\t} else if (!strcmp(tag->name, \"i\")) {\n\t\tlist_insert(state, (void*)(uintptr_t)current_state);\n\t\tcurrent_state |= (1 << 1);\n\t} else if (!strcmp(tag->name, \"h1\")) {\n\t\tlist_insert(state, (void*)(uintptr_t)current_state);\n\t\tcurrent_state |= (1 << 2);\n\t} else if (!strcmp(tag->name, \"mono\")) {\n\t\tlist_insert(state, (void*)(uintptr_t)current_state);\n\t\tcurrent_state |= (1 << 3);\n\t} else if (!strcmp(tag->name, \"br\")) {\n\t\twrite_buffer();\n\t\tcursor_x = BASE_X;\n\t\tcursor_y += current_line_height();\n\t}\n\tmarkup_free_tag(tag);\n\treturn 0;\n}\n\nstatic int parser_close(struct markup_state * self, void * user, char * tag_name) {\n\tif (!strcmp(tag_name, \"b\")) {\n\t\tnode_t * nstate = list_pop(state);\n\t\tcurrent_state = (int)(uintptr_t)nstate->value;\n\t\tfree(nstate);\n\t} else if (!strcmp(tag_name, \"i\")) {\n\t\tnode_t * nstate = list_pop(state);\n\t\tcurrent_state = (int)(uintptr_t)nstate->value;\n\t\tfree(nstate);\n\t} else if (!strcmp(tag_name, \"mono\")) {\n\t\tnode_t * nstate = list_pop(state);\n\t\tcurrent_state = (int)(uintptr_t)nstate->value;\n\t\tfree(nstate);\n\t} else if (!strcmp(tag_name, \"h1\")) {\n\t\twrite_buffer();\n\t\tcursor_x = BASE_X;\n\t\tcursor_y += current_line_height();\n\t\tnode_t * nstate = list_pop(state);\n\t\tcurrent_state = (int)(uintptr_t)nstate->value;\n\t\tfree(nstate);\n\t}\n\treturn 0;\n}\n\nstatic int parser_data(struct markup_state * self, void * user, char * data) {\n\tchar * c = data;\n\twhile (*c) {\n\t\tif (*c == ' ' && !(current_state & (1 << 3))) {\n\t\t\tif (buffer->length) {\n\t\t\t\twrite_buffer();\n\t\t\t}\n\t\t} else if (*c == '\\n') {\n\t\t\tif (buffer->length) {\n\t\t\t\twrite_buffer();\n\t\t\t}\n\t\t\tif (current_state & (1 << 3)) {\n\t\t\t\tcursor_x = BASE_X;\n\t\t\t\tcursor_y += current_line_height();\n\t\t\t}\n\t\t} else {\n\t\t\tint chr = *c;\n\t\t\tif (*c == '&') {\n\t\t\t\tif (c[1] == 'l' && c[2] == 't' && c[3] == ';') {\n\t\t\t\t\tc += 3;\n\t\t\t\t\tchr = '<';\n\t\t\t\t} else if (c[1] == 'g' && c[2] == 't' && c[3] == ';') {\n\t\t\t\t\tc += 3;\n\t\t\t\t\tchr = '>';\n\t\t\t\t}\n\t\t\t}\n\t\t\tstruct Char * ch = malloc(sizeof(struct Char));\n\t\t\tch->c = chr;\n\t\t\tch->state = current_state;\n\t\t\tlist_insert(buffer, ch);\n\t\t}\n\t\tc++;\n\t}\n\treturn 0;\n}\n\n/* } End Markup Renderer */\n\nstatic struct menu_bar menu_bar = {0};\nstatic struct menu_bar_entries menu_entries[] = {\n\t{\"File\", \"file\"},\n\t{\"Go\", \"go\"},\n\t{\"Help\", \"help\"},\n\t{NULL, NULL},\n};\n\nstatic void _menu_action_exit(struct MenuEntry * entry) {\n\tapplication_running = 0;\n}\n\nstatic void reinitialize_contents(void) {\n\tif (contents) {\n\t\tfree(contents);\n\t}\n\n\tif (contents_sprite) {\n\t\tsprite_free(contents_sprite);\n\t}\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\tcontents = NULL;\n\tcontents_width = main_window->width - bounds.width;\n\n\t{\n\t\tstruct markup_state * parser = markup_init(NULL, parser_open, parser_close, parser_data);\n\t\tcursor_y = BASE_Y;\n\t\tcursor_x = BASE_X;\n\t\tstate = list_create();\n\t\tbuffer = list_create();\n\n\t\tchar * str = current_topic;\n\t\twhile (*str) {\n\t\t\tif (markup_parse(parser, *str++)) {\n\t\t\t\tfprintf(stderr,\"There was an error.\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tmarkup_finish(parser);\n\t\twrite_buffer();\n\t\tlist_free(state);\n\t\tfree(state);\n\t\tfree(buffer);\n\t}\n\n\tcontents_sprite = create_sprite(contents_width, cursor_y + current_size() + 20, ALPHA_EMBEDDED);\n\tcontents = init_graphics_sprite(contents_sprite);\n\n\tdraw_fill(contents, rgb(255,255,255));\n\n\t{\n\t\tstruct markup_state * parser = markup_init(NULL, parser_open, parser_close, parser_data);\n\t\tcursor_y = BASE_Y;\n\t\tcursor_x = BASE_X;\n\t\tstate = list_create();\n\t\tbuffer = list_create();\n\n\t\tchar * str = current_topic;\n\t\twhile (*str) {\n\t\t\tif (markup_parse(parser, *str++)) {\n\t\t\t\tfprintf(stderr,\"There was an error.\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tmarkup_finish(parser);\n\t\twrite_buffer();\n\t\tlist_free(state);\n\t\tfree(state);\n\t\tfree(buffer);\n\t}\n}\n\nstatic void redraw_window(void) {\n\tdraw_fill(ctx, rgb(255,255,255));\n\n\trender_decorations(main_window, ctx, APPLICATION_TITLE);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\n\tmenu_bar.x = bounds.left_width;\n\tmenu_bar.y = bounds.top_height;\n\tmenu_bar.width = ctx->width - bounds.width;\n\tmenu_bar.window = main_window;\n\tmenu_bar_render(&menu_bar, ctx);\n\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT, ctx->width - bounds.width, ctx->height - MENU_BAR_HEIGHT - bounds.height);\n\tdraw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT - scroll_offset);\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, 0, 0, ctx->width, ctx->height);\n\n\tflip(ctx);\n\tyutani_flip(yctx, main_window);\n}\n\nstatic void resize_finish(int w, int h) {\n\tint height_changed = (main_window->width != (unsigned int)w);\n\n\tyutani_window_resize_accept(yctx, main_window, w, h);\n\treinit_graphics_yutani(ctx, main_window);\n\n\tif (height_changed) {\n\t\treinitialize_contents();\n\t}\n\n\tredraw_window();\n\tyutani_window_resize_done(yctx, main_window);\n\n\tyutani_flip(yctx, main_window);\n}\n\nstatic char * generate_index(void) {\n\tlist_t * components = list_create();\n\n\tlist_insert(components, strdup(\"<h1>Topics</h1>\\n\"));\n\n\t/* Open /usr/share/help */\n\tDIR * dirp = opendir(\"/usr/share/help\");\n\n\tif (dirp) {\n\t\tfor (struct dirent * ent = readdir(dirp); ent; ent = readdir(dirp)) {\n\t\t\tif (ent->d_name[0] == '.') continue;\n\n\t\t\t/* TODO: Extract the actual heading... */\n\t\t\tchar tmp[1024];\n\t\t\tsnprintf(tmp, 1024, \" » %s<br>\\n\", ent->d_name);\n\t\t\tlist_insert(components, strdup(tmp));\n\t\t}\n\t\tclosedir(dirp);\n\t}\n\n\tsize_t totalSize = 0;\n\tforeach(node, components) {\n\t\tchar * s = node->value;\n\t\ttotalSize += strlen(s);\n\t}\n\n\tchar * out = malloc(totalSize + 1);\n\tchar * ptr = out;\n\tforeach(node, components) {\n\t\tchar * s = node->value;\n\t\tsize_t len = strlen(s);\n\t\tmemcpy(ptr, s, len);\n\t\tptr += len;\n\t\tfree(s);\n\t}\n\t*ptr = '\\0';\n\n\tlist_free(components);\n\treturn out;\n}\n\nstatic void navigate(const char * t) {\n\n\tif (current_topic) free(current_topic);\n\n\t/* Is this a special file? */\n\n\tif (strstr(t, \"special:\") == t) {\n\n\t\tconst char * pageName = t + 8;\n\n\t\tif (!strcmp(pageName, \"contents\")) {\n\t\t\t/* Oh boy... */\n\t\t\tcurrent_topic = generate_index();\n\t\t} else {\n\t\t\tcurrent_topic = strdup(\"File not found.\");\n\t\t}\n\n\t} else {\n\t\tchar file_path[1024];\n\n\t\tif (t[0] == '/') {\n\t\t\tsprintf(file_path, \"%s\", t);\n\t\t} else {\n\t\t\tsprintf(file_path, \"%s/%s\", HELP_DIR, t);\n\t\t}\n\n\t\tFILE * f = fopen(file_path, \"r\");\n\n\t\tif (!f) {\n\t\t\tcurrent_topic = strdup(\"File not found.\");\n\t\t} else {\n\t\t\tfseek(f, 0, SEEK_END);\n\t\t\tsize_t size = ftell(f);\n\t\t\tfseek(f, 0, SEEK_SET);\n\n\t\t\tcurrent_topic = malloc(size+1);\n\t\t\tfread(current_topic, 1, size, f);\n\t\t\tcurrent_topic[size] = '\\0';\n\n\t\t\tfclose(f);\n\t\t}\n\t}\n\n\treinitialize_contents();\n\tredraw_window();\n\n}\n\n#define SCROLL_AMOUNT 120\nstatic void _scroll_up(void) {\n\tscroll_offset -= SCROLL_AMOUNT;\n\tif (scroll_offset < 0) {\n\t\tscroll_offset = 0;\n\t}\n}\n\nstatic void _scroll_down(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\tint available_height = main_window->height - bounds.height - MENU_BAR_HEIGHT;\n\n\tif (available_height > contents->height) {\n\t\tscroll_offset = 0;\n\t} else {\n\t\tscroll_offset += SCROLL_AMOUNT;\n\t\tif (scroll_offset > contents->height - available_height) {\n\t\t\tscroll_offset = contents->height - available_height;\n\t\t}\n\t}\n}\n\n\nstatic void _menu_action_navigate(struct MenuEntry * entry) {\n\t/* go to entry->action */\n\tstruct MenuEntry_Normal * _entry = (void*)entry;\n\tnavigate(_entry->action);\n}\n\n#if 0\nstatic void _menu_action_back(struct MenuEntry * entry) {\n\t/* go back */\n}\n\nstatic void _menu_action_forward(struct MenuEntry * entry) {\n\t/* go forward */\n}\n#endif \n\nstatic void _menu_action_about(struct MenuEntry * entry) {\n\t/* Show About dialog */\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About Help Browser\\\" /usr/share/icons/48/help.png \\\"ToaruOS Help Browser\\\" \\\"© 2018-2020 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\tredraw_window();\n}\n\nstatic void redraw_window_callback(struct menu_bar * self) {\n\t(void)self;\n\tredraw_window();\n}\n\nint main(int argc, char * argv[]) {\n\n\tyctx = yutani_init();\n\tinit_decorations();\n\tmain_window = yutani_window_create(yctx, 640, 480);\n\tyutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2);\n\tctx = init_graphics_yutani_double_buffer(main_window);\n\n\ttt_font_thin         = tt_font_from_shm(\"sans-serif\");\n\ttt_font_bold         = tt_font_from_shm(\"sans-serif.bold\");\n\ttt_font_oblique      = tt_font_from_shm(\"sans-serif.italic\");\n\ttt_font_bold_oblique = tt_font_from_shm(\"sans-serif.bolditalic\");\n\ttt_font_mono         = tt_font_from_shm(\"monospace\");\n\n\tyutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, \"help\");\n\n\tmenu_bar.entries = menu_entries;\n\tmenu_bar.redraw_callback = redraw_window_callback;\n\n\tmenu_bar.set = menu_set_create();\n\n\tstruct MenuList * m = menu_create(); /* File */\n\tmenu_insert(m, menu_create_normal(\"exit\",NULL,\"Exit\", _menu_action_exit));\n\tmenu_set_insert(menu_bar.set, \"file\", m);\n\n\tm = menu_create(); /* Go */\n\tmenu_insert(m, menu_create_normal(\"home\",\"0_index.trt\",\"Home\",_menu_action_navigate));\n\tmenu_insert(m, menu_create_normal(\"bookmark\",\"special:contents\",\"Topics\",_menu_action_navigate));\n#if 0\n\t/* TODO: History */\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"back\",NULL,\"Back\",_menu_action_back));\n\tmenu_insert(m, menu_create_normal(\"forward\",NULL,\"Forward\",_menu_action_forward));\n#endif\n\tmenu_set_insert(menu_bar.set, \"go\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(\"help\",\"help-browser.trt\",\"Contents\",_menu_action_navigate));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"star\",NULL,\"About \" APPLICATION_TITLE,_menu_action_about));\n\tmenu_set_insert(menu_bar.set, \"help\", m);\n\n\tif (argc > 1) {\n\t\tnavigate(argv[1]);\n\t} else {\n\t\tnavigate(\"0_index.trt\");\n\t}\n\n\twhile (application_running) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->wid == main_window->wid) {\n\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'g':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'h':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == main_window->wid) {\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid);\n\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/* Menu bar */\n\t\t\t\t\t\t\tmenu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y);\n\n\t\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t\tdecor_get_bounds(main_window, &bounds);\n\n\t\t\t\t\t\t\tif (me->new_x >= 0 && me->new_x <= (int)main_window->width &&\n\t\t\t\t\t\t\t\tme->new_y > bounds.top_height + MENU_BAR_HEIGHT &&\n\t\t\t\t\t\t\t\tme->new_y < (int)main_window->height) {\n\t\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\t\t\t/* Scroll up */\n\t\t\t\t\t\t\t\t\t_scroll_up();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\t\t\t_scroll_down();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/hexify.c",
    "content": "/**\n * @brief hexify - Convert binary to hex.\n *\n * This is based on the output of xxd.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <ctype.h>\n#include <errno.h>\n#include <unistd.h>\n#include <string.h>\n#include <stdlib.h>\n\nvoid print_line(unsigned char * buf, size_t width, size_t sizer, size_t offset) {\n\tfprintf(stdout, \"%08zx: \", sizer);\n\tfor (size_t i = 0; i < width; ) {\n\t\tif (i >= offset) {\n\t\t\tfprintf(stdout, \"  \");\n\t\t} else {\n\t\t\tfprintf(stdout, \"%02x\", buf[i]);\n\t\t}\n\t\ti++;\n\t\tif (i == width) break; /* in case of odd width */\n\t\tif (i >= offset) {\n\t\t\tfprintf(stdout, \"   \");\n\t\t} else {\n\t\t\tfprintf(stdout, \"%02x \", buf[i]);\n\t\t}\n\t\ti++;\n\t}\n\tfprintf(stdout, \" \");\n\tfor (size_t i = 0; i < width; i++) {\n\t\tif (i >= offset) {\n\t\t\tfprintf(stdout, \" \");\n\t\t} else {\n\t\t\tif (isprint(buf[i])) {\n\t\t\t\tfprintf(stdout, \"%c\", buf[i]);\n\t\t\t} else {\n\t\t\t\tfprintf(stdout, \".\");\n\t\t\t}\n\t\t}\n\t}\n\tfprintf(stdout, \"\\n\");\n}\n\nstatic int stoih(size_t w, char c[w], size_t *out) {\n\t*out = 0;\n\tfor (size_t  i = 0; i < w; ++i) {\n\t\t(*out) <<= 4;\n\t\tif (c[i] >= '0' && c[i] <= '9') {\n\t\t\t*out |= (c[i] - '0');\n\t\t} else if (c[i] >= 'A' && c[i] <= 'F') {\n\t\t\t*out |= (c[i] - 'A' + 0xA);\n\t\t} else if (c[i] >= 'a' && c[i] <= 'f') {\n\t\t\t*out |= (c[i] - 'a' + 0xA);\n\t\t} else {\n\t\t\t*out = 0;\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"heixfy - convert to and from hexadecimal representation\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-w width] [-d] [file]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -w width  \\033[3mdisplay 'width' bytes per line\\033[0m\\n\"\n\t\t\t\"           \\033[3m(default is 16, max is 256)\\033[0m\\n\"\n\t\t\t\" -d        \\033[3mconvert output from hexify back to binary data\\033[0m\\n\"\n\t\t\t\" -?        \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tsize_t width = 16; /* TODO make configurable */\n\tint opt;\n\tint direction = 0;\n\n\twhile ((opt = getopt(argc, argv, \"?w:d\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tdefault:\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'w':\n\t\t\t\twidth = strtoul(optarg, NULL, 0);\n\t\t\t\tif (width == 0) width = 16;\n\t\t\t\tif (width > 256) {\n\t\t\t\t\tfprintf(stderr, \"%s: invalid width\\n\", argv[0]);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tdirection = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tFILE * f;\n\tchar * name;\n\n\tif (optind < argc) {\n\t\tf = fopen(argv[optind], \"r\");\n\t\tname = argv[optind];\n\t\tif (!f) goto _fail;\n\t} else {\n\t\tname = \"[stdin]\";\n\t\tf = stdin;\n\t}\n\n\tif (direction == 0) {\n\t\t/* Convert to hexadecimal */\n\n\t\tsize_t sizer = 0;\n\t\tsize_t offset = 0;\n\t\tunsigned char buf[width];\n\t\twhile (!feof(f)) {\n\t\t\tsize_t r = fread(buf+offset, 1, width-offset, f);\n\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\t\t\toffset += r;\n\n\t\t\tif (offset == width) {\n\t\t\t\tprint_line(buf, width, sizer, offset);\n\t\t\t\toffset = 0;\n\t\t\t\tsizer += width;\n\t\t\t}\n\t\t}\n\n\t\tif (offset != 0) {\n\t\t\tprint_line(buf, width, sizer, offset);\n\t\t}\n\n\t} else {\n\t\t/* Convert from hexify's output format */\n\t\tsize_t eoffset = 0;\n\t\tsize_t lineno = 1;\n\t\twhile (!feof(f)) {\n\t\t\t/* Read offset */\n\t\t\tchar offset_bytes[8];\n\t\t\tsize_t r = fread(&offset_bytes, 1, 8, f);\n\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\n\t\t\t/* Convert offset for verification */\n\t\t\tsize_t offset;\n\t\t\tif (stoih(8, offset_bytes, &offset)) {\n\t\t\t\tfprintf(stderr, \"%s: %s: syntax error (bad offset) on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\tfprintf(stderr, \"offset bytes: %8s\\n\", offset_bytes);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tif (offset != eoffset) {\n\t\t\t\tfprintf(stderr, \"%s: %s: offset mismatch on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\tfprintf(stderr, \"expected 0x%zx, got 0x%zx\\n\", offset, eoffset);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t/* Read \": \" */\n\t\t\tchar tmp[2];\n\t\t\tr = fread(&tmp, 1, 2, f);\n\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\n\t\t\tif (tmp[0] != ':' || tmp[1] != ' ') {\n\t\t\t\tfprintf(stderr, \"%s: %s: syntax error (unexpected characters after offset) on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t/* Read [width] characters */\n\t\t\tfor (size_t i = 0; i < width; ) {\n\t\t\t\tsize_t byte = 0;\n\t\t\t\tfor (size_t j = 0; i < width && j < 2; ++j, ++i) {\n\t\t\t\t\tr = fread(&tmp, 1, 2, f);\n\t\t\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\t\t\t\t\tif (tmp[0] == ' ' && tmp[1] == ' ') {\n\t\t\t\t\t\t/* done; return */\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\t\t\t\t\tif (stoih(2, tmp, &byte)) {\n\t\t\t\t\t\tfprintf(stderr, \"%s: %s: syntax error (bad byte) on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\t\t\tfprintf(stderr, \"byte bytes: %2s\\n\", tmp);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t\tfwrite(&byte, 1, 1, stdout);\n\t\t\t\t}\n\t\t\t\t/* Read space */\n\t\t\t\tr = fread(&tmp, 1, 1, f);\n\t\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\t\t\t\tif (tmp[0] != ' ') {\n\t\t\t\t\tfprintf(stderr, \"%s: %s: syntax error (unexpected characters after byte) on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\t\tfprintf(stderr, \"unexpected character: %c\\n\", tmp[0]);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tr = fread(&tmp, 1, 1, f);\n\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\t\t\tif (tmp[0] != ' ') {\n\t\t\t\tfprintf(stderr, \"%s: %s: syntax error (unexpected characters after bytes) on line %zu\\n\", argv[0], name, lineno);\n\t\t\t}\n\n\t\t\t/* Read correct number of characters, plus line feed */\n\t\t\tchar tmp2[width+2];\n\t\t\tr = fread(&tmp2, 1, width+1, f);\n\t\t\tif (r == 0 && ferror(f)) goto _fail;\n\t\t\ttmp2[width+1] = '\\0';\n\t\t\tif (tmp2[width] != '\\n') {\n\t\t\t\tfprintf(stderr, \"%s: %s: syntax error: expected end of line, got garbage on line %zu\\n\", argv[0], name, lineno);\n\t\t\t\tfprintf(stderr, \"eol data: %s\\n\", tmp2);\n\t\t\t}\n\n\t\t\tlineno++;\n\t\t\teoffset += width;\n\t\t}\n\t}\n\n\treturn 0;\n\n_fail:\n\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], name, strerror(errno));\n\treturn 1;\n}\n"
  },
  {
    "path": "apps/highlight-source.krk",
    "content": "#!/bin/kuroko\nimport fileio, syntax.highlighter, kuroko\n\nlet code\nwith fileio.open(kuroko.argv[-1]) as f:\n    code = f.read()\n\nlet highlighter = syntax.highlighter.KurokoHighlighter(code)\n\nhighlighter.highlight()\nsyntax.highlighter.toTerminal(highlighter.process())\n"
  },
  {
    "path": "apps/hostname.c",
    "content": "/**\n * @brief hostname - Prints or sets the system hostname.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <string.h>\n\nint main(int argc, char * argv[]) {\n\tif ((argc > 1 && argv[1][0] == '-') || (argc < 2)) {\n\t\tchar tmp[256] = {0};\n\t\tgethostname(tmp, 255);\n\t\tprintf(\"%s\\n\", tmp);\n\t\treturn 0;\n\t} else {\n\t\tif (geteuid() != 0) {\n\t\t\tfprintf(stderr,\"Must be root to set hostname.\\n\");\n\t\t\treturn 1;\n\t\t} else {\n\t\t\tsethostname(argv[1], strlen(argv[1]));\n\t\t\tFILE * file = fopen(\"/etc/hostname\", \"w\");\n\t\t\tif (!file) {\n\t\t\t\treturn 1;\n\t\t\t} else {\n\t\t\t\tfprintf(file, \"%s\\n\", argv[1]);\n\t\t\t\tfclose(file);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/ifconfig.c",
    "content": "/**\n * @file  apps/ifconfig.c\n * @brief Network interface configuration tool.\n *\n * Manipulates and enumerates network interfaces.\n *\n * All of the APIs used in this tool are temporary and subject to change.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <ctype.h>\n#include <unistd.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n\nextern char * _argv_0;\n\nstatic void ip_ntoa(const uint32_t src_addr, char * out) {\n\tsnprintf(out, 16, \"%d.%d.%d.%d\",\n\t\t(src_addr & 0xFF000000) >> 24,\n\t\t(src_addr & 0xFF0000) >> 16,\n\t\t(src_addr & 0xFF00) >> 8,\n\t\t(src_addr & 0xFF));\n}\n\nstatic char * flagsToStr(uint32_t flags) {\n\tstatic char out[1024] = {0};\n\tchar * o = out;\n\n#define FLAG(f) if (flags & IFF_ ## f) { \\\n\tif (o != out) o += sprintf(o,\",\");          \\\n\to += sprintf(o,\"%s\",#f); }                  \\\n\n\tFLAG(UP)\n\tFLAG(BROADCAST)\n\tFLAG(DEBUG)\n\tFLAG(LOOPBACK)\n\tFLAG(RUNNING)\n\tFLAG(MULTICAST)\n\n\treturn out;\n}\n\nstatic int print_human_readable_size(char * _out, size_t s) {\n\tsize_t count = 5;\n\tchar * prefix = \"PTGMK\";\n\tfor (; count > 0 && *prefix; count--, prefix++) {\n\t\tsize_t base = 1UL << (count * 10);\n\t\tif (s >= base) {\n\t\t\tsize_t t = s / base;\n\t\t\treturn sprintf(_out, \"%zu.%1zu %cB\", t, (s - t * base) / (base / 10), *prefix);\n\t\t}\n\t}\n\treturn sprintf(_out, \"%d B\", (int)s);\n}\n\nstatic int open_netdev(const char * if_name) {\n\tchar if_path[100];\n\tsnprintf(if_path, 100, \"/dev/net/%s\", if_name);\n\treturn open(if_path, O_RDONLY);\n}\n\nstatic int print_interface(const char * if_name) {\n\tint netdev = open_netdev(if_name);\n\n\tif (netdev < 0) {\n\t\tperror(_argv_0);\n\t\treturn 1;\n\t}\n\n\tuint32_t flags = 0;\n\tioctl(netdev, SIOCGIFFLAGS, &flags);\n\tuint32_t mtu = 0;\n\tioctl(netdev, SIOCGIFMTU, &mtu);\n\n\tfprintf(stdout,\"%s: flags=%d<%s> mtu %d\\n\", if_name, flags, flagsToStr(flags), mtu);\n\n\t/* Get IPv4 address */\n\tuint32_t ip_addr = 0;\n\tif (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) {\n\t\tchar ip_str[16];\n\t\tip_ntoa(ntohl(ip_addr), ip_str);\n\t\tfprintf(stdout,\"        inet %s\", ip_str);\n\t\t/* Netmask ? */\n\t\tuint32_t netmask = 0;\n\t\tif (!ioctl(netdev, SIOCGIFNETMASK, &netmask)) {\n\t\t\tip_ntoa(ntohl(netmask), ip_str);\n\t\t\tfprintf(stdout, \"  netmask %s\", ip_str);\n\n\t\t\tuint32_t bcast = (ip_addr & netmask) | (~netmask);\n\t\t\tip_ntoa(ntohl(bcast), ip_str);\n\t\t\tfprintf(stdout, \"  broadcast %s\", ip_str);\n\t\t}\n\t\tfprintf(stdout,\"\\n\");\n\t}\n\n\tuint8_t ip6_addr[16];\n\tif (!ioctl(netdev, SIOCGIFADDR6, &ip6_addr)) {\n\t\t/* TODO inet6 address to nice string */\n\t\tfprintf(stdout,\"        inet6 %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\\n\",\n\t\t\tip6_addr[0], ip6_addr[1], ip6_addr[2], ip6_addr[3],\n\t\t\tip6_addr[4], ip6_addr[5], ip6_addr[6], ip6_addr[7],\n\t\t\tip6_addr[8], ip6_addr[9], ip6_addr[10], ip6_addr[11],\n\t\t\tip6_addr[12], ip6_addr[13], ip6_addr[14], ip6_addr[15]);\n\t}\n\n\t/* Get ethernet address */\n\tuint8_t mac_addr[6];\n\tif (!ioctl(netdev, SIOCGIFHWADDR, &mac_addr)) {\n\t\tfprintf(stdout,\"        ether %02x:%02x:%02x:%02x:%02x:%02x\\n\",\n\t\t\tmac_addr[0], mac_addr[1], mac_addr[2],\n\t\t\tmac_addr[3], mac_addr[4], mac_addr[5]);\n\t}\n\n\tnetif_counters_t counts;\n\tif (!ioctl(netdev, SIOCGIFCOUNTS, &counts)) {\n\t\tchar _buf[100];\n\t\tprint_human_readable_size(_buf, counts.rx_bytes);\n\t\tfprintf(stdout,\"        RX packets %zu  bytes %zu (%s)\\n\", counts.rx_count, counts.rx_bytes, _buf);\n\t\tprint_human_readable_size(_buf, counts.tx_bytes);\n\t\tfprintf(stdout,\"        TX packets %zu  bytes %zu (%s)\\n\", counts.tx_count, counts.tx_bytes, _buf);\n\t}\n\n\t/* TODO stats */\n\n\tfprintf(stdout,\"\\n\");\n\n\treturn 0;\n}\n\nstatic int print_all_interfaces(void) {\n\tint retval = 0;\n\n\t/* Read /dev/net for interfaces */\n\tDIR * d = opendir(\"/dev/net\");\n\tif (!d) {\n\t\tfprintf(stderr, \"%s: no network?\\n\", _argv_0);\n\t\treturn 1;\n\t}\n\n\tstruct dirent * ent;\n\twhile ((ent = readdir(d))) {\n\t\tif (ent->d_name[0] == '.') continue;\n\n\t\t/* Retrieve data for the interface and print the results. */\n\t\tif (print_interface(ent->d_name)) {\n\t\t\tretval = 1;\n\t\t}\n\t}\n\n\tclosedir(d);\n\treturn retval;\n}\n\nstatic int maybe_address(const char * s) {\n\t/* Our inet_addr is not great, let's help it a little... */\n\tint dots = 0;\n\tfor (;*s;s++) {\n\t\tif (*s == '.') {\n\t\t\tdots++;\n\t\t\tif (dots > 3) return 0;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isdigit(*s)) return 0;\n\t}\n\n\treturn dots == 3;\n}\n\nstatic int parse_address(const char * cmd, const char * addr, in_addr_t * out) {\n\tif (!addr) {\n\t\tfprintf(stderr, \"%s: %s: expected argument\\n\", _argv_0, cmd);\n\t\treturn 1;\n\t}\n\tif (!maybe_address(addr)) {\n\t\tfprintf(stderr, \"%s: %s: '%s' doesn't look like a valid address\\n\", _argv_0, cmd, addr);\n\t\treturn 1;\n\t}\n\n\t*out = inet_addr(addr);\n\treturn 0;\n}\n\nstatic int _set_address(int netdev, const char * cmd, const char * arg, const char * ioctlstr, unsigned long ioctltype) {\n\tint status;\n\tin_addr_t ip;\n\tif ((status = parse_address(cmd, arg, &ip))) return status;\n\tif ((status = ioctl(netdev, ioctltype, &ip))) { perror(ioctlstr); return status; }\n\treturn 0;\n}\n\n#define set_address(cmd, arg, itype) _set_address(netdev, cmd, arg, #itype, itype)\n#define command_with_address(cmd, itype) if (!strcmp(argv[i], cmd)) { if (_set_address(netdev, argv[i], argv[i+1], #itype, itype)) { return 1; } continue; }\n\nint main(int argc, char * argv[]) {\n\t/* Figure out what we're trying to do. */\n\tif (argc < 2) return print_all_interfaces();\n\n\t/* Handle (ignore) some common commands */\n\tif (!strcmp(argv[1], \"up\") || !strcmp(argv[1],\"down\")) {\n\t\tfprintf(stderr, \"%s: 'up' and 'down' commands are unsupported\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* If there is an interface name and nothing else, print and be done with it. */\n\tif (argc == 2) return print_interface(argv[1]);\n\n\t/* All other options here require a leading interface. */\n\tint netdev = open_netdev(argv[1]);\n\n\tif (netdev < 0) {\n\t\tperror(argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* Now let's figure out what we want to do with remaining options */\n\tint collected_address = 0;\n\n\tfor (int i = 2; i < argc; ++i) {\n\t\t/* Is this argument an address? */\n\t\tif (maybe_address(argv[i])) {\n\t\t\t/* Try to set IPv4 address */\n\t\t\tif (collected_address) {\n\t\t\t\tfprintf(stderr, \"%s: expected at most one bare address, but found a second\\n\", argv[0]);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (set_address(\"inet\", argv[i], SIOCSIFADDR)) return 1;\n\t\t\tcollected_address = 1;\n\t\t} else {\n\t\t\tcommand_with_address(\"netmask\", SIOCSIFNETMASK);\n\t\t\tcommand_with_address(\"gw\", SIOCSIFGATEWAY);\n\t\t\tcommand_with_address(\"gateway\", SIOCSIFGATEWAY);\n\t\t\tcommand_with_address(\"inet\", SIOCSIFADDR);\n\t\t\tfprintf(stderr, \"%s: '%s' is not an understood command\\n\", argv[0], argv[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/imgviewer.c",
    "content": "/**\n * @brief imgviewer - Display bitmaps in a graphical window.\n *\n * This is probably the 4th time I've (re)written a version of\n * this application... This uses the libtoaru_graphics sprite\n * functionality to load images, so it will support whatever\n * that ends up supporting - which at the time of writing is\n * just bitmaps of various types.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <libgen.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n\n/* Pointer to graphics memory */\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\n\nstatic int decor_left_width = 0;\nstatic int decor_top_height = 0;\nstatic int decor_right_width = 0;\nstatic int decor_bottom_height = 0;\nstatic int decor_width = 0;\nstatic int decor_height = 0;\n\nint left   = 40;\nint top    = 40;\nint width  = 300;\nint height = 300;\n\nint current_scale = 100;\n\nsprite_t img = {0};\n\n#define APPLICATION_TITLE \"Image Viewer\"\nstatic char window_title[1024] = APPLICATION_TITLE;\n\nvoid usage(char * argv[]) {\n\tprintf(\n\t\t\t\"Image Viewer - Shows images.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s \\033[3mimage\\033[0m\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -? --help      \\033[3mShow this help message.\\033[0m\\n\",\n\t\t\targv[0]);\n}\n\nstatic void decors() {\n\tif (current_scale != 100) {\n\t\tchar tmp[1100];\n\t\tsnprintf(tmp, 1100, \"%s [%d%%]\", window_title, current_scale);\n\t\trender_decorations(window, ctx, tmp);\n\t} else {\n\t\trender_decorations(window, ctx, window_title);\n\t}\n}\n\nvoid redraw() {\n\tuint32_t dark  = rgb(107,107,107);\n\tuint32_t light = rgb(147,147,147);\n\tuint32_t black = rgb(0,0,0);\n\n\tint calc_width = img.width * (current_scale / 100.0);\n\tint calc_height = img.height * (current_scale / 100.0);\n\n\tint image_left  = width / 2 - calc_width / 2;\n\tint image_right = image_left + calc_width; /* TODO scaling */\n\tint image_top   = height / 2 - calc_height / 2;\n\tint image_bot   = image_top + calc_height;\n\n\tfor (int y = 0; y < height; ++y) {\n\t\tfor (int x = 0; x < width; ++x) {\n\t\t\tuint32_t color = (x < image_left || x >= image_right || y < image_top || y >= image_bot) ? black :\n\t\t\t((((y / 10) % 2 == 0) ^ ((x / 10) % 2 == 0)) ? dark : light);\n\t\t\tGFX(ctx,x+decor_left_width,y+decor_top_height) = color;\n\t\t}\n\t}\n\n\tif (current_scale != 100) {\n\t\tdraw_sprite_scaled(ctx, &img, decor_left_width + image_left, decor_top_height + image_top, calc_width, calc_height);\n\t} else {\n\t\tdraw_sprite(ctx, &img, decor_left_width + image_left, decor_top_height + image_top);\n\t}\n\tdecors();\n\tflip(ctx);\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\twidth  = w - decor_left_width - decor_right_width;\n\theight = h - decor_top_height - decor_bottom_height;\n\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n\n\tyutani_flip(yctx, window);\n}\n\nstatic int one_fifth(int scale) {\n\tint out = scale * 0.05;\n\tif (out) return out;\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\n\tstatic struct option long_opts[] = {\n\t\t{\"help\",   no_argument,       0, '?'},\n\t\t{0,0,0,0}\n\t};\n\n\tif (argc > 1) {\n\t\t/* Read some arguments */\n\t\tint index, c;\n\t\twhile ((c = getopt_long(argc, argv, \"h\", long_opts, &index)) != -1) {\n\t\t\tif (!c) {\n\t\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\t\tc = long_opts[index].val;\n\t\t\t\t}\n\t\t\t}\n\t\t\tswitch (c) {\n\t\t\t\tcase 'h':\n\t\t\t\t\tusage(argv);\n\t\t\t\t\texit(0);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (optind >= argc) {\n\t\tusage(argv);\n\t\treturn 1;\n\t}\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\tint status = load_sprite(&img, argv[optind]);\n\tif (status) {\n\t\tfprintf(stderr, \"%s: failed to open image %s\\n\", argv[0], argv[optind]);\n\t\treturn 1;\n\t}\n\n\twidth  = img.width  < 300 ? 300 : img.width;\n\theight = img.height < 300 ? 300 : img.height;\n\n\twindow = yutani_window_create(yctx, width + decor_width, height + decor_height);\n\tyutani_window_move(yctx, window, left, top);\n\n\tsnprintf(window_title, 1023, \"%s - \" APPLICATION_TITLE, basename(argv[1]));\n\n\tyutani_window_advertise_icon(yctx, window, window_title, \"image\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\tredraw();\n\tyutani_flip(yctx, window);\n\n\tint playing = 1;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\t/* just decorations should be fine */\n\t\t\t\tdecors();\n\t\t\t\tflip(ctx);\n\t\t\t\tyutani_flip(yctx, window);\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win && win == window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tdecors();\n\t\t\t\t\t\t\tflip(ctx);\n\t\t\t\t\t\t\tyutani_flip(yctx, window);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\t\tcurrent_scale = current_scale + one_fifth(current_scale);\n\t\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\t\tyutani_flip(yctx, window);\n\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\t\tcurrent_scale = current_scale - one_fifth(current_scale);\n\t\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\t\tyutani_flip(yctx, window);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/init.c",
    "content": "/**\n * @brief init - First process.\n *\n * `init` calls startup scripts and then waits for them to complete.\n * It also waits for orphaned proceses so they can be collected.\n *\n * `init` itself is statically-linked, so minimizing libc dependencies\n * is worthwhile as it reduces to the total size of init itself, which\n * remains in memory throughout the entire lifetime of the OS.\n *\n * Startup scripts for init are stored in /etc/startup.d and are run\n * in sorted alphabetical order. It is generally recommended that these\n * startup scripts be named with numbers at the front to ensure easy\n * ordering. This system of running a set of scripts on startup is\n * somewhat similar to how sysvinit worked, but no claims of\n * compatibility are made.\n *\n * Startup scripts can be any executable binary. Shell scripts are\n * generally used to allow easy editing, but you could also use\n * a binary (even a dynamically linked one) as a startup script.\n * `init` will wait for each startup script (that is, it will wait for\n * the original process it started to exit) before running the next one.\n * So if you wish to run daemons, be sure to fork them off and then\n * exit so that the rest of the startup process can continue.\n *\n * When the last startup script finishes, `init` will reboot the system.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n\n#include <dirent.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <syscall.h>\n#include <unistd.h>\n#include <wait.h>\n#include <sys/wait.h>\n\n#define INITD_PATH \"/etc/startup.d\"\n\n/* Initialize fd 0, 1, 2 */\nvoid set_console(void) {\n\tsyscall_open(\"/dev/null\", 0, 0);\n\tsyscall_open(\"/dev/null\", 1, 0);\n\tsyscall_open(\"/dev/null\", 1, 0);\n}\n\n/* Run a startup script and wait for it to finish */\nint start_options(char * args[]) {\n\n\t/* Fork child to run script */\n\tint cpid = syscall_fork();\n\n\t/* Child process... */\n\tif (!cpid) {\n\t\t/* Pass environment from init to child */\n\t\tsyscall_execve(args[0], args, environ);\n\t\t/* exec failed, exit this subprocess */\n\t\tsyscall_exit(0);\n\t}\n\n\t/* Wait for the child process to finish */\n\tint pid = 0;\n\tdo {\n\t\t/*\n\t\t * Wait, ignoring kernel threads\n\t\t * (which also end up as children to init)\n\t\t */\n\t\tpid = waitpid(-1, NULL, WNOKERN);\n\n\t\tif (pid == -1 && errno == ECHILD) {\n\t\t\t/* There are no more children */\n\t\t\tbreak;\n\t\t}\n\n\t\tif (pid == cpid) {\n\t\t\t/* The child process finished */\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Continue while no error (or error was \"interrupted\") */\n\t} while ((pid > 0) || (pid == -1 && errno == EINTR));\n\n\treturn cpid;\n}\n\nint main(int argc, char * argv[]) {\n\t/* Initialize stdin/out/err */\n\tset_console();\n\n\t/* Get directory listing for /etc/startup.d */\n\tint initd_dir = syscall_open(INITD_PATH, 0, 0);\n\tif (initd_dir < 0) {\n\t\t/* No init scripts; try to start getty as a fallback */\n\t\tstart_options((char *[]){\"/bin/getty\",NULL});\n\t} else {\n\t\tint count = 0, i = 0, ret = 0;\n\n\t\t/* Figure out how many entries we have with a dry run */\n\t\tdo {\n\t\t\tstruct dirent ent;\n\t\t\tret = syscall_readdir(initd_dir, ++count, &ent);\n\t\t} while (ret > 0);\n\n\t\t/* Read each directory entry */\n\t\tstruct dirent entries[count];\n\t\tdo {\n\t\t\tsyscall_readdir(initd_dir, i, &entries[i]);\n\t\t\ti++;\n\t\t} while (i < count);\n\n\t\t/* Sort the directory entries */\n\t\tint comparator(const void * c1, const void * c2) {\n\t\t\tconst struct dirent * d1 = c1;\n\t\t\tconst struct dirent * d2 = c2;\n\t\t\treturn strcmp(d1->d_name, d2->d_name);\n\t\t}\n\t\tqsort(entries, count, sizeof(struct dirent), comparator);\n\n\t\t/* Run scripts */\n\t\tfor (int i = 0; i < count; ++i) {\n\t\t\tif (entries[i].d_name[0] != '.') {\n\t\t\t\tchar path[256];\n\t\t\t\tsprintf(path, \"/etc/startup.d/%s\", entries[i].d_name);\n\t\t\t\tstart_options((char *[]){path, NULL});\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Self-explanatory */\n\tsyscall_reboot();\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/insmod.c",
    "content": "/**\n * @brief insmod - Load kernel module\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016 K. Lange\n */\n#include <stdio.h>\n#include <errno.h>\n#include <string.h>\n#include <sys/sysfunc.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"Usage: %s <modulepath> [ARGS...]\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tint status = sysfunc(TOARU_SYS_FUNC_INSMOD, &argv[1]);\n\tif (status != 0) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[1], strerror(errno));\n\t\treturn status;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/irc.c",
    "content": "/**\n * @brief irc - Internet Relay Chat client\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <termios.h>\n#include <va_list.h>\n#include <time.h>\n#include <string.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n#include <sys/fswait.h>\n\n#define _ITALIC \"\\033[3m\"\n#define _END    \"\\033[0m\\n\"\n\n#define VERSION_STRING \"0.3.0\"\n\n/* Theming */\n#define TIME_FMT \"%02d:%02d:%02d\"\n#define TIME_ARGS hr, min, sec\n\nstatic char * nick = \"toaru-user\";\nstatic char * host = NULL;\nstatic char * pass = NULL;\nstatic unsigned short port = 6667;\n\nstatic char * channel = NULL;\n\nstatic int sock_fd;\nstatic FILE * sock_r;\nstatic FILE * sock_w;\n\nstruct color_pair {\n\tint fg;\n\tint bg;\n};\n\nstatic void show_usage(int argc, char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"irc - Terminal IRC client.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-h] [-p port] [-n nick] host\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -p port \" _ITALIC \"Specify port to connect to\" _END\n\t\t\t\" -P pass \" _ITALIC \"Password for server connection\" _END\n\t\t\t\" -n nick \" _ITALIC \"Specify a nick to use\" _END\n\t\t\t\" -h      \" _ITALIC \"Print this help message\" _END\n\t\t\t\"\\n\", argv[0]);\n\texit(1);\n}\n\nstatic struct termios old;\n\nstatic void set_unbuffered() {\n\ttcgetattr(fileno(stdin), &old);\n\tstruct termios new = old;\n\tnew.c_lflag &= (~ICANON & ~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\nstatic void set_buffered() {\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n}\n\nstatic int user_color(char * user) {\n\tint i = 0;\n\twhile (*user) {\n\t\ti += *user;\n\t\tuser++;\n\t}\n\ti = i % 5;\n\tswitch (i) {\n\t\tcase 0: return 2;\n\t\tcase 1: return 3;\n\t\tcase 2: return 4;\n\t\tcase 3: return 6;\n\t\tcase 4: return 10;\n\t}\n\treturn 0;\n}\n\nstatic int color_pairs[] = {\n\t15, 0, 4, 2, 9, 1, 5, 3, 11, 10, 6, 14, 12, 13, 8, 7\n};\n\nstatic struct color_pair irc_color_to_pair(int fg, int bg) {\n\tint _fg = 0;\n\tint _bg = 0;\n\tif (fg == -1) {\n\t\t_fg = -1;\n\t} else {\n\t\t_fg = color_pairs[fg % 16];\n\t}\n\tif (bg == -1) {\n\t\t_bg = -1;\n\t} else {\n\t\t_bg = color_pairs[bg % 16];\n\t}\n\treturn (struct color_pair){_fg, _bg};\n}\n\nstatic void get_time(int * h, int * m, int * s) {\n\ttime_t rawtime;\n\ttime(&rawtime);\n\tstruct tm *tm_struct = localtime(&rawtime);\n\n\t*h = tm_struct->tm_hour;\n\t*m = tm_struct->tm_min;\n\t*s = tm_struct->tm_sec;\n}\n\nstatic void print_color(struct color_pair t) {\n\tfprintf(stdout, \"\\033[\");\n\tif (t.fg == -1) {\n\t\tfprintf(stdout,\"39\");\n\t} else if (t.fg > 15) {\n\t\t/* TODO */\n\t} else if (t.fg > 7) {\n\t\tfprintf(stdout,\"9%d\", t.fg - 8);\n\t} else {\n\t\tfprintf(stdout,\"3%d\", t.fg);\n\t}\n\tfprintf(stdout, \";\");\n\tif (t.bg == -1) {\n\t\tfprintf(stdout, \"49\");\n\t} else if (t.bg > 15) {\n\t\t/* TODO */\n\t} else if (t.bg > 7) {\n\t\tfprintf(stdout,\"10%d\", t.bg - 8);\n\t} else {\n\t\tfprintf(stdout,\"4%d\", t.bg);\n\t}\n\tfprintf(stdout, \"m\");\n\tfflush(stdout);\n}\n\nstatic void WRITE(const char * fmt, ...) {\n\n\tint bold_on = 0;\n\tint italic_on = 0;\n\n\tva_list args;\n\tva_start(args, fmt);\n\tchar * tmp;\n\tvasprintf(&tmp, fmt, args);\n\tva_end(args);\n\n\tstruct winsize w;\n\tioctl(0, TIOCGWINSZ, &w);\n\tfprintf(stdout,\"\\033[%d;1H\\033[K\", w.ws_row);\n\n\tint line_feed_pending = 0;\n\tchar * c = tmp;\n\n\twhile (*c) {\n\t\tif (*c == '\\n') {\n\t\t\tif (line_feed_pending) {\n\t\t\t\t/* Print line feed */\n\t\t\t\tfprintf(stdout, \"\\n\");\n\t\t\t}\n\t\t\tline_feed_pending = 1;\n\t\t\tc++;\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tif (line_feed_pending) {\n\t\t\t\tline_feed_pending = 0;\n\t\t\t\t/* Print line feed */\n\t\t\t\tfprintf(stdout, \"\\n\");\n\t\t\t}\n\t\t}\n\t\tif (*c == 0x03) {\n\t\t\tc++;\n\t\t\tint i = -1;\n\t\t\tint j = -1;\n\t\t\tif (*c >= '0' && *c <= '9') {\n\t\t\t\ti = (*c - '0');\n\t\t\t\tc++;\n\t\t\t}\n\t\t\tif (*c >= '0' && *c <= '9') {\n\t\t\t\ti *= 10;\n\t\t\t\ti += (*c - '0');\n\t\t\t\tc++;\n\t\t\t}\n\t\t\tif (*c == ',') {\n\t\t\t\tc++;\n\t\t\t\tif (*c >= '0' && *c <= '9') {\n\t\t\t\t\tj = (*c - '0');\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t\tif (*c >= '0' && *c <= '9') {\n\t\t\t\t\tj *= 10;\n\t\t\t\t\tj = (*c - '0');\n\t\t\t\t\tc++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstruct color_pair t = irc_color_to_pair(i, j);\n\t\t\tprint_color(t);\n\t\t\tcontinue;\n\t\t}\n\t\tif (*c == 0x02) {\n\t\t\tif (bold_on) {\n\t\t\t\tfprintf(stdout,\"\\033[22m\");\n\t\t\t\tbold_on = 0;\n\t\t\t} else {\n\t\t\t\tfprintf(stdout,\"\\033[1m\");\n\t\t\t\tbold_on = 1;\n\t\t\t}\n\t\t\tc++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (*c == 0x16) {\n\t\t\tif (italic_on) {\n\t\t\t\tfprintf(stdout,\"\\033[23m\");\n\t\t\t\titalic_on = 0;\n\t\t\t} else {\n\t\t\t\tfprintf(stdout,\"\\033[3m\");\n\t\t\t\titalic_on = 1;\n\t\t\t}\n\t\t\tc++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (*c == 0x0f) {\n\t\t\tfprintf(stdout, \"\\033[0m\");\n\t\t\tc++;\n\t\t\tbold_on = 0;\n\t\t\titalic_on = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfprintf(stdout, \"%c\", *c);\n\t\tc++;\n\t}\n\tif (line_feed_pending) {\n\t\tfprintf(stdout, \"\\033[0m\\033[K\\n\");\n\t}\n\tfflush(stdout);\n\tfree(tmp);\n}\n\nstatic void handle(char * line) {\n\tchar * c = line;\n\twhile (c < line + strlen(line)) {\n\t\tchar * e = strstr(c, \"\\r\\n\");\n\t\tif (e > line + strlen(line)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!e) {\n\t\t\t/* Write c */\n\t\t\tWRITE(c);\n\t\t\tgoto next;\n\t\t}\n\n\t\t*e = '\\0';\n\n\t\tif (strstr(c, \"PING\") == c) {\n\t\t\tchar * t = strstr(c, \":\");\n\t\t\tfprintf(sock_w, \"PONG %s\\r\\n\", t);\n\t\t\tfflush(sock_w);\n\t\t\tgoto next;\n\t\t}\n\n\t\tchar * user, * command, * channel, * message;\n\n\t\tuser = c;\n\t\tif (user[0] == ':') {\n\t\t\tuser++;\n\t\t}\n\n\t\tcommand = strstr(user, \" \");\n\t\tif (!command) {\n\t\t\tWRITE(\"%s\\n\", user);\n\t\t\tgoto next;\n\t\t}\n\t\tcommand[0] = '\\0';\n\t\tcommand++;\n\n\t\tchannel = strstr(command, \" \");\n\t\tif (!channel) {\n\t\t\tWRITE(\"%s %s\\n\", user, command);\n\t\t\tgoto next;\n\t\t}\n\t\tchannel[0] = '\\0';\n\t\tchannel++;\n\n\t\tmessage = strstr(channel, \" \");\n\t\tif (message) {\n\t\t\tmessage[0] = '\\0';\n\t\t\tmessage++;\n\t\t\tif (message[0] == ':') {\n\t\t\t\tmessage++;\n\t\t\t}\n\t\t}\n\n\t\tint hr, min, sec;\n\t\tget_time(&hr, &min, &sec);\n\n\t\tif (!strcmp(command, \"PRIVMSG\")) {\n\t\t\tif (!message) continue;\n\t\t\tchar * t = strstr(user, \"!\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\t\t\tt = strstr(user, \"@\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\n\t\t\tif (strstr(message, \"\\001ACTION \") == message) {\n\t\t\t\tmessage = message + 8;\n\t\t\t\tchar * x = strstr(message, \"\\001\");\n\t\t\t\tif (x) *x = '\\0';\n\t\t\t\tWRITE(TIME_FMT \" \\002* \\003%d%s\\003\\002 %s\\n\", TIME_ARGS, user_color(user), user, message);\n\t\t\t} else {\n\t\t\t\tWRITE(TIME_FMT \" \\00314<\\003%d%s\\00314>\\003 %s\\n\", TIME_ARGS, user_color(user), user, message);\n\t\t\t}\n\t\t} else if (!strcmp(command, \"332\")) {\n\t\t\tif (!message) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Topic */\n\t\t} else if (!strcmp(command, \"JOIN\")) {\n\t\t\tchar * t = strstr(user, \"!\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\t\t\tt = strstr(user, \"@\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\t\t\tif (channel[0] == ':') { channel++; }\n\n\t\t\tWRITE(TIME_FMT \" \\00312-\\003!\\00312-\\00311 %s\\003 has joined \\002%s\\n\", TIME_ARGS, user, channel);\n\t\t} else if (!strcmp(command, \"PART\")) {\n\t\t\tchar * t = strstr(user, \"!\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\t\t\tt = strstr(user, \"@\");\n\t\t\tif (t) { t[0] = '\\0'; }\n\t\t\tif (channel[0] == ':') { channel++; }\n\n\t\t\tWRITE(TIME_FMT \" \\00312-\\003!\\00312\\003-\\00310 %s\\003 has left \\002%s\\n\", TIME_ARGS, user, channel);\n\t\t} else if (!strcmp(command, \"372\")) {\n\t\t\tWRITE(TIME_FMT \" \\00314%s\\003 %s\\n\", TIME_ARGS, user, message ? message : \"\");\n\t\t} else if (!strcmp(command, \"376\")) {\n\t\t\t/* End of MOTD */\n\t\t\tWRITE(TIME_FMT \" \\00314%s (end of MOTD)\\n\", TIME_ARGS, user);\n\t\t} else {\n\t\t\tWRITE(TIME_FMT \" \\00310%s %s %s %s\\n\", TIME_ARGS, user, command, channel, message ? message : \"\");\n\t\t}\n\nnext:\n\t\tif (!e) break;\n\t\tc = e + 2;\n\t}\n}\n\nstatic void redraw_buffer(char * buf) {\n\tstruct winsize w;\n\tioctl(0, TIOCGWINSZ, &w);\n\n\tchar tmp[1024];\n\tsize_t left = snprintf(tmp,1024,\" [%s] \", channel ? channel : \"(status)\");\n\tsize_t avail = w.ws_col - left - 1;\n\tsize_t buflen = strlen(buf);\n\tchar * from = buf;\n\n\tif (buflen >= avail) {\n\t\tfrom = buf + (buflen - avail);\n\t}\n\n\tfprintf(stdout,\"\\033[%d;1H%s%s\\033[K\\033[?25h\", w.ws_row, tmp, from);\n\tfflush(stdout);\n}\n\nvoid handle_input(char * buf) {\n\tfflush(stdout);\n\tif (strstr(buf, \"/help\") == buf) {\n\t\tWRITE(\"[help] help text goes here\\n\");\n\t} else if (strstr(buf, \"/quit\") == buf) {\n\t\tchar * m = strstr(buf, \" \"); if (m) m++;\n\t\tfprintf(sock_w, \"QUIT :%s\\r\\n\", m ? m : \"https://github.com/klange/toaruos\");\n\t\tfflush(sock_w);\n\t\tfprintf(stderr,\"\\033[0m\\n\");\n\t\tset_buffered();\n\t\texit(0);\n\t} else if (strstr(buf,\"/part\") == buf) {\n\t\tif (!channel) {\n\t\t\tfprintf(stderr, \"Not in a channel.\\n\");\n\t\t\treturn;\n\t\t}\n\t\tchar * m = strstr(buf, \" \"); if (m) m++;\n\t\tfprintf(sock_w, \"PART %s%s%s\\r\\n\", channel, m ? \" :\" : \"\", m ? m : \"\");\n\t\tfflush(sock_w);\n\t\tfree(channel);\n\t\tchannel = NULL;\n\t} else if (strstr(buf,\"/join \") == buf) {\n\t\tchar * m = strstr(buf, \" \"); if (m) m++;\n\t\tfprintf(sock_w, \"JOIN %s\\r\\n\", m);\n\t\tfflush(sock_w);\n\t\tchannel = strdup(m);\n\t} else if (strstr(buf, \"/\") == buf) {\n\t\tWRITE(\"[system] Unknown command: %s\\n\", buf);\n\t} else {\n\t\tint hr, min, sec;\n\t\tget_time(&hr, &min, &sec);\n\t\tWRITE(\"%02d:%02d:%02d \\00314<\\003\\002%s\\002\\00314>\\003 %s\\n\", hr, min, sec, nick, buf);\n\t\tfprintf(sock_w, \"PRIVMSG %s :%s\\r\\n\", channel, buf);\n\t}\n\tredraw_buffer(\"\");\n}\n\nint main(int argc, char * argv[]) {\n\n\t/* Option parsing */\n\tint c;\n\twhile ((c = getopt(argc, argv, \"?hp:n:P:\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'n':\n\t\t\t\tnick = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'P':\n\t\t\t\tpass = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'p':\n\t\t\t\tport = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\tcase '?':\n\t\t\tdefault:\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind >= argc) {\n\t\tshow_usage(argc, argv);\n\t}\n\n\thost = argv[optind];\n\n\t/* Connect */\n\t{\n\t\tsock_fd = socket(AF_INET, SOCK_STREAM, 0);\n\t\tfprintf(stderr, \"Looking up host...\\n\");\n\t\tstruct hostent * remote = gethostbyname(host);\n\t\tif (!remote) {\n\t\t\tperror(\"gethostbyname\");\n\t\t\treturn 1;\n\t\t}\n\t\tstruct sockaddr_in addr;\n\t\taddr.sin_family = AF_INET;\n\t\tmemcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length);\n\t\taddr.sin_port = htons(port);\n\t\tfprintf(stderr, \"Connecting...\\n\");\n\t\tif (connect(sock_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {\n\t\t\tperror(\"connect\");\n\t\t\treturn 1;\n\t\t}\n\t\tsock_r = fdopen(sock_fd, \"r\");\n\t\tsock_w = fdopen(sock_fd, \"w\");\n\t}\n\n\tset_unbuffered();\n\n\tfprintf(stdout, \" - Toaru IRC v %s - \\n\", VERSION_STRING);\n\tfprintf(stdout, \" Copyright 2015-2018 K. Lange\\n\");\n\tfprintf(stdout, \" https://toaruos.org - https://github.com/klange/toaruos\\n\");\n\tfprintf(stdout, \" \\n\");\n\tfprintf(stdout, \" For help, type /help\\n\");\n\n\tif (pass) {\n\t\tfprintf(sock_w, \"PASS %s\\r\\n\", pass);\n\t}\n\tfprintf(sock_w, \"NICK %s\\r\\nUSER %s * 0 :%s\\r\\n\", nick, nick, nick);\n\tfflush(sock_w);\n\n\tint fds[] = {sock_fd, STDIN_FILENO, sock_fd};\n\n\tchar net_buf[2048];\n\tmemset(net_buf, 0, 2048);\n\tint net_buf_p = 0;\n\n\tchar buf[1024] = {0};\n\tint buf_p = 0;\n\n\twhile (1) {\n\t\tint index = fswait2(2,fds,200);\n\n\t\tif (index == 1) {\n\t\t\t/* stdin */\n\t\t\tint c = fgetc(stdin);\n\t\t\tif (c < 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (c == 0x08 || c == 0x7F) {\n\t\t\t\t/* Remove from buffer */\n\t\t\t\tif (buf_p) {\n\t\t\t\t\tbuf[buf_p-1] = '\\0';\n\t\t\t\t\tbuf_p--;\n\t\t\t\t\tredraw_buffer(buf);\n\t\t\t\t}\n\t\t\t} else if (c == '\\n') {\n\t\t\t\t/* Send buffer */\n\t\t\t\thandle_input(buf);\n\t\t\t\tmemset(buf, 0, 1024);\n\t\t\t\tbuf_p = 0;\n\t\t\t} else {\n\t\t\t\t/* Append buffer, or check special keys */\n\t\t\t\tbuf[buf_p] = c;\n\t\t\t\tbuf_p++;\n\t\t\t\tredraw_buffer(buf);\n\t\t\t}\n\t\t} else if (index == 0) {\n\t\t\t/* network */\n\t\t\tdo {\n\t\t\t\tint c = fgetc(sock_r);\n\t\t\t\tif (c < 0) continue;\n\t\t\t\tnet_buf[net_buf_p] = c;\n\t\t\t\tnet_buf_p++;\n\t\t\t\tif (c == '\\n' || net_buf_p == 2046) {\n\t\t\t\t\thandle(net_buf);\n\t\t\t\t\tnet_buf_p = 0;\n\t\t\t\t\tmemset(net_buf, 0, 2048);\n\t\t\t\t\tredraw_buffer(buf);\n\t\t\t\t}\n\t\t\t} while (!_fwouldblock(sock_r));\n\t\t} else {\n\t\t\t/* timer */\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "apps/json-test.c",
    "content": "/**\n * @brief Test suite for the JSON library.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2019-2020 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n#include <toaru/json.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n\ntypedef struct JSON_Value Value;\n\nint main(int argc, char * argv[]) {\n\n\t{\n\t\tValue * result = json_parse(\"\\\"foo bar baz\\\"\");\n\t\tassert(result && result->type == JSON_TYPE_STRING);\n\t\tassert(strcmp(result->string, \"foo bar baz\") == 0);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"\\\"foo \\\\nbar baz\\\"\");\n\t\tassert(result && result->type == JSON_TYPE_STRING);\n\t\tassert(strcmp(result->string, \"foo \\nbar baz\") == 0);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"-123\");\n\t\tassert(result && result->type == JSON_TYPE_NUMBER);\n\t\tassert(fabs(result->number - (-123.0)) < 0.0001);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"2e3\");\n\t\tassert(result && result->type == JSON_TYPE_NUMBER);\n\t\tassert(fabs(result->number - (2000.0)) < 0.0001);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"0.124\");\n\t\tassert(result && result->type == JSON_TYPE_NUMBER);\n\t\tassert(fabs(result->number - (0.124)) < 0.0001);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"[ 1, 2, 3 ]\");\n\t\tassert(result && result->type == JSON_TYPE_ARRAY);\n\t\tassert(result->array->length == 3);\n\n\t\tassert(fabs(((Value *)list_dequeue(result->array)->value)->number - 1.0) < 0.0001);\n\t\tassert(fabs(((Value *)list_dequeue(result->array)->value)->number - 2.0) < 0.0001);\n\t\tassert(fabs(((Value *)list_dequeue(result->array)->value)->number - 3.0) < 0.0001);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"true\");\n\t\tassert(result && result->type == JSON_TYPE_BOOL);\n\t\tassert(result->boolean == 1);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"false\");\n\t\tassert(result && result->type == JSON_TYPE_BOOL);\n\t\tassert(result->boolean == 0);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"null\");\n\t\tassert(result && result->type == JSON_TYPE_NULL);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"torbs\");\n\t\tassert(!result);\n\t}\n\n\t{\n\t\tValue * result = json_parse(\"{\\\"foo\\\": \\\"bar\\\", \\\"bix\\\": 123}\");\n\t\tassert(result && result->type == JSON_TYPE_OBJECT);\n\n\t\thashmap_t * hash = result->object;\n\t\tassert(hashmap_get(hash, \"foo\"));\n\t\tassert(((Value *)hashmap_get(hash, \"foo\"))->type == JSON_TYPE_STRING);\n\t\tassert(strcmp(((Value *)hashmap_get(hash, \"foo\"))->string, \"bar\") == 0);\n\t\tassert(((Value *)hashmap_get(hash, \"bix\"))->type == JSON_TYPE_NUMBER);\n\t\tassert(fabs(((Value *)hashmap_get(hash, \"bix\"))->number - 123.0) < 0.00001);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/julia.c",
    "content": "/**\n * @brief julia - Julia Fractal Generator\n *\n * Displays Julia fractals in a window. Use the keyboard\n * to navigate, switch palettes, and change parameters.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <math.h>\n#include <libgen.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/markup_text.h>\n\n#define GFX_(xpt, ypt) (GFX(ctx,xpt+decor_left_width,ypt+decor_top_height))\n\nstatic char * app_name = \"Julia Fractals\";\nstatic char * app_desc = \"Julia fractal generator\";\nstatic char * app_icon = \"julia\";\n\n/* Pointer to graphics memory */\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\n\nstatic int decor_left_width = 0;\nstatic int decor_top_height = 0;\nstatic int decor_right_width = 0;\nstatic int decor_bottom_height = 0;\nstatic int decor_width = 0;\nstatic int decor_height = 0;\n\n\n/* Julia fractals elements */\ndouble conx = -0.752;  /* real part of c */\ndouble cony = 0.117;    /* imag part of c */\ndouble expx = 0.0;\ndouble expy = 0.0;\ndouble expz = 1.0; /* scale */\ndouble Maxx = 2;      /* X bounds */\ndouble Minx = -2;\ndouble Maxy = 1;      /* Y bounds */\ndouble Miny = -1;\ndouble pixcorx;       /* Internal values */\ndouble pixcory;\ndouble rotation = 4.1888; /* Blue */\nint maxiter = 1000; /* Iteration levels */\nint explore_mode = 0;\n\nuint32_t * palette = NULL;\n\nstatic uint32_t hsv_to_rgb(float h, float s, float v) {\n\tfloat c  = v * s;\n\tfloat hp = fmod(h, 2 * M_PI);\n\tfloat x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0));\n\tfloat m = v - c;\n\tfloat rp, gp, bp;\n\tif (hp <= 1.0472)      { rp = c; gp = x; bp = 0; }\n\telse if (hp <= 2.0944) { rp = x; gp = c; bp = 0; }\n\telse if (hp <= 3.1416) { rp = 0; gp = c; bp = x; }\n\telse if (hp <= 4.1888) { rp = 0; gp = x; bp = c; }\n\telse if (hp <= 5.2360) { rp = x; gp = 0; bp = c; }\n\telse                   { rp = c; gp = 0; bp = x; }\n\treturn rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255);\n}\n\nstatic uint32_t hue_palette(int k) {\n\tdouble ratio = (double)k / (double)maxiter;\n\tdouble hue   = sin(ratio * M_PI / 2.0);\n\treturn hsv_to_rgb(4.18879 * hue + rotation, 1.0, 1.0);\n}\n\nstatic uint32_t rhue_palette(int k) {\n\tdouble ratio = (double)k / (double)maxiter;\n\tdouble hue   = sin(ratio * M_PI / 2.0);\n\treturn hsv_to_rgb(-4.18879 * hue + rotation, 1.0, 1.0);\n}\n\nstatic uint32_t bnw_palette(int k) {\n\treturn rgb(255 * k / maxiter, 255 * k / maxiter, 255 * k / maxiter);\n}\n\nstatic uint32_t mix(uint32_t base, uint32_t mixer, float ratio) {\n\treturn rgb(\n\t\t_RED(base) * (1.0 - ratio) + _RED(mixer) * (ratio),\n\t\t_GRE(base) * (1.0 - ratio) + _GRE(mixer) * (ratio),\n\t\t_BLU(base) * (1.0 - ratio) + _BLU(mixer) * (ratio));\n}\n\nstatic uint32_t wiki_palette(int k) {\n\tdouble ratio = (double)k / (double)maxiter;\n\n\tfor (int i = 0; i < 100; ++i) {\n\t\tif (ratio <= 0.025) return mix(rgb(14,21,101), rgb(40,100,200), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(40,100,200), rgb(90,200,225), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(90,200,225), rgb(255,255,255), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(255,255,255), rgb(255,255,100), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(255,255,100), rgb(255,255,0), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(255,255,0), rgb(255,120,0), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(255,120,0), rgb(255,0,0), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (ratio <= 0.025) return mix(rgb(255,0,0), rgb(0,0,0), ratio / 0.025);\n\t\tratio -= 0.025;\n\t\tratio /= 0.975;\n\t\tif (i < 99) {\n\t\t\tif (ratio <= 0.025) return mix(rgb(0,0,0), rgb(14,21,101), ratio / 0.025);\n\t\t\tratio -= 0.025;\n\t\t\tratio /= 0.975;\n\t\t}\n\t}\n\treturn rgb(0,0,0);\n}\n\nstatic uint32_t (*palette_funcs[])(int) = {\n\twiki_palette,\n\thue_palette,\n\trhue_palette,\n\tbnw_palette,\n};\n\nstatic int current_palette = 0;\n\nstatic void initialize_palette(void) {\n\tif (!palette) {\n\t\tpalette = malloc(sizeof(uint32_t) * (maxiter + 1));\n\t}\n\tfor (int k = 0; k < maxiter; ++k) {\n\t\tpalette[k] = palette_funcs[current_palette](k);\n\t}\n\tpalette[maxiter] = rgb(0,0,0);\n}\n\nstatic void next_palette(void) {\n\tcurrent_palette = (current_palette + 1) % (sizeof(palette_funcs) / sizeof(*palette_funcs));\n\tinitialize_palette();\n}\n\nint left   = 40;\nint top    = 40;\nint width  = 300;\nint height = 300;\n\n\nstatic uint32_t julia(int xpt, int ypt) {\n\tlong double x = (xpt * pixcorx + Minx) * expz + expx;\n\tlong double y = (Maxy - ypt * pixcory) * expz + expy;\n\tlong double xnew = 0;\n\tlong double ynew = 0;\n\n\tint k = 0;\n\tfor (k = 0; k < maxiter; k++) {\n\t\txnew = x * x - y * y + conx;\n\t\tynew = 2 * x * y     + cony;\n\t\tx    = xnew;\n\t\ty    = ynew;\n\t\tif ((x * x + y * y) > 4.0)\n\t\t\tbreak;\n\t}\n\n\treturn palette[k];\n}\n\nstatic uint32_t mandelbrot(int xpt, int ypt) {\n\tlong double x0 = (xpt * pixcorx + Minx) * expz + expx;\n\tlong double y0 = (Maxy - ypt * pixcory) * expz + expy;\n\tlong double x = 0;\n\tlong double y = 0;\n\tlong double xnew = 0;\n\tlong double ynew = 0;\n\n\tint k = 0;\n\tfor (k = 0; k < maxiter; k++) {\n\t\txnew = x * x - y * y + x0;\n\t\tynew = 2 * x * y + y0;\n\t\tx    = xnew;\n\t\ty    = ynew;\n\t\tif ((x * x + y * y) > 4.0)\n\t\t\tbreak;\n\t}\n\n\treturn palette[k];\n}\n\nuint32_t (*function)(int,int) = julia;\n\n#define T_I \"\\033[3m\"\n#define T_N \"\\033[0m\"\nvoid usage(char * argv[]) {\n\tprintf(\n\t\t\t\"%s.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-i \" T_I \"iterations\" T_N \"] [-x \" T_I \"minx\" T_N \"]\\n\"\n\t\t\t\"          [-X \" T_I \"maxx\" T_N \"] [-c \" T_I \"real\" T_N \"] [-C \" T_I \"imag\" T_N \"]\\n\"\n\t\t\t\"          [-W \" T_I \"width\" T_N \"] [-H \" T_I \"height\" T_N \"] [-h]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -i --iterations  \" T_I \"Number of iterations to run\" T_N \"\\n\"\n\t\t\t\" -x --center-x    \" T_I \"Center X\" T_N \"\\n\"\n\t\t\t\" -y --center-y    \" T_I \"Center Y\" T_N \"\\n\"\n\t\t\t\" -c --creal       \" T_I \"Real component of c\" T_N \"\\n\"\n\t\t\t\" -C --cimag       \" T_I \"Imaginary component of c\" T_N \"\\n\"\n\t\t\t\" -r --rotate      \" T_I \"Hue rotation for color mapping\" T_N \"\\n\"\n\t\t\t\" -W --width       \" T_I \"Window width\" T_N \"\\n\"\n\t\t\t\" -H --height      \" T_I \"Window height\" T_N \"\\n\"\n\t\t\t\" -h --help        \" T_I \"Show this help message.\" T_N \"\\n\",\n\t\t\tapp_desc,\n\t\t\targv[0]);\n}\n\nstatic void decors() {\n\trender_decorations(window, ctx, app_name);\n}\n\nstatic void do_line(gfx_context_t * ctx, int j) {\n\tfor (int i = 0; i < width; ++i) {\n\t\tGFX_(i,j) = function(i,j);\n\t}\n\tmemcpy(&GFXR(ctx,0,decor_top_height+j),&GFX(ctx,0,decor_top_height+j),ctx->stride);\n\tyutani_flip_region(yctx, window, decor_left_width, decor_top_height + j, width, 1);\n}\n\nstatic int step_res;\nstatic int step_n;\nstatic int step_y;\nstatic int step_i;\nstatic int processing = 0;\nstatic clock_t time_before;\n#define START_POINT -4\n\nvoid step_once(void);\n\nvoid start_processing(void) {\n\tdouble _x = Maxx - Minx;\n\tdouble _y = _x / width * height;\n\n\tMiny = 0 - _y / 2;\n\tMaxy = _y / 2;\n\n\tpixcorx = (Maxx - Minx) / width;\n\tpixcory = (Maxy - Miny) / height;\n\n\tstep_n = START_POINT;\n\tstep_y = 0;\n\tstep_i = 0;\n\tstep_res = 64;\n\n\tprocessing = 1;\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\tdecors();\n\n\ttime_before = clock();\n\tstep_once();\n}\n\nvoid draw_label(void) {\n\tclock_t time_after = clock();\n\tchar description[100];\n\tif (explore_mode) {\n\t\tsnprintf(description, 100, \"<i>x</i>=%g <i>y</i>=%g, <i>zoom</i>=%g×, %ld ms%s\", expx, expy, 1.0/expz, (time_after - time_before) / 1000, step_n == 0 ? \"*\" : \"\");\n\t} else {\n\t\tsnprintf(description, 100, \"<i>c</i> = %g + %g<i>i</i>, %ld ms%s\", conx, cony, (time_after - time_before) / 1000, step_n == 0 ? \"*\" : \"\");\n\t}\n\n\t/* Set up a clip box */\n\tgfx_context_t * tmp = init_graphics_subregion(ctx, decor_left_width, decor_top_height, width, height);\n\n\t/* Create a sprite to draw into */\n\tsprite_t * stmp = create_sprite(width, height, ALPHA_EMBEDDED);\n\tgfx_context_t * sctx = init_graphics_sprite(stmp);\n\n\t/* Draw shadow */\n\tdraw_fill(sctx, rgba(0,0,0,0));\n\tmarkup_draw_string(sctx, 2, height - 2, description, rgb(0,0,0));\n\tblur_context_box(sctx, 2);\n\tblur_context_box(sctx, 2);\n\n\t/* Paint it twice */\n\tdraw_sprite(tmp, stmp, 0, 0);\n\tdraw_sprite(tmp, stmp, 0, 0);\n\n\t/* Free the sprite part */\n\tfree(sctx);\n\tsprite_free(stmp);\n\n\t/* Now draw the white text */\n\tmarkup_draw_string(tmp, 2, height - 2, description, rgb(255,255,255));\n\n\t/* Free clip space */\n\tfree(tmp);\n\n\tflip(ctx);\n\tyutani_flip(yctx,window);\n}\n\nvoid step_once(void) {\n\tif (step_n < 0 && step_y > height) {\n\t\tflip(ctx);\n\t\tyutani_flip(yctx, window);\n\n\t\tstep_res /= 2;\n\t\tstep_y = 0;\n\t\tstep_i = 0;\n\t\tstep_n++;\n\t}\n\n\tif (step_n >= height) {\n\t\tprocessing = 0;\n\t\tdraw_label();\n\t\treturn;\n\t}\n\n\tif (step_n == 0) {\n\t\tdraw_label();\n\t}\n\n\tif (step_n < 0) {\n\t\tfor (int x = 0, i = 0; x < width; x += step_res, i++) {\n\t\t\tif ((step_n != START_POINT) && (step_i & 1) == 0 && (i & 1) == 0) continue;\n\n\t\t\tuint32_t c = function(x,step_y);\n\t\t\tfor (int _y = 0; _y < step_res && _y + step_y < height; _y++) {\n\t\t\t\tfor (int _x = 0; _x < step_res && _x + x < width; _x++) {\n\t\t\t\t\tGFX_(_x+x,_y+step_y) = c;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tstep_i += 1;\n\t\tstep_y += step_res;\n\t} else if (step_n % 2) {\n\t\tdo_line(ctx,height/2 + step_n/2);\n\t\tstep_n++;\n\t} else {\n\t\tdo_line(ctx,height/2 - step_n/2 - 1);\n\t\tstep_n++;\n\t}\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\twidth  = w - decor_left_width - decor_right_width;\n\theight = h - decor_top_height - decor_bottom_height;\n\n\tstart_processing();\n\tyutani_window_resize_done(yctx, window);\n\tyutani_flip(yctx, window);\n}\n\nstatic double shift_amount = 0.001;\nstatic double pan_amount = 0.1;\nstatic double zoom_amount = 2.0;\nstatic double amount(struct yutani_msg_key_event * ke, double basis) {\n\tif (ke->event.modifiers & (KEY_MOD_LEFT_SHIFT | KEY_MOD_RIGHT_SHIFT)) basis *= 10.0;\n\tif (ke->event.modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) basis *= 5.0;\n\n\treturn basis;\n}\n\nint main(int argc, char * argv[]) {\n\n\tif (!strcmp(basename(argv[0]),\"mandelbrot\")) {\n\t\tfunction = mandelbrot;\n\t\tapp_name = \"Mandelbrot Explorer\";\n\t\tapp_desc = \"Mandelbrot set plotter\";\n\t\tapp_icon = \"mandelbrot\";\n\t\texplore_mode = 1;\n\t\texpx = -0.75;\n\t}\n\n\tstatic struct option long_opts[] = {\n\t\t{\"iterations\", required_argument, 0, 'i'},\n\t\t{\"center-x\",   required_argument, 0, 'x'},\n\t\t{\"center-y\",   required_argument, 0, 'y'},\n\t\t{\"creal\",      required_argument, 0, 'c'},\n\t\t{\"cimag\",      required_argument, 0, 'C'},\n\t\t{\"rotate\",     required_argument, 0, 'r'},\n\t\t{\"width\",      required_argument, 0, 'W'},\n\t\t{\"height\",     required_argument, 0, 'H'},\n\t\t{\"help\",       no_argument,       0, 'h'},\n\t\t{0,0,0,0}\n\t};\n\n\tif (argc > 1) {\n\t\t/* Read some arguments */\n\t\tint index, c;\n\t\twhile ((c = getopt_long(argc, argv, \"ni:x:X:c:C:W:H:h\", long_opts, &index)) != -1) {\n\t\t\tif (!c) {\n\t\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\t\tc = long_opts[index].val;\n\t\t\t\t}\n\t\t\t}\n\t\t\tswitch (c) {\n\t\t\t\tcase 'i':\n\t\t\t\t\tmaxiter = atoi(optarg);\n\t\t\t\t\tif (maxiter < 10) maxiter = 10;\n\t\t\t\t\tif (maxiter > 1000) maxiter = 1000;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'x':\n\t\t\t\t\texpx = atof(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'y':\n\t\t\t\t\texpy = atof(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'c':\n\t\t\t\t\tconx = atof(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'C':\n\t\t\t\t\tcony = atof(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'r':\n\t\t\t\t\trotation = atof(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'W':\n\t\t\t\t\twidth = atoi(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'H':\n\t\t\t\t\theight = atoi(optarg);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'h':\n\t\t\t\t\tusage(argv);\n\t\t\t\t\texit(0);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\tdecor_left_width = bounds.left_width;\n\tdecor_top_height = bounds.top_height;\n\tdecor_right_width = bounds.right_width;\n\tdecor_bottom_height = bounds.bottom_height;\n\tdecor_width = bounds.width;\n\tdecor_height = bounds.height;\n\n\twindow = yutani_window_create(yctx, width + decor_width, height + decor_height);\n\tyutani_window_move(yctx, window, left, top);\n\n\tyutani_window_advertise_icon(yctx, window, app_name, app_icon);\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\tinitialize_palette();\n\n\tstart_processing();\n\n\tint playing = 1;\n\tint needs_redraw = 0;\n\n\twhile (playing) {\n\n\t\tif (processing && !yutani_query(yctx)) {\n\t\t\tstep_once();\n\t\t\tcontinue;\n\t\t}\n\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\t/* just decorations should be fine */\n\t\t\t\tdecors();\n\t\t\t\tflip(ctx);\n\t\t\t\tyutani_flip(yctx, window);\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\t\tif (explore_mode) {\n\t\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\t\t\t\t\t\t\texpx -= amount(ke, pan_amount) * expz;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\t\t\t\t\t\t\texpx += amount(ke, pan_amount) * expz;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_UP:\n\t\t\t\t\t\t\t\t\t\texpy += amount(ke, pan_amount) * expz;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\t\t\t\t\t\t\texpy -= amount(ke, pan_amount) * expz;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_PAGE_UP:\n\t\t\t\t\t\t\t\t\t\texpz /= amount(ke, zoom_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_PAGE_DOWN:\n\t\t\t\t\t\t\t\t\t\texpz *= amount(ke, zoom_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase 'p':\n\t\t\t\t\t\t\t\t\t\tnext_palette();\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\t\t\t\texplore_mode = 0;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\t\t\t\t\t\t\tconx -= amount(ke, shift_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\t\t\t\t\t\t\tconx += amount(ke, shift_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_UP:\n\t\t\t\t\t\t\t\t\t\tcony += amount(ke, shift_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\t\t\t\t\t\t\tcony -= amount(ke, shift_amount);\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase 'p':\n\t\t\t\t\t\t\t\t\t\tnext_palette();\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tcase 'e':\n\t\t\t\t\t\t\t\t\t\texplore_mode = 1;\n\t\t\t\t\t\t\t\t\t\tneeds_redraw = 1;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win && win == window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tdecors();\n\t\t\t\t\t\t\tflip(ctx);\n\t\t\t\t\t\t\tyutani_flip(yctx, window);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\n\t\tif (needs_redraw) {\n\t\t\tstart_processing();\n\t\t\tneeds_redraw = 0;\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/kbd-test.c",
    "content": "/**\n * @brief Keyboard test tool\n *\n * XXX This probably doesn't work anymore. It uses the VGA text mode\n *     region but it doesn't map it; legacy interfaces in toaru32\n *     just mapped this accessible to userspace...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <toaru/kbd.h>\n\nstatic unsigned short * textmemptr = (unsigned short *)0xB8000;\nstatic void placech(unsigned char c, int x, int y, int attr) {\n\tunsigned short *where;\n\tunsigned att = attr << 8;\n\twhere = textmemptr + (y * 80 + x);\n\t*where = c | att;\n}\n\nstatic void clear_screen(void) {\n\tfor (int y = 0; y < 24; ++y) {\n\t\tfor (int x = 0; x < 80; ++x) {\n\t\t\tplacech(' ', x, y, 0); /* Clear */\n\t\t}\n\t}\n}\n\n#define BUF_SIZE 4096\nstatic char keys[256] = {0};\n\nstatic void redraw(void) {\n\tint i = 0;\n\tfor (int c = 'a'; c <= 'z'; ++c, i += 2) {\n\t\tplacech(c, i, 0, keys[c] ? 0x2 : 0x7);\n\t}\n}\n\nstatic void print_scancode(unsigned int sc) {\n\tchar buf[10];\n\tsprintf(buf, \"%d\", sc);\n\n\tint i = 0;\n\tfor (char * c = buf; *c; ++c, ++i) {\n\t\tplacech(*c, i, 1, 0x7);\n\t}\n\tfor (; i < 4; ++i) {\n\t\tplacech(' ', i, 1, 0x7);\n\t}\n}\n\nint main(int argc, char * argv[]) {\n\tclear_screen();\n\tint kfd = open(\"/dev/kbd\", O_RDONLY);\n\tkey_event_t event;\n\tkey_event_state_t kbd_state = {0};\n\n\twhile (1) {\n\t\tunsigned char buf[BUF_SIZE];\n\t\tint r = read(kfd, buf, BUF_SIZE);\n\t\tfor (int i = 0; i < r; ++i) {\n\t\t\tkbd_scancode(&kbd_state, buf[i], &event);\n\t\t\tif (event.keycode >= 'a' && event.keycode < 'z') {\n\t\t\t\tkeys[event.keycode] = (event.action == KEY_ACTION_DOWN);\n\t\t\t}\n\t\t\tprint_scancode(buf[i]);\n\t\t}\n\t\tredraw();\n\n\t}\n\n}\n"
  },
  {
    "path": "apps/kcmdline.c",
    "content": "/**\n * @brief kcmdline - Parse /proc/cmdline usefully.\n *\n * Parses /proc/cmdline and provides an interface for querying\n * whether an argument was present, and its value if applicable.\n *\n * Also converts ASCII field separators to spaces so that cmdline\n * arguments can have spaces in them.\n *\n * Useful for shell scripts.\n *\n * TODO: This should probably be a library we can use in other\n *       applications...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <toaru/hashmap.h>\n\n#include \"../kernel/misc/args.c\"\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"kcmdline - query the kernel command line\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s -g ARG...\\n\"\n\t\t\t\"       %s -q ARG...\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -g     \\033[3mprint the value for the requested argument\\033[0m\\n\"\n\t\t\t\" -q     \\033[3mquery whether the requested argument is present (0 = yes)\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0], argv[0]);\n}\n\nint main(int argc, char * argv[]) {\n\tchar * cmdline = args_from_procfs();\n\n\t/* Figure out what we're doing */\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?g:q:s\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'g':\n\t\t\t\tif (hashmap_has(kernel_args_map, optarg)) {\n\t\t\t\t\tchar * tmp = (char*)hashmap_get(kernel_args_map, optarg);\n\t\t\t\t\tif (!tmp) {\n\t\t\t\t\t\tprintf(\"%s\\n\", optarg); /* special case = present but not set should yield name of variable */\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprintf(\"%s\\n\", tmp);\n\t\t\t\t\t}\n\t\t\t\t\treturn 0;\n\t\t\t\t} else {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\tcase 'q':\n\t\t\t\treturn !hashmap_has(kernel_args_map,optarg);\n\t\t\tcase 's':\n\t\t\t\treturn strlen(cmdline);\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tfprintf(stdout, \"%s\\n\", cmdline);\n}\n"
  },
  {
    "path": "apps/kill.c",
    "content": "/**\n * @brief kill - Send a signal to a process\n *\n * Supports signal names like any mature `kill` should.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <signal.h>\n#include <ctype.h>\n#include <errno.h>\n\nstruct sig_def {\n\tint sig;\n\tconst char * name;\n};\n\nstruct sig_def signals[] = {\n\t{SIGHUP,\"HUP\"},\n\t{SIGINT,\"INT\"},\n\t{SIGQUIT,\"QUIT\"},\n\t{SIGILL,\"ILL\"},\n\t{SIGTRAP,\"TRAP\"},\n\t{SIGABRT,\"ABRT\"},\n\t{SIGEMT,\"EMT\"},\n\t{SIGFPE,\"FPE\"},\n\t{SIGKILL,\"KILL\"},\n\t{SIGBUS,\"BUS\"},\n\t{SIGSEGV,\"SEGV\"},\n\t{SIGSYS,\"SYS\"},\n\t{SIGPIPE,\"PIPE\"},\n\t{SIGALRM,\"ALRM\"},\n\t{SIGTERM,\"TERM\"},\n\t{SIGUSR1,\"USR1\"},\n\t{SIGUSR2,\"USR2\"},\n\t{SIGCHLD,\"CHLD\"},\n\t{SIGPWR,\"PWR\"},\n\t{SIGWINCH,\"WINCH\"},\n\t{SIGURG,\"URG\"},\n\t{SIGPOLL,\"POLL\"},\n\t{SIGSTOP,\"STOP\"},\n\t{SIGTSTP,\"TSTP\"},\n\t{SIGCONT,\"CONT\"},\n\t{SIGTTIN,\"TTIN\"},\n\t{SIGTTOUT,\"TTOUT\"},\n\t{SIGVTALRM,\"VTALRM\"},\n\t{SIGPROF,\"PROF\"},\n\t{SIGXCPU,\"XCPU\"},\n\t{SIGXFSZ,\"XFSZ\"},\n\t{SIGWAITING,\"WAITING\"},\n\t{SIGDIAF,\"DIAF\"},\n\t{SIGHATE,\"HATE\"},\n\t{SIGWINEVENT,\"WINEVENT\"},\n\t{SIGCAT,\"CAT\"},\n\t{0,NULL},\n};\n\nvoid usage(char * argv[]) {\n\tprintf(\n\t\t\t\"%s - send a signal to another process\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-\\033[3mx\\033[0m] \\033[3mprocess\\033[0m\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\"\n\t\t\t\" -\\033[3mx\\033[0m              \\033[3mSignal number to send\\033[0m\\n\"\n\t\t\t\"\\n\",\n\t\t\targv[0], argv[0]);\n}\n\nint main(int argc, char * argv[]) {\n\tint signum = SIGKILL;\n\tint pid = 0;\n\tint i = 1;\n\n\tif (argc < 2) {\n\t\tusage(argv);\n\t\treturn 1;\n\t}\n\n\tif (argv[1][0] == '-') {\n\t\tsignum = -1;\n\t\tif (strlen(argv[1]+1) > 3 && strstr(argv[1]+1,\"SIG\") == (argv[1]+1)) {\n\t\t\tstruct sig_def * s = signals;\n\t\t\twhile (s->name) {\n\t\t\t\tif (!strcmp(argv[1]+4,s->name)) {\n\t\t\t\t\tsignum = s->sig;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\ts++;\n\t\t\t}\n\t\t} else {\n\t\t\tif (!isdigit(argv[1][1])) {\n\t\t\t\tstruct sig_def * s = signals;\n\t\t\t\twhile (s->name) {\n\t\t\t\t\tif (!strcmp(argv[1]+1,s->name)) {\n\t\t\t\t\t\tsignum = s->sig;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\ts++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tsignum = atoi(argv[1]+1);\n\t\t\t}\n\t\t}\n\t\tif (signum == -1) {\n\t\t\tfprintf(stderr,\"%s: %s: invalid signal specification\\n\",argv[0],argv[1]+1);\n\t\t\treturn 1;\n\t\t}\n\t\ti++;\n\t}\n\n\tif (i == argc) {\n\t\tusage(argv);\n\t\treturn 1;\n\t}\n\n\tint retval = 0;\n\n\tfor (; i < argc; ++i) {\n\t\tpid = atoi(argv[i]);\n\t\tif (pid) {\n\t\t\tif (kill(pid, signum) < 0) {\n\t\t\t\tfprintf(stderr, \"%s: (%d) %s\\n\", argv[0], pid, strerror(errno));\n\t\t\t\tretval = 1;\n\t\t\t}\n\t\t} else {\n\t\t\tfprintf(stderr, \"%s: invalid pid (%s)\\n\", argv[0], argv[i]);\n\t\t\tretval = 1;\n\t\t}\n\t}\n\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/killall.c",
    "content": "/**\n * @brief killall - Send signals to processes matching name\n *\n * Find processes by name and send them signals.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <signal.h>\n#include <getopt.h>\n#include <errno.h>\n#include <sys/stat.h>\n#include <sys/signal.h>\n\ntypedef struct process {\n\tint pid;\n\tint ppid;\n\tint tgid;\n\tchar name[100];\n\tchar path[200];\n} p_t;\n\n#define LINE_LEN 4096\n\np_t * build_entry(struct dirent * dent) {\n\tchar tmp[300];\n\tFILE * f;\n\tchar line[LINE_LEN];\n\n\tsprintf(tmp, \"/proc/%s/status\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\n\tp_t * proc = malloc(sizeof(p_t));\n\n\twhile (fgets(line, LINE_LEN, f) != NULL) {\n\t\tchar * n = strstr(line,\"\\n\");\n\t\tif (n) { *n = '\\0'; }\n\t\tchar * tab = strstr(line,\"\\t\");\n\t\tif (tab) {\n\t\t\t*tab = '\\0';\n\t\t\ttab++;\n\t\t}\n\t\tif (strstr(line, \"Pid:\") == line) {\n\t\t\tproc->pid = atoi(tab);\n\t\t} else if (strstr(line, \"PPid:\") == line) {\n\t\t\tproc->ppid = atoi(tab);\n\t\t} else if (strstr(line, \"Tgid:\") == line) {\n\t\t\tproc->tgid = atoi(tab);\n\t\t} else if (strstr(line, \"Name:\") == line) {\n\t\t\tstrcpy(proc->name, tab);\n\t\t} else if (strstr(line, \"Path:\") == line) {\n\t\t\tstrcpy(proc->path, tab);\n\t\t}\n\t}\n\n\tif (strstr(proc->name,\"python\") == proc->name) {\n\t\tchar * name = proc->path + strlen(proc->path) - 1;\n\n\t\twhile (1) {\n\t\t\tif (*name == '/') {\n\t\t\t\tname++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (name == proc->name) break;\n\t\t\tname--;\n\t\t}\n\n\t\tmemcpy(proc->name, name, strlen(name)+1);\n\t}\n\n\tif (proc->tgid != proc->pid) {\n\t\tchar tmp[100] = {0};\n\t\tsprintf(tmp, \"{%s}\", proc->name);\n\t\tmemcpy(proc->name, tmp, strlen(tmp)+1);\n\t}\n\n\tfclose(f);\n\n\treturn proc;\n}\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"killall - send signal to processes with given name\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-s SIG] name\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -s     \\033[3msignal to send\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nstruct sig_def {\n\tint sig;\n\tconst char * name;\n};\n\nstruct sig_def signals[] = {\n\t{SIGHUP,\"HUP\"},\n\t{SIGINT,\"INT\"},\n\t{SIGQUIT,\"QUIT\"},\n\t{SIGILL,\"ILL\"},\n\t{SIGTRAP,\"TRAP\"},\n\t{SIGABRT,\"ABRT\"},\n\t{SIGEMT,\"EMT\"},\n\t{SIGFPE,\"FPE\"},\n\t{SIGKILL,\"KILL\"},\n\t{SIGBUS,\"BUS\"},\n\t{SIGSEGV,\"SEGV\"},\n\t{SIGSYS,\"SYS\"},\n\t{SIGPIPE,\"PIPE\"},\n\t{SIGALRM,\"ALRM\"},\n\t{SIGTERM,\"TERM\"},\n\t{SIGUSR1,\"USR1\"},\n\t{SIGUSR2,\"USR2\"},\n\t{SIGCHLD,\"CHLD\"},\n\t{SIGPWR,\"PWR\"},\n\t{SIGWINCH,\"WINCH\"},\n\t{SIGURG,\"URG\"},\n\t{SIGPOLL,\"POLL\"},\n\t{SIGSTOP,\"STOP\"},\n\t{SIGTSTP,\"TSTP\"},\n\t{SIGCONT,\"CONT\"},\n\t{SIGTTIN,\"TTIN\"},\n\t{SIGTTOUT,\"TTOUT\"},\n\t{SIGVTALRM,\"VTALRM\"},\n\t{SIGPROF,\"PROF\"},\n\t{SIGXCPU,\"XCPU\"},\n\t{SIGXFSZ,\"XFSZ\"},\n\t{SIGWAITING,\"WAITING\"},\n\t{SIGDIAF,\"DIAF\"},\n\t{SIGHATE,\"HATE\"},\n\t{SIGWINEVENT,\"WINEVENT\"},\n\t{SIGCAT,\"CAT\"},\n\t{0,NULL},\n};\n\nint main (int argc, char * argv[]) {\n\n\tint signum = SIGTERM;\n\n\tint c;\n\twhile ((c = getopt(argc, argv, \"s:?\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 's':\n\t\t\t\t{\n\t\t\t\t\tsignum = -1;\n\t\t\t\t\tif (strlen(optarg) > 3 && strstr(optarg,\"SIG\") == (optarg)) {\n\t\t\t\t\t\tstruct sig_def * s = signals;\n\t\t\t\t\t\twhile (s->name) {\n\t\t\t\t\t\t\tif (!strcmp(optarg+3,s->name)) {\n\t\t\t\t\t\t\t\tsignum = s->sig;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ts++;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (optarg[0] < '0' || optarg[0] > '9') {\n\t\t\t\t\t\t\tstruct sig_def * s = signals;\n\t\t\t\t\t\t\twhile (s->name) {\n\t\t\t\t\t\t\t\tif (!strcmp(optarg,s->name)) {\n\t\t\t\t\t\t\t\t\tsignum = s->sig;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\ts++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsignum = atoi(optarg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (signum == -1) {\n\t\t\t\t\t\tfprintf(stderr,\"%s: %s: invalid signal specification\\n\",argv[0],optarg);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif (optind >= argc) {\n\t\tshow_usage(argc, argv);\n\t\treturn 1;\n\t}\n\n\tint killed_something = 0;\n\tint retval = 0;\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\t/* Open the directory */\n\t\tDIR * dirp = opendir(\"/proc\");\n\n\t\tstruct dirent * ent = readdir(dirp);\n\t\twhile (ent != NULL) {\n\t\t\tif (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {\n\t\t\t\tp_t * proc = build_entry(ent);\n\n\t\t\t\tif (!strcmp(proc->name, argv[i])) {\n\t\t\t\t\tif (kill(proc->pid, signum) < 0) {\n\t\t\t\t\t\tfprintf(stderr, \"%s(%d) %s\\n\", argv[i], proc->pid, strerror(errno));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tkilled_something = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfree(proc);\n\t\t\t}\n\t\t\tent = readdir(dirp);\n\t\t}\n\t\tclosedir(dirp);\n\t\tif (!killed_something) {\n\t\t\tfprintf(stderr, \"%s: no process found\\n\", argv[i]);\n\t\t\tretval = 1;\n\t\t}\n\t}\n\n\treturn retval;\n}\n\n"
  },
  {
    "path": "apps/krk_test_noise.krk",
    "content": "#!/bin/kuroko\nimport os\nimport gc\nif 'KUROKO_TEST_ENV' in os.environ:\n    return 0\n\nfrom time import sleep\nfrom fileio import open, stdin\nfrom threading import Thread\n\nlet d = {}\nlet stop = False\n\nfor y in range(0x40):\n    for x in range(0x40):\n        d[(y,x)] = 0\n\nclass NoisePainter(Thread):\n    def run(self):\n        let myRando = open('/dev/urandom','rb')\n        while not stop:\n            let bytes = myRando.read(3)\n            let x = bytes[0] & 0x3F\n            let y = bytes[1] & 0x3F\n            d[(y,x)] = bytes[2]\n\nlet painters = [NoisePainter() for i in range(5)]\n\ngc.pause()\nfor painter in painters:\n    painter.start()\n\ndef drawScreen():\n    print(\"\\[[H\",end=\"\")\n    for y in range(0x20):\n        for x in range(0x40):\n            let top = d[(y*2,x)]\n            let bottom = d[(y*2+1,x)]\n            print(\"\\[[38\",\"2\",top,top,top,\"48\",\"2\",bottom,bottom,bottom,sep=\";\",end=\"m▀\")\n        print(\"\\[[0m\")\n\nfor i in range(5):\n    drawScreen()\n\nstop = True\nfor painter in painters:\n    painter.join()\n\ngc.resume()\n\ndrawScreen()\n"
  },
  {
    "path": "apps/krk_yutani_test.krk",
    "content": "#!/bin/kuroko\nfrom _yutani2 import (YutaniCtx, Font, rgb, rgb, MenuBar, decor_get_bounds, decor_render,\n                      MenuList, MenuEntry, MenuEntrySeparator, Message, decor_handle_event, decor_show_default_menu,\n                      MenuEntryCustom)\n\nfrom yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep\nimport math\nimport random\n\nlet mainloop = AsyncMainloop()\n\ndef close_enough(me):\n    return me.command == 2 and math.sqrt((me.new_x - me.old_x) ** 2 + (me.new_y - me.old_y) **2) < 10\n\nclass Button:\n    ''' Basic button widget based on draw_button '''\n\n    def __init__(self, window, x, y, width, height, title, callback):\n        self.ctx = window\n        self.x = x\n        self.y = y\n        self.width = width\n        self.height = height\n        self.title = title\n        self.state = 0\n        self.callback = asyncCallback\n\n    def __contains__(self, coord):\n        if not isinstance(cord, tuple):\n            return False\n\n    def focus_enter(self):\n        self.state = 1\n        return True\n\n    def focus_leave(self):\n        self.state = 0\n        return True\n\n    def mouse_down(self, msg):\n        self.state = 2\n        return True\n\n    def mouse_up(self, msg):\n        # TODO check if old_x, old_y is in button\n        self.callback(self)\n        self.state = 0\n        return True\n\n    def draw(self):\n        draw_button(self.ctx, self.x, self.y, self.width, self.height, self.title, self.state)\n\nclass MyMenuWidget(MenuEntryCustom):\n    def __init__(self):\n        super().__init__()\n        self.height = 30\n        self.rwidth = 148\n        self.dejavu = Font(\"sans-serif\", 13)\n        self.x = 0\n        self.y = 0\n\n    def render(self, ctx, offset):\n        self.offset = offset\n        if self.hilight:\n            ctx.rect(1,offset,self.width-2,self.height,rgb(100,int(255 * self.x / self.width),int(255 * (self.y-offset) / self.height)),radius=4)\n            self.dejavu.draw_string(ctx, f\"{self.x=},{self.y=}\", 2, offset + 13)\n        else:\n            self.dejavu.draw_string(ctx, f\"{offset=}\", 2, offset + 13)\n\n    def activate(self, flags):\n        print(\"Activated by keyboard!\")\n\n    def click_within(self, evt):\n        if evt.command == 0: return True\n        if evt.command == 2:\n            if \\\n                evt.new_x >= 0 and evt.new_x < self.width and \\\n                evt.new_y >= self.offset and evt.new_y < self.offset + self.height and \\\n                evt.old_x >= 0 and evt.old_x < self.width and \\\n                evt.old_y >= self.offset and evt.old_y < self.offset + self.height:\n                return True\n            else:\n                print(f'{evt.old_x=} {evt.old_y=} {evt.new_x=} {evt.old_y=} {self.offset=} {self.width=} {self.height=}')\n\n    async def async_thingy(self):\n        print(\"Schedule async callback\")\n        await sleep(2)\n        print(\"Finish async callback\")\n\n    def mouse_event(self, evt):\n        self.x = evt.new_x\n        self.y = evt.new_y\n        if self.click_within(evt):\n            Task(self.async_thingy())\n            print(\"Clicked!\")\n        return True\n\nclass MyWindow(Window):\n    def __init__(self):\n        super().__init__(640, 480, title=\"Hello\", doublebuffer=True)\n        self.bgc = rgb(255,255,255)\n        self.dejavu = Font(\"sans-serif\", 13)\n        self.mb = MenuBar(((\"File\",'file'),(\"Help\",'help')))\n        let _menu_File = MenuList()\n        _menu_File.insert(MenuEntry(\"Test\", lambda menu: print(\"hello, world\")))\n        _menu_File.insert(MenuEntrySeparator())\n        _menu_File.insert(MenuEntry(\"Quit\", lambda menu: self.close()))\n        self.mb.insert('file', _menu_File)\n        let _menu_Help = MenuList()\n        let _menu_Help_help = MenuEntry(\"Help\",lambda menu: print(\"oh no!\"))\n        _menu_Help.insert(_menu_Help_help)\n        _menu_Help.insert(MyMenuWidget())\n        _menu_Help.insert(MyMenuWidget())\n        self.mb.insert('help', _menu_Help)\n        self.mb.callback = lambda x: self.draw()\n\n    def draw(self):\n        self.fill(self.bgc)\n        decor_render(self)\n        let bounds = decor_get_bounds(self)\n        self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self)\n        self.mb.render(self)\n        self.dejavu.draw_string(self,\"Hello, world.\",20,120)\n        self.dejavu.draw_string(self,f\"{self.x}, {self.y}\",20,140)\n        self.flip()\n\n    def mouse_event(self, msg):\n        let decResponse = decor_handle_event(msg)\n        if decResponse == 2:\n            print(\"Close me?\")\n            self.close()\n            return True\n        else if decResponse == 5:\n            decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y)\n        self.mb.mouse_event(self, msg)\n\n    def interp(a,b,p):\n        return a * (1.0 - p) + b * p\n\n    def interp_color(a,b,p):\n        return rgb(*(int(self.interp(l,r,p)) for l,r in zip(a,b)))\n\n    async def animate(self):\n        let mySentinel = object()\n        self.animator = mySentinel\n        let start = (255 * random.random(),255 * random.random(),255 * random.random())\n        let end = (255,255,255)\n        for i in range(31):\n            await sleep(0.033)\n            if self.animator is not mySentinel:\n                break\n            self.bgc = self.interp_color(start,end,i/30)\n            self.draw()\n\n    def keyboard_event(self, msg):\n        print(msg.keycode)\n        if msg.keycode == 102 and msg.action == 1:\n            Task(self.animate())\n\n    def close(self):\n        super().close()\n        mainloop.exit()\n\n    def window_moved(self, msg):\n        self.draw()\n\n    def menu_close(self):\n        self.draw()\n\nlet w = MyWindow()\nw.move(200,200)\nw.draw()\n\nmainloop.menu_closed_callback = w.menu_close\nmainloop.run()\n"
  },
  {
    "path": "apps/live-session.c",
    "content": "/**\n * @brief live-session - Run live CD user session.\n *\n * Launches the general session manager as 'local', waits for the\n * session to end, then launches the login manager.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/wait.h>\n#include <toaru/auth.h>\n#include <toaru/yutani.h>\n#include <toaru/trace.h>\n#define TRACE_APP_NAME \"live-session\"\n\nint main(int argc, char * argv[]) {\n\tint pid;\n\n\tif (geteuid() != 0) {\n\t\treturn 1;\n\t}\n\n\tint _session_pid = fork();\n\tif (!_session_pid) {\n\t\ttoaru_set_credentials(1000);\n\t\tchar * args[] = {\"/bin/session\", NULL};\n\t\texecvp(args[0], args);\n\n\t\treturn 1;\n\t}\n\n\t/* Dummy session for live-session prevents compositor from killing itself\n\t * when the main session dies the first time. */\n\tyutani_init();\n\n\tdo {\n\t\tpid = wait(NULL);\n\t} while ((pid > 0 && pid != _session_pid) || (pid == -1 && errno == EINTR));\n\n\tTRACE(\"Live session has ended, launching graphical login.\");\n\tint _glogin_pid = fork();\n\tif (!_glogin_pid) {\n\t\tchar * args[] = {\"/bin/glogin\",NULL};\n\t\texecvp(args[0],args);\n\t\tsystem(\"reboot\");\n\t}\n\n\tdo {\n\t\tpid = wait(NULL);\n\t} while ((pid > 0 && pid != _glogin_pid) || (pid == -1 && errno == EINTR));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/ln.c",
    "content": "/**\n * @brief Make symlinks\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015 Mike Gerow\n *               2018 K. Lange\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n\nstatic const char usage[] =\n\"Usage: %s [-s] TARGET NAME\\n\"\n\"    -s: Create a symbolic link.\\n\"\n\"    -h: Print this help message and exit.\\n\";\n\nextern int link(const char *old, const char *new);\n\nint main(int argc, char * argv[]) {\n\tint symlink_flag = 0;\n\n\tint c;\n\twhile ((c = getopt(argc, argv, \"sh\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 's':\n\t\t\t\tsymlink_flag = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tfprintf(stdout, usage, argv[0]);\n\t\t\t\texit(EXIT_SUCCESS);\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, usage, argv[0]);\n\t\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t}\n\tif (argc - optind < 2) {\n\t\tfprintf(stderr, usage, argv[0]);\n\t\texit(EXIT_FAILURE);\n\t}\n\tchar * target = argv[optind];\n\tchar * name = argv[optind + 1];\n\n\tif (symlink_flag) {\n\t\tif(symlink(target, name) < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], name, strerror(errno));\n\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\tif (link(target, name) < 0) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], name, strerror(errno));\n\t\texit(EXIT_FAILURE);\n\t}\n\n\texit(EXIT_SUCCESS);\n}\n"
  },
  {
    "path": "apps/login-loop.c",
    "content": "/**\n * @brief Repeatedly invoke `login` in a loop.\n *\n * This is more closely related to the 'getty' command in Linux\n * than our actual 'getty' command as it is also where we process\n * and display /etc/issue.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <dirent.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <time.h>\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <sys/wait.h>\n#include <sys/utsname.h>\n#include <sys/time.h>\n#include <net/if.h>\n#include <arpa/inet.h>\n\nstruct colorNames {\n\tconst char * name;\n\tconst char * output;\n} ColorNames[] = {\n\t{\"black\", \"\\033[30m\"},\n\t{\"blue\", \"\\033[34m\"},\n\t{\"bold\", \"\\033[1m\"},\n\t{\"brown\", \"\\033[33m\"},\n\t{\"cyan\", \"\\033[36\"},\n\t{\"darkgray\", \"\\033[90m\"},\n\t{\"gray\", \"\\033[37m\"},\n\t{\"green\", \"\\033[32m\"},\n\t{\"lightblue\", \"\\033[94m\"},\n\t{\"lightcyan\", \"\\033[96m\"},\n\t{\"lightgray\", \"\\033[97m\"},\n\t{\"lightgreen\", \"\\033[92m\"},\n\t{\"lightmagenta\", \"\\033[95m\"},\n\t{\"lightred\", \"\\033[91m\"},\n\t{\"magenta\", \"\\033[35m\"},\n\t{\"red\", \"\\033[31m\"},\n\t{\"reset\", \"\\033[0m\"},\n\t{\"reverse\", \"\\033[7m\"},\n\t{\"yellow\", \"\\033[93m\"},\n\t{NULL, NULL},\n};\n\nchar * get_arg(FILE * f) {\n\tstatic char buf[32];\n\tint n = fgetc(f);\n\tif (n == '{') {\n\t\tint count = 0;\n\t\tdo {\n\t\t\tint x = fgetc(f);\n\t\t\tif (x == '}') break;\n\t\t\tif (x < 0) break;\n\t\t\tbuf[count++] = x;\n\t\t\tbuf[count] = '\\0';\n\t\t} while (count < 31);\n\t\tif (count) return buf;\n\t}\n\treturn NULL;\n}\n\nchar * get_ipv4_address(char * arg) {\n\tif (arg) {\n\t\tchar if_path[1024];\n\t\tsnprintf(if_path, 300, \"/dev/net/%s\", arg);\n\t\tint netdev = open(if_path, O_RDWR);\n\t\tif (netdev >= 0) {\n\t\t\tuint32_t ip_addr = 0;\n\t\t\tif (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) {\n\t\t\t\treturn inet_ntoa((struct in_addr){ntohl(ip_addr)});\n\t\t\t}\n\t\t}\n\t} else {\n\t\t/* Read /dev/net for interfaces */\n\t\tDIR * d = opendir(\"/dev/net\");\n\t\tif (d) {\n\t\t\tstruct dirent * ent;\n\t\t\twhile ((ent = readdir(d))) {\n\t\t\t\tif (ent->d_name[0] == '.') continue;\n\t\t\t\tclosedir(d);\n\t\t\t\treturn get_ipv4_address(ent->d_name);\n\t\t\t}\n\t\t\tclosedir(d);\n\t\t}\n\t}\n\n\treturn \"127.0.0.1\";\n}\n\nvoid print_issue(void) {\n\tprintf(\"\\033[H\\033[2J\\n\");\n\n\tFILE * f = fopen(\"/etc/issue\",\"r\");\n\tif (!f) return;\n\n\t/* Parse and display /etc/issue with support\n\t * for some escape sequences that fill in\n\t * dynamic information... */\n\n\tstruct utsname u;\n\tuname(&u);\n\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\tstatic char buf[1024];\n\n\twhile (!feof(f)) {\n\t\tint c = fgetc(f);\n\n\t\tif (c < 0) break; /* Probably EOF */\n\n\t\tif (c == '\\\\') {\n\t\t\tint next = fgetc(f);\n\t\t\tswitch (next) {\n\t\t\t\tcase '\\n':\n\t\t\t\t\t/* A linefeed we should quietly skip. */\n\t\t\t\t\tcontinue;\n\t\t\t\tcase '\\\\':\n\t\t\t\t\t/* A literal backslash */\n\t\t\t\t\tprintf(\"\\\\\");\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Various things from uname */\n\t\t\t\tcase 'n':\n\t\t\t\t\tprintf(\"%s\", u.nodename);\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 's':\n\t\t\t\t\tprintf(\"%s\", u.sysname);\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 'r':\n\t\t\t\t\tprintf(\"%s\", u.release);\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 'm':\n\t\t\t\t\tprintf(\"%s\", u.machine);\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 'v':\n\t\t\t\t\tprintf(\"%s\", u.version);\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Various other useful things */\n\t\t\t\tcase '4':\n\t\t\t\t\tprintf(\"%s\", get_ipv4_address(get_arg(f)));\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 'l':\n\t\t\t\t\tprintf(\"%s\", ttyname(STDIN_FILENO));\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 't':\n\t\t\t\t\tstrftime(buf,1024,\"%T %Z\",timeinfo);\n\t\t\t\t\tprintf(\"%s\", buf);\n\t\t\t\t\tcontinue;\n\t\t\t\tcase 'd':\n\t\t\t\t\tstrftime(buf,1024,\"%a %b %d %Y\",timeinfo);\n\t\t\t\t\tprintf(\"%s\", buf);\n\t\t\t\t\tcontinue;\n\n\t\t\t\t/* Formatting stuff listed in Linux's getty(8) manpage */\n\t\t\t\tcase 'e': {\n\t\t\t\t\tchar * arg = get_arg(f);\n\t\t\t\t\tif (arg) {\n\t\t\t\t\t\tfor (struct colorNames * cn = ColorNames; cn->name; cn++) {\n\t\t\t\t\t\t\tif (!strcmp(arg,cn->name)) {\n\t\t\t\t\t\t\t\tprintf(\"%s\", cn->output);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tprintf(\"\\033\");\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tprintf(\"%c\", c);\n\t\t}\n\t}\n\n\n\tfclose(f);\n}\n\nint main(int argc, char * argv[]) {\n\twhile (1) {\n\t\tprint_issue();\n\t\tpid_t f = fork();\n\t\tif (!f) {\n\t\t\tchar * args[] = {\n\t\t\t\t\"login\",\n\t\t\t\tNULL\n\t\t\t};\n\t\t\texecvp(args[0], args);\n\t\t} else {\n\t\t\tint result, status;\n\t\t\tdo {\n\t\t\t\tresult = waitpid(f, &status, 0);\n\t\t\t} while (result < 0);\n\t\t\tif (WEXITSTATUS(status) == 2) break;\n\t\t}\n\t}\n\n\treturn 1;\n}\n"
  },
  {
    "path": "apps/login.c",
    "content": "/**\n * @brief TTY login prompt\n *\n * Provides the user with a login prompt and starts their session.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2014 K. Lange\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <time.h>\n#include <signal.h>\n#include <termios.h>\n#include <errno.h>\n#include <pwd.h>\n#include <sys/wait.h>\n#include <sys/utsname.h>\n#include <sys/ioctl.h>\n\n#include <toaru/auth.h>\n\n#define LINE_LEN 1024\n\nuint32_t child = 0;\n\nvoid sig_pass(int sig) {\n\t/* Pass onto the shell */\n\tif (child) {\n\t\tkill(child, sig);\n\t}\n\t/* Else, ignore */\n}\n\nvoid sig_segv(int sig) {\n\tprintf(\"Segmentation fault.\\n\");\n\texit(127 + sig);\n\t/* no return */\n}\n\nint main(int argc, char ** argv) {\n\n\tchar * user = NULL;\n\tint uid;\n\tpid_t pid, f;\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"f:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'f':\n\t\t\t\tuser = optarg;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (user) {\n\t\tstruct passwd * pw = getpwnam(user);\n\t\tif (pw) {\n\t\t\tuid = pw->pw_uid;\n\t\t\tgoto do_fork;\n\t\t} else {\n\t\t\tfprintf(stderr, \"%s: no such user\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tsignal(SIGINT, sig_pass);\n\tsignal(SIGWINCH, sig_pass);\n\tsignal(SIGSEGV, sig_segv);\n\n\twhile (1) {\n\n\t\tchar username[1024] = {0};\n\t\tchar password[1024] = {0};\n\n\t\t/* TODO: gethostname() */\n\t\tchar _hostname[256];\n\t\tgethostname(_hostname, 255);\n\n\t\tfprintf(stdout, \"%s login: \", _hostname);\n\t\tfflush(stdout);\n\t\tchar * r = fgets(username, 1024, stdin);\n\t\tif (!r) {\n\t\t\tclearerr(stdin);\n\t\t\tfprintf(stderr, \"\\n\");\n\t\t\tsleep(2);\n\t\t\tfprintf(stderr, \"\\nLogin failed.\\n\");\n\t\t\tcontinue;\n\t\t}\n\t\tusername[strlen(username)-1] = '\\0';\n\n\t\tif (!strcmp(username, \"reboot\")) {\n\t\t\t/* Quick hack so vga text mode login can exit */\n\t\t\tsystem(\"reboot\");\n\t\t}\n\n\t\tif (!strcmp(username, \"disconnect\")) {\n\t\t\treturn 2;\n\t\t}\n\n\t\tfprintf(stdout, \"password: \");\n\t\tfflush(stdout);\n\n\t\t/* Disable echo */\n\t\tstruct termios old, new;\n\t\ttcgetattr(fileno(stdin), &old);\n\t\tnew = old;\n\t\tnew.c_lflag &= (~ECHO);\n\t\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n\n\t\tr = fgets(password, 1024, stdin);\n\t\tif (!r) {\n\t\t\tclearerr(stdin);\n\t\t\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n\t\t\tfprintf(stderr, \"\\n\");\n\t\t\tsleep(2);\n\t\t\tfprintf(stderr, \"\\nLogin failed.\\n\");\n\t\t\tcontinue;\n\t\t}\n\t\tpassword[strlen(password)-1] = '\\0';\n\t\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n\t\tfprintf(stdout, \"\\n\");\n\n\t\tuid = toaru_auth_check_pass(username, password);\n\n\t\tif (uid < 0) {\n\t\t\tsleep(2);\n\t\t\tfprintf(stdout, \"\\nLogin failed.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tbreak;\n\t}\n\n\tsystem(\"cat /etc/motd\");\n\ndo_fork:\n\tpid = getpid();\n\tf = fork();\n\tif (getpid() != pid) {\n\t\tioctl(STDIN_FILENO, IOCTLTTYLOGIN, &uid);\n\t\tsetsid();\n\t\tioctl(STDIN_FILENO, TIOCSCTTY, &(int){1});\n\t\ttcsetpgrp(STDIN_FILENO, getpid());\n\t\ttoaru_set_credentials(uid);\n\t\tchar * args[] = {\n\t\t\tgetenv(\"SHELL\"),\n\t\t\tNULL\n\t\t};\n\t\texecvp(args[0], args);\n\t\treturn 1;\n\t} else {\n\t\tchild = f;\n\t\tint result;\n\t\tdo {\n\t\t\tresult = waitpid(f, NULL, 0);\n\t\t} while (result < 0);\n\t}\n\tchild = 0;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/logname.c",
    "content": "/**\n * @brief Display the user's name, as returned by getlogin()\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <unistd.h>\n#include <stdio.h>\n\nint main(int argc, char ** argv) {\n\tchar * name = getlogin();\n\tif (!name) {\n\t\tfprintf(stderr, \"%s: failed to determine login name\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tfprintf(stdout, \"%s\\n\", name);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/ls.c",
    "content": "/**\n * @brief List files\n *\n * Lists files in a directory, with nice color\n * output like any modern ls should have.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <termios.h>\n#include <time.h>\n#include <pwd.h>\n#include <errno.h>\n\n#include <sys/ioctl.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n\n#define TRACE_APP_NAME \"ls\"\n//#include \"lib/trace.h\"\n#define TRACE(...)\n\n#include <toaru/list.h>\n\n#define MIN_COL_SPACING 2\n\n#define EXE_COLOR\t\t\"1;32\"\n#define DIR_COLOR\t\t\"1;34\"\n#define SYMLINK_COLOR\t\"1;36\"\n#define REG_COLOR\t\t\"0\"\n#define MEDIA_COLOR\t\t\"\"\n#define SYM_COLOR\t\t\"\"\n#define BROKEN_COLOR\t\"1;\"\n#define DEVICE_COLOR\t\"1;33;40\"\n#define SETUID_COLOR\t\"37;41\"\n\n#define DEFAULT_TERM_WIDTH 0\n#define DEFAULT_TERM_HEIGHT 0\n\n#define MAX(a, b) (((a) > (b)) ? (a) : (b))\n\n#define LINE_LEN 4096\n\nstatic int human_readable = 0;\nstatic int stdout_is_tty = 1;\nstatic int this_year = 0;\nstatic int show_hidden = 0;\nstatic int long_mode   = 0;\nstatic int print_dir   = 0;\nstatic int term_width = DEFAULT_TERM_WIDTH;\nstatic int term_height = DEFAULT_TERM_HEIGHT;\n\nstruct tfile {\n\tchar * name;\n\tstruct stat statbuf;\n\tchar * link;\n\tstruct stat statbufl;\n};\n\nstatic const char * color_str(struct stat * sb) {\n\tif (S_ISDIR(sb->st_mode)) {\n\t\t/* Directory */\n\t\treturn DIR_COLOR;\n\t} else if (S_ISLNK(sb->st_mode)) {\n\t\t/* Symbolic Link */\n\t\treturn SYMLINK_COLOR;\n\t} else if (sb->st_mode & S_ISUID) {\n\t\t/* setuid - sudo, etc. */\n\t\treturn SETUID_COLOR;\n\t} else if (sb->st_mode & 0111) {\n\t\t/* Executable */\n\t\treturn EXE_COLOR;\n\t} else if (S_ISBLK(sb->st_mode) || S_ISCHR(sb->st_mode) || S_ISFIFO(sb->st_mode)) {\n\t\t/* Device file */\n\t\treturn DEVICE_COLOR;\n\t} else {\n\t\t/* Regular file */\n\t\treturn REG_COLOR;\n\t}\n}\n\nstatic int filecmp(const void * c1, const void * c2) {\n\tconst struct tfile * d1 = *(const struct tfile **)c1;\n\tconst struct tfile * d2 = *(const struct tfile **)c2;\n\n\tint a = S_ISDIR(d1->statbuf.st_mode);\n\tint b = S_ISDIR(d2->statbuf.st_mode);\n\n\tif (a == b) return strcmp(d1->name, d2->name);\n\telse if (a < b) return -1;\n\telse if (a > b) return 1;\n\treturn 0; /* impossible ? */\n}\n\nstatic int filecmp_notypesort(const void * c1, const void * c2) {\n\tconst struct tfile * d1 = *(const struct tfile **)c1;\n\tconst struct tfile * d2 = *(const struct tfile **)c2;\n\n\treturn strcmp(d1->name, d2->name);\n}\n\nstatic void print_entry(struct tfile * file, int colwidth) {\n\tconst char * ansi_color_str = color_str(&file->statbuf);\n\n\t/* Print the file name */\n\tif (stdout_is_tty) {\n\t\tprintf(\"\\033[%sm%s\\033[0m\", ansi_color_str, file->name);\n\t} else {\n\t\tprintf(\"%s\", file->name);\n\t}\n\n\t/* Pad the rest of the column */\n\tfor (int rem = colwidth - strlen(file->name); rem > 0; rem--) {\n\t\tprintf(\" \");\n\t}\n}\n\nstatic int print_username(char * _out, int uid) {\n\n\tTRACE(\"getpwuid\");\n\tstruct passwd * p = getpwuid(uid);\n\tint out = 0;\n\n\tif (p) {\n\t\tTRACE(\"p is set\");\n\t\tout = sprintf(_out, \"%s\", p->pw_name);\n\t} else {\n\t\tTRACE(\"p is not set\");\n\t\tout = sprintf(_out, \"%d\", uid);\n\t}\n\n\tendpwent();\n\n\treturn out;\n}\n\nstatic int print_human_readable_size(char * _out, size_t s) {\n\tif (s >= 1<<20) {\n\t\tsize_t t = s / (1 << 20);\n\t\treturn sprintf(_out, \"%d.%1dM\", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10));\n\t} else if (s >= 1<<10) {\n\t\tsize_t t = s / (1 << 10);\n\t\treturn sprintf(_out, \"%d.%1dK\", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10));\n\t} else {\n\t\treturn sprintf(_out, \"%d\", (int)s);\n\t}\n}\n\nstatic void update_column_widths(int * widths, struct tfile * file) {\n\tchar tmp[256];\n\tint n;\n\n\t/* Links */\n\tTRACE(\"links\");\n\tn = sprintf(tmp, \"%d\", file->statbuf.st_nlink);\n\tif (n > widths[0]) widths[0] = n;\n\n\t/* User */\n\tTRACE(\"user\");\n\tn = print_username(tmp, file->statbuf.st_uid);\n\tif (n > widths[1]) widths[1] = n;\n\n\t/* Group */\n\tTRACE(\"group\");\n\tn = print_username(tmp, file->statbuf.st_gid);\n\tif (n > widths[2]) widths[2] = n;\n\n\t/* File size */\n\tTRACE(\"file size\");\n\tif (human_readable) {\n\t\tn = print_human_readable_size(tmp, file->statbuf.st_size);\n\t} else {\n\t\tn = sprintf(tmp, \"%d\", (int)file->statbuf.st_size);\n\t}\n\tif (n > widths[3]) widths[3] = n;\n}\n\nstatic void print_entry_long(int * widths, struct tfile * file) {\n\tconst char * ansi_color_str = color_str(&file->statbuf);\n\n\t/* file permissions */\n\tif (S_ISLNK(file->statbuf.st_mode))       { printf(\"l\"); }\n\telse if (S_ISCHR(file->statbuf.st_mode))  { printf(\"c\"); }\n\telse if (S_ISBLK(file->statbuf.st_mode))  { printf(\"b\"); }\n\telse if (S_ISDIR(file->statbuf.st_mode))  { printf(\"d\"); }\n\telse { printf(\"-\"); }\n\tprintf( (file->statbuf.st_mode & S_IRUSR) ? \"r\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IWUSR) ? \"w\" : \"-\");\n\tif (file->statbuf.st_mode & S_ISUID) {\n\t\tprintf(\"s\");\n\t} else {\n\t\tprintf( (file->statbuf.st_mode & S_IXUSR) ? \"x\" : \"-\");\n\t}\n\tprintf( (file->statbuf.st_mode & S_IRGRP) ? \"r\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IWGRP) ? \"w\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IXGRP) ? \"x\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IROTH) ? \"r\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IWOTH) ? \"w\" : \"-\");\n\tprintf( (file->statbuf.st_mode & S_IXOTH) ? \"x\" : \"-\");\n\n\tprintf( \" %*d \", widths[0], file->statbuf.st_nlink); /* number of links, not supported */\n\n\tchar tmp[100];\n\tprint_username(tmp, file->statbuf.st_uid);\n\tprintf(\"%-*s \", widths[1], tmp);\n\tprint_username(tmp, file->statbuf.st_gid);\n\tprintf(\"%-*s \", widths[2], tmp);\n\n\tif (human_readable) {\n\t\tprint_human_readable_size(tmp, file->statbuf.st_size);\n\t\tprintf(\"%*s \", widths[3], tmp);\n\t} else {\n\t\tprintf(\"%*d \", widths[3], (int)file->statbuf.st_size);\n\t}\n\n\tchar time_buf[80];\n\tstruct tm * timeinfo = localtime((time_t*)&file->statbuf.st_mtime);\n\tif (timeinfo->tm_year == this_year) {\n\t\tstrftime(time_buf, 80, \"%b %d %H:%M\", timeinfo);\n\t} else {\n\t\tstrftime(time_buf, 80, \"%b %d  %Y\", timeinfo);\n\t}\n\tprintf(\"%s \", time_buf);\n\n\t/* Print the file name */\n\tif (stdout_is_tty) {\n\t\tprintf(\"\\033[%sm%s\\033[0m\", ansi_color_str, file->name);\n\t\tif (S_ISLNK(file->statbuf.st_mode)) {\n\t\t\tconst char * s = color_str(&file->statbufl);\n\t\t\tprintf(\" -> \\033[%sm%s\\033[0m\", s, file->link);\n\t\t}\n\t} else {\n\t\tprintf(\"%s\", file->name);\n\t\tif (S_ISLNK(file->statbuf.st_mode)) {\n\t\t\tprintf(\" -> %s\", file->link);\n\t\t}\n\t}\n\n\tprintf(\"\\n\");\n}\n\nstatic void show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"ls - list files\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-lha] [path]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -a     \\033[3mlist all files (including . files)\\033[0m\\n\"\n\t\t\t\" -l     \\033[3muse a long listing format\\033[0m\\n\"\n\t\t\t\" -h     \\033[3mhuman-readable file sizes\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nstatic void display_tfiles(struct tfile ** ents_array, int numents) {\n\tif (long_mode) {\n\t\tTRACE(\"long mode display, column lengths\");\n\t\tint widths[4] = {0,0,0,0};\n\t\tfor (int i = 0; i < numents; i++) {\n\t\t\tupdate_column_widths(widths, ents_array[i]);\n\t\t}\n\t\tTRACE(\"actual printing\");\n\t\tfor (int i = 0; i < numents; i++) {\n\t\t\tprint_entry_long(widths, ents_array[i]);\n\t\t}\n\t} else {\n\t\t/* Determine the gridding dimensions */\n\t\tint ent_max_len = 0;\n\t\tfor (int i = 0; i < numents; i++) {\n\t\t\tent_max_len = MAX(ent_max_len, (int)strlen(ents_array[i]->name));\n\t\t}\n\n\t\tint col_ext = ent_max_len + MIN_COL_SPACING;\n\t\tint cols = ((term_width - ent_max_len) / col_ext) + 1;\n\n\t\t/* Print the entries */\n\n\t\tfor (int i = 0; i < numents;) {\n\n\t\t\t/* Columns */\n\t\t\tprint_entry(ents_array[i++], ent_max_len);\n\n\t\t\tfor (int j = 0; (i < numents) && (j < (cols-1)); j++) {\n\t\t\t\tprintf(\"  \");\n\t\t\t\tprint_entry(ents_array[i++], ent_max_len);\n\t\t\t}\n\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t}\n}\n\nstatic int display_dir(char * p) {\n\t/* Open the directory */\n\tDIR * dirp = opendir(p);\n\tif (dirp == NULL) {\n\t\treturn 2;\n\t}\n\n\tif (print_dir) {\n\t\tprintf(\"%s:\\n\", p);\n\t}\n\n\t/* Read the entries in the directory */\n\tlist_t * ents_list = list_create();\n\n\tTRACE(\"reading entries\");\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (show_hidden || (ent->d_name[0] != '.')) {\n\t\t\tstruct tfile * f = malloc(sizeof(struct tfile));\n\n\t\t\tf->name = strdup(ent->d_name);\n\n\t\t\tchar tmp[strlen(p)+strlen(ent->d_name)+2];\n\t\t\tsprintf(tmp, \"%s/%s\", p, ent->d_name);\n\t\t\tlstat(tmp, &f->statbuf);\n\t\t\tif (S_ISLNK(f->statbuf.st_mode)) {\n\t\t\t\tstat(tmp, &f->statbufl);\n\t\t\t\tf->link = malloc(4096);\n\t\t\t\treadlink(tmp, f->link, 4096);\n\t\t\t}\n\n\t\t\tlist_insert(ents_list, (void *)f);\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tTRACE(\"copying\");\n\n\t/* Now, copy those entries into an array (for sorting) */\n\n\tif (!ents_list->length) return 0;\n\n\tstruct tfile ** file_arr = malloc(sizeof(struct tfile *) * ents_list->length);\n\tint index = 0;\n\tforeach(node, ents_list) {\n\t\tfile_arr[index++] = (struct tfile *)node->value;\n\t}\n\n\tlist_free(ents_list);\n\n\tTRACE(\"sorting\");\n\tqsort(file_arr, index, sizeof(struct tfile *), filecmp_notypesort);\n\n\tTRACE(\"displaying\");\n\tdisplay_tfiles(file_arr, index);\n\n\tfree(file_arr);\n\n\treturn 0;\n}\n\nint main (int argc, char * argv[]) {\n\n\t/* Parse arguments */\n\tchar * p = \".\";\n\n\tif (argc > 1) {\n\t\tint c;\n\t\twhile ((c = getopt(argc, argv, \"ahl?\")) != -1) {\n\t\t\tswitch (c) {\n\t\t\t\tcase 'a':\n\t\t\t\t\tshow_hidden = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'h':\n\t\t\t\t\thuman_readable = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'l':\n\t\t\t\t\tlong_mode = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '?':\n\t\t\t\t\tshow_usage(argc, argv);\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tif (optind < argc) {\n\t\t\tp = argv[optind];\n\t\t}\n\t\tif (optind + 1 < argc) {\n\t\t\tprint_dir = 1;\n\t\t}\n\t}\n\n\n\tstdout_is_tty = isatty(STDOUT_FILENO);\n\n\tif (long_mode) {\n\t\tstruct tm * timeinfo;\n\t\tstruct timeval now;\n\t\tgettimeofday(&now, NULL); //time(NULL);\n\t\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\t\tthis_year = timeinfo->tm_year;\n\t}\n\n\tif (stdout_is_tty) {\n\t\tTRACE(\"getting display size\");\n\t\tstruct winsize w;\n\t\tioctl(1, TIOCGWINSZ, &w);\n\t\tterm_width = w.ws_col;\n\t\tterm_height = w.ws_row;\n\t\tterm_width -= 1; /* And this just helps clean up our math */\n\t}\n\n\tint out = 0;\n\n\tif (argc == 1 || optind == argc) {\n\t\tTRACE(\"no file to look up\");\n\t\tif (display_dir(p) == 2) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], p, strerror(errno));\n\t\t}\n\t} else {\n\t\tlist_t * files = list_create();\n\t\twhile (p) {\n\t\t\tstruct tfile * f = malloc(sizeof(struct tfile));\n\n\t\t\tf->name = p;\n\t\t\tint t = lstat(p, &f->statbuf);\n\n\t\t\tif (t < 0) {\n\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], p, strerror(errno));\n\t\t\t\tfree(f);\n\t\t\t\tout = 2;\n\t\t\t} else {\n\t\t\t\tif (S_ISLNK(f->statbuf.st_mode)) {\n\t\t\t\t\tstat(p, &f->statbufl);\n\t\t\t\t\tf->link = malloc(4096);\n\t\t\t\t\treadlink(p, f->link, 4096);\n\t\t\t\t}\n\t\t\t\tlist_insert(files, f);\n\t\t\t}\n\n\t\t\toptind++;\n\t\t\tif (optind >= argc) p = NULL;\n\t\t\telse p = argv[optind];\n\t\t}\n\n\t\tif (!files->length) {\n\t\t\t/* No valid entries */\n\t\t\treturn out;\n\t\t}\n\n\t\tstruct tfile ** file_arr = malloc(sizeof(struct tfile *) * files->length);\n\t\tint index = 0;\n\t\tforeach(node, files) {\n\t\t\tfile_arr[index++] = (struct tfile *)node->value;\n\t\t}\n\n\t\tlist_free(files);\n\n\t\tqsort(file_arr, index, sizeof(struct tfile *), filecmp);\n\n\t\tint first_directory = index;\n\n\t\tfor (int i = 0; i < index; ++i) {\n\t\t\tif (S_ISDIR(file_arr[i]->statbuf.st_mode)) {\n\t\t\t\tfirst_directory = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (first_directory) {\n\t\t\tdisplay_tfiles(file_arr, first_directory);\n\t\t}\n\n\t\tfor (int i = first_directory; i < index; ++i) {\n\t\t\tif (i != 0) {\n\t\t\t\tprintf(\"\\n\");\n\t\t\t}\n\t\t\tif (display_dir(file_arr[i]->name) == 2) {\n\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], file_arr[i]->name, strerror(errno));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn out;\n}\n\n"
  },
  {
    "path": "apps/lspci.c",
    "content": "/**\n * lspci - Print information about connected PCI devices.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <errno.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdint.h>\n\nstruct device_class {\n\tuint16_t id;\n\tchar * name;\n} _pci_classes[] = {\n\t{0x0101, \"IDE interface\"},\n\t{0x0102, \"Floppy disk controller\"},\n\t{0x0105, \"ATA controller\"},\n\t{0x0106, \"SATA controller\"},\n\t{0x0200, \"Ethernet controller\"},\n\t{0x0280, \"Network controller\"},\n\t{0x0300, \"VGA compatible controller\"},\n\t{0x0380, \"Display controller\"},\n\t{0x0401, \"Multimedia audio controller\"},\n\t{0x0403, \"Audio device\"},\n\t{0x0480, \"Multimedia controller\"},\n\t{0x0600, \"Host bridge\"},\n\t{0x0601, \"ISA bridge\"},\n\t{0x0604, \"PCI bridge\"},\n\t{0x0680, \"Bridge\"},\n\t{0x0780, \"Communication controller\"},\n\t{0x0805, \"SD Host controller\"},\n\t{0x0880, \"System peripheral\"},\n\t{0x0900, \"Keyboard\"},\n\t{0x0980, \"Input Device\"},\n\t{0x0c00, \"FireWire controller\"},\n\t{0x0c03, \"USB controller\"},\n\t{0x0c05, \"SMBus controller\"},\n\t{0x1180, \"Signal processing controller\"},\n};\n\nstruct {\n\tuint16_t id;\n\tconst char * name;\n} _pci_vendors[] = {\n\t{0x1013, \"Cirrus Logic\"},\n\t{0x1022, \"AMD\"},\n\t{0x106b, \"Apple, Inc.\"},\n\t{0x10de, \"NVIDIA Corp.\"},\n\t{0x1180, \"Ricoh Ct. Ltd.\"},\n\t{0x1234, \"Bochs/QEMU\"},\n\t{0x1274, \"Ensoniq\"},\n\t{0x15ad, \"VMWare\"},\n\t{0x1912, \"Renesas Electronics Corp.\"}, /* Formerly \"Renesas Technology Corp.\" */\n\t{0x1af4, \"Red Hat, Inc.\"}, /* virtio */\n\t{0x1b36, \"Red Hat, Inc.\"},\n\t{0x8086, \"Intel Corporation\"},\n\t{0x80EE, \"VirtualBox\"},\n};\n\nstruct {\n\tuint16_t ven_id;\n\tuint16_t dev_id;\n\tconst char * name;\n} _pci_devices[] = {\n\t{0x1013, 0x00b8, \"CLGD 54xx VGA Adapter\"},\n\t{0x1022, 0x2000, \"PCNet Ethernet Controller (pcnet)\"},\n\t{0x106b, 0x003f, \"OHCI Controller\"},\n\t{0x10de, 0x0a6c, \"Quadro NVS 3100M\"},\n\n\t/* Ricoh */\n\t{0x1180, 0xe822, \"MMC/SD Host Controller\"},\n\t{0x1180, 0xe230, \"R5U2xx Memory Stick Host Controller\"},\n\t{0x1180, 0xe832, \"R5C832 PCIe IEEE 1394 Controller\"},\n\n\t{0x1234, 0x1111, \"VGA BIOS Graphics Extensions\"},\n\t{0x1274, 0x1371, \"Creative Labs CT2518 (ensoniq audio)\"},\n\n\t/* VMWare */\n\t{0x15ad, 0x0740, \"VM Communication Interface\"},\n\t{0x15ad, 0x0405, \"SVGA II Adapter\"},\n\t{0x15ad, 0x0790, \"PCI bridge\"},\n\t{0x15ad, 0x07a0, \"PCI Express Root Port\"},\n\n\t/* Renesas */\n\t{0x1912, 0x0015, \"uPD720202 USB 3.0 Host Controller\"},\n\n\t/* Red Hat */\n\t{0x1af4, 0x1000, \"Virtio Network Device\"},\n\t{0x1af4, 0x1052, \"Virtio Input Device\"},\n\t{0x1b36, 0x0008, \"QEMU PCIe Host Bridge\"},\n\t{0x1b36, 0x000d, \"QEMU XHCI Host Controller\"},\n\n\t/* Intel */\n\t{0x8086, 0x0044, \"DRAM Controller\"},\n\t{0x8086, 0x0045, \"PCI Express x16 Root Port\"},\n\t{0x8086, 0x0046, \"Gen 5 HD Graphics\"},\n\t{0x8086, 0x1004, \"82543GC Gigabit Ethernet Controller (e1000)\"},\n\t{0x8086, 0x100e, \"82540EM Gigabit Ethernet Controller (e1000)\"},\n\t{0x8086, 0x100f, \"82545EM Gigabit Ethernet Controller (e1000)\"},\n\t{0x8086, 0x10d3, \"82574L Gigabit Ethernet Controller (e1000e)\"},\n\t{0x8086, 0x10ea, \"82577LM Gigabit Ethernet Controller (e1000)\"},\n\t{0x8086, 0x1237, \"PCI & Memory\"},\n\t{0x8086, 0x2415, \"82801AA AC'97 Audio Controller\"},\n\t{0x8086, 0x2448, \"82801 Mobile PCI Bridge\"},\n\t{0x8086, 0x2668, \"ICH6 HD Audio Controller\"},\n\t{0x8086, 0x29c0, \"DRAM Controller\"},\n\t{0x8086, 0x2918, \"ICH9 LPC Interface Controller\"},\n\t{0x8086, 0x2922, \"ICH9 6-port SATA Controller\"},\n\t{0x8086, 0x2930, \"ICH9 SMBus Controller\"},\n\t{0x8086, 0x3b07, \"QM57 Chipset LPC Interface Controller\"},\n\t{0x8086, 0x3b2f, \"ICH10 6-port SATA AHCI Controller\"},\n\t{0x8086, 0x3b30, \"ICH10 SMBus Controller\"},\n\t{0x8086, 0x3b32, \"ICH10 Thermal Subsystem\"},\n\t{0x8086, 0x3b34, \"ICH10 USB 2.0 Enhanced Host Controller\"},\n\t{0x8086, 0x3b3c, \"ICH10 USB 2.0 Enhanced Host Controller\"},\n\t{0x8086, 0x3b42, \"ICH10 PCI Express Root Port 1\"},\n\t{0x8086, 0x3b44, \"ICH10 PCI Express Root Port 2\"},\n\t{0x8086, 0x3b46, \"ICH10 PCI Express Root Port 3\"},\n\t{0x8086, 0x3b48, \"ICH10 PCI Express Root Port 4\"},\n\t{0x8086, 0x3b4a, \"ICH10 PCI Express Root Port 5\"},\n\t{0x8086, 0x3b4c, \"ICH10 PCI Express Root Port 6\"},\n\t{0x8086, 0x3b4e, \"ICH10 PCI Express Root Port 7\"},\n\t{0x8086, 0x3b50, \"ICH10 PCI Express Root Port 8\"},\n\t{0x8086, 0x3b56, \"ICH10 HD Audio Controller\"},\n\t{0x8086, 0x3b64, \"ICH10 HECI Controller\"},\n\t{0x8086, 0x422b, \"Centrino Ultimate-N 6300\"},\n\t{0x8086, 0x7000, \"PCI-to-ISA Bridge\"},\n\t{0x8086, 0x7010, \"IDE Interface\"},\n\t{0x8086, 0x7110, \"PIIX4 ISA\"},\n\t{0x8086, 0x7111, \"PIIX4 IDE\"},\n\t{0x8086, 0x7113, \"Power Management Controller\"},\n\t{0x8086, 0x7190, \"Host Bridge\"},\n\t{0x8086, 0x7191, \"AGP Bridge\"},\n\n\t/* VirtualBox */\n\t{0x80EE, 0xBEEF, \"Bochs/QEMU-compatible Graphics Adapter\"},\n\t{0x80EE, 0xCAFE, \"Guest Additions Device\"},\n};\n\nconst char * pci_class_lookup(unsigned short class_id) {\n\tfor (unsigned int i = 0; i < sizeof(_pci_classes)/sizeof(_pci_classes[0]); ++i) {\n\t\tif (_pci_classes[i].id == class_id) {\n\t\t\treturn _pci_classes[i].name;\n\t\t}\n\t}\n\treturn \"(unknown)\";\n}\n\nconst char * pci_vendor_lookup(unsigned short vendor_id) {\n\tfor (unsigned int i = 0; i < sizeof(_pci_vendors)/sizeof(_pci_vendors[0]); ++i) {\n\t\tif (_pci_vendors[i].id == vendor_id) {\n\t\t\treturn _pci_vendors[i].name;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nconst char * pci_device_lookup(unsigned short vendor_id, unsigned short device_id) {\n\tfor (unsigned int i = 0; i < sizeof(_pci_devices)/sizeof(_pci_devices[0]); ++i) {\n\t\tif (_pci_devices[i].ven_id == vendor_id && _pci_devices[i].dev_id == device_id) {\n\t\t\treturn _pci_devices[i].name;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void show_usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"lspci - show information about PCI devices\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-n]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -n     \\033[3mshow numeric device codes\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nint main(int argc, char * argv[]) {\n\tint numeric = 0;\n\tint opt;\n\tchar * query = NULL;\n\n\twhile ((opt = getopt(argc, argv, \"nq:?\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argv);\n\t\t\t\treturn 0;\n\t\t\tcase 'n':\n\t\t\t\tnumeric = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tquery = optarg;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tFILE * f = fopen(\"/proc/pci\",\"r\");\n\tif (!f) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], \"/proc/pci\", strerror(errno));\n\t\treturn 1;\n\t}\n\n\twhile (!feof(f)) {\n\t\tchar line[1024];\n\t\tfgets(line, 1024, f);\n\t\tif (line[0] == ' ' || line[0] == '\\n') {\n\t\t\t/* Skip; don't care about this information */\n\t\t\tcontinue;\n\t\t}\n\t\t/* Read bus, etc. verbatim */\n\t\tchar * device_bus   = line;\n\n\t\t/* Read device class */\n\t\tchar * device_class = strstr(line,\" (\");\n\t\tif (!device_class) {\n\t\t\tfprintf(stderr, \"%s: parse error - expected (\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t*device_class = '\\0';\n\t\tdevice_class++; /* space */\n\t\tdevice_class++; /* ( */\n\n\t\tchar * device_vendor = strstr(device_class, \", \");\n\t\tif (!device_vendor) {\n\t\t\tfprintf(stderr, \"%s: parse error - expected ,\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t*device_vendor = '\\0';\n\t\tdevice_vendor++; /* comma */\n\t\tdevice_vendor++; /* space */\n\n\t\tchar * device_code = strstr(device_vendor, \":\");\n\t\tif (!device_code) {\n\t\t\tfprintf(stderr, \"%s: parse error - expected :\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t*device_code = '\\0';\n\t\tdevice_code++; /* colon */\n\n\t\tchar * last_paren = strstr(device_code, \")\");\n\t\tif (!last_paren) {\n\t\t\tfprintf(stderr, \"%s: parse error - expected )\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t*last_paren = '\\0';\n\n\t\tif (query) {\n\t\t\tunsigned short vendor_id = strtoul(device_vendor, NULL, 16);\n\t\t\tunsigned short device_id = strtoul(device_code,   NULL, 16);\n\n\t\t\tchar * start = query;\n\t\t\twhile (start) {\n\t\t\t\tchar * colon = strchr(start, ':');\n\t\t\t\tif (!colon) return 2; /* Invalid query string */\n\t\t\t\t*colon = '\\0';\n\t\t\t\tchar * comma = strchr(colon+1, ',');\n\t\t\t\tif (comma) *comma = '\\0';\n\t\t\t\tunsigned long query_man = strtoul(start,NULL,16);\n\t\t\t\tunsigned long query_dev = strtoul(colon+1,NULL,16);\n\n\t\t\t\tif (query_man == vendor_id && query_dev == device_id) return 0;\n\n\t\t\t\t*colon = ':';\n\t\t\t\tif (comma) {\n\t\t\t\t\t*comma = ',';\n\t\t\t\t\tstart = comma + 1;\n\t\t\t\t} else {\n\t\t\t\t\tstart = NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (numeric) {\n\t\t\tfprintf(stdout, \"%s %s: %s:%s\\n\", device_bus, device_class, device_vendor, device_code);\n\t\t} else {\n\t\t\tunsigned short class_id  = strtoul(device_class, NULL, 16);\n\t\t\tunsigned short vendor_id = strtoul(device_vendor, NULL, 16);\n\t\t\tunsigned short device_id = strtoul(device_code,   NULL, 16);\n\t\t\tconst char * class_name  = pci_class_lookup(class_id);\n\t\t\tconst char * vendor_name = pci_vendor_lookup(vendor_id);\n\t\t\tconst char * device_name = pci_device_lookup(vendor_id, device_id);\n\t\t\tif (!vendor_name && !device_name) {\n\t\t\t\tfprintf(stdout, \"%s %s: %s:%s\\n\", device_bus, class_name, device_vendor, device_code);\n\t\t\t} else if (!vendor_name) {\n\t\t\t\tfprintf(stdout, \"%s %s: %s %s\\n\", device_bus, class_name, device_vendor, device_name);\n\t\t\t} else if (!device_name) {\n\t\t\t\tfprintf(stdout, \"%s %s: %s %s\\n\", device_bus, class_name, vendor_name, device_code);\n\t\t\t} else {\n\t\t\t\tfprintf(stdout, \"%s %s: %s %s\\n\", device_bus, class_name, vendor_name, device_name);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (query) return 1;\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/maybe-pdfviewer.krk",
    "content": "#!/bin/kuroko\nimport os\nimport kuroko\n\ntry:\n    os.stat('/usr/bin/pdfviewer')\n    os.execl('/usr/bin/pdfviewer','pdfviewer',kuroko.argv[1])\nexcept OSError:\n    if os.system('showdialog \"PDF Viewer\" \"/usr/share/icons/48/pdf.png\" \"Do you want to install the PDF viewer from the Package Manager?\"') == 0:\n        os.system('terminal gsudo sh -c \"msk update; msk install mupdf\"')\n        os.execl('/usr/bin/pdfviewer','pdfviewer',kuroko.argv[1])\n"
  },
  {
    "path": "apps/migrate.c",
    "content": "/**\n * @brief migrate - Relocate root filesystem to tmpfs\n *\n * Run as part of system startup to move the ext2 root ramdisk\n * into a flexible in-memory temporary filesystem, which allows\n * file creation and editing and is much faster than the using\n * the ext2 driver against the static in-memory ramdisk.\n *\n * Based on the original Python implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <string.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <dirent.h>\n#include <unistd.h>\n\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n\n#include <toaru/trace.h>\n#include <toaru/hashmap.h>\n#define TRACE_APP_NAME \"migrate\"\n\n#define TRACE_(...) do { \\\n\tif (_splash) { char _trace_tmp[512]; sprintf(_trace_tmp, __VA_ARGS__); fprintf(_splash, \":%s\", _trace_tmp); fflush(_splash); } \\\n\tif (_debug) { TRACE(__VA_ARGS__); } \\\n} while (0)\n\n#define CHUNK_SIZE 4096\n\nstatic int _debug = 0;\nstatic FILE * _splash = NULL;\n\nvoid copy_link(char * source, char * dest, int mode, int uid, int gid) {\n\t//fprintf(stderr, \"need to copy link %s to %s\\n\", source, dest);\n\tchar tmp[1024];\n\treadlink(source, tmp, 1024);\n\tsymlink(tmp, dest);\n\t//chmod(dest, mode); /* links don't have modes */\n\tchown(dest, uid, gid);\n}\n\nvoid copy_file(char * source, char * dest, int mode,int uid, int gid) {\n\t//fprintf(stderr, \"need to copy file %s to %s %x\\n\", source, dest, mode);\n\t//TRACE_(\"Copying %s...\", dest);\n\n\tint d_fd = open(dest, O_WRONLY | O_CREAT, mode);\n\tint s_fd = open(source, O_RDONLY);\n\n\tssize_t length;\n\n\tlength = lseek(s_fd, 0, SEEK_END);\n\tlseek(s_fd, 0, SEEK_SET);\n\n\t//fprintf(stderr, \"%d bytes to copy\\n\", length);\n\n\tchar buf[CHUNK_SIZE];\n\n\twhile (length > 0) {\n\t\tsize_t r = read(s_fd, buf, length < CHUNK_SIZE ? length : CHUNK_SIZE);\n\t\t//fprintf(stderr, \"copying %d bytes from %s to %s\\n\", r, source, dest);\n\t\twrite(d_fd, buf, r);\n\t\tlength -= r;\n\t\t//fprintf(stderr, \"%d bytes remaining\\n\", length);\n\t}\n\n\tclose(s_fd);\n\tclose(d_fd);\n\n\tchown(dest, uid, gid);\n\tchmod(dest, mode);\n}\n\nvoid copy_directory(char * source, char * dest, int mode, int uid, int gid) {\n\tDIR * dirp = opendir(source);\n\tif (dirp == NULL) {\n\t\tfprintf(stderr, \"Failed to copy directory %s\\n\", source);\n\t\treturn;\n\t}\n\n\tTRACE_(\"Copying %s/...\", dest);\n\t//fprintf(stderr, \"Creating %s\\n\", dest);\n\tif (!strcmp(dest, \"/\")) {\n\t\tdest = \"\";\n\t} else {\n\t\tmkdir(dest, mode);\n\t}\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (!strcmp(ent->d_name,\".\") || !strcmp(ent->d_name,\"..\")) {\n\t\t\t//fprintf(stderr, \"Skipping %s\\n\", ent->d_name);\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\t//fprintf(stderr, \"not skipping %s/%s → %s/%s\\n\", source, ent->d_name, dest, ent->d_name);\n\t\tstruct stat statbuf;\n\t\tchar tmp[strlen(source)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp, \"%s/%s\", source, ent->d_name);\n\t\tchar tmp2[strlen(dest)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp2, \"%s/%s\", dest, ent->d_name);\n\t\t//fprintf(stderr,\"%s → %s\\n\", tmp, tmp2);\n\t\tlstat(tmp,&statbuf);\n\t\tif (S_ISLNK(statbuf.st_mode)) {\n\t\t\tcopy_link(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t\t} else if (S_ISDIR(statbuf.st_mode)) {\n\t\t\tcopy_directory(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t\t} else if (S_ISREG(statbuf.st_mode)) {\n\t\t\tcopy_file(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid);\n\t\t} else {\n\t\t\tfprintf(stderr, \" %s is not any of the required file types?\\n\", tmp);\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tchown(dest, uid, gid);\n}\n\nvoid free_ramdisk(char * path) {\n\tint fd = open(path, O_RDONLY);\n\tioctl(fd, 0x4001, NULL);\n\tclose(fd);\n}\n\n#include \"../kernel/misc/args.c\"\nstatic hashmap_t * get_cmdline(void) {\n\tchar * results = args_from_procfs();\n\tif (results) free(results);\n\treturn kernel_args_map;\n}\n\nstatic int root_is_tmpfs(void) {\n\tFILE *f = fopen(\"/proc/mounts\", \"r\");\n\tif (!f) return 0;\n\n\tchar *line = NULL;\n\tsize_t len = 0;\n\tint found = 0;\n\n\twhile (getline(&line, &len, f) != -1) {\n\t\tif (strstr(line, \"/ tmpfs \") == line) {\n\t\t\tfound = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tfree(line);\n\tfclose(f);\n\treturn found;\n}\n\nint main(int argc, char * argv[]) {\n\n\thashmap_t * cmdline = get_cmdline();\n\n\tif (root_is_tmpfs()) {\n\t    fprintf(stderr, \"You have already migrated the filesystem.\\n\");\n\t\treturn 1;\n\t}\n\n\tif (hashmap_has(cmdline, \"logtoserial\")) {\n\t\t_debug = 1;\n\t}\n\n\t_splash = fopen(\"/dev/pex/splash\",\"r+\");\n\n\tif (hashmap_has(cmdline, \"root\")) {\n\t\tTRACE_(\"Original root was %s\", (char*)hashmap_get(cmdline, \"root\"));\n\t} else if (hashmap_has(cmdline,\"init\") && !strcmp(hashmap_get(cmdline,\"init\"),\"/dev/ram0\")) {\n\t\tTRACE_(\"Init is ram0, so this is probably a netboot image, going to assume root is /tmp/netboot.img\");\n\t\thashmap_set(cmdline,\"root\",\"/tmp/netboot.img\");\n\t} else {\n\t\tTRACE_(\"Fatal: Don't know how to boot this. No root set.\\n\");\n\t\treturn 1;\n\t}\n\n\tchar * root = hashmap_get(cmdline,\"root\");\n\n\tchar * start = hashmap_get(cmdline,\"_start\");\n\tif (!start) {\n\t\tstart = \"\";\n\t}\n\tchar * root_type = hashmap_get(cmdline,\"root_type\");\n\tif (!root_type) {\n\t\troot_type = \"tar\";\n\t}\n\n\tchar tmp[1024];\n\n\tTRACE_(\"Remounting root to /dev/base\");\n\tsprintf(tmp, \"mount %s %s /dev/base\", root_type, root);\n\tsystem(tmp);\n\n\tTRACE_(\"Mounting tmpfs to /\");\n\tsystem(\"mount tmpfs x,755 /\");\n\n\tTRACE_(\"Migrating root...\");\n\tcopy_directory(\"/dev/base\",\"/\",0660,0,0);\n\tsystem(\"mount tmpfs x,755 /dev/base\");\n\n\tif (strstr(root, \"/dev/ram\") != NULL) {\n\t\tchar * tmp = strdup(root);\n\t\tchar * c = strchr(tmp, ',');\n\t\tif (c) {\n\t\t\t*c = '\\0';\n\t\t}\n\t\tTRACE_(\"Freeing ramdisk at %s\", tmp);\n\t\tfree_ramdisk(tmp);\n\t\tfree(tmp);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/mines.krk",
    "content": "#!/bin/kuroko\n'''\nMinesweeper game\n\nOriginally written in Python and ported to Kuroko.\nVisual design is based on the Gnome \"Mines\".\n'''\nimport math\nimport random\nfrom _yutani2 import (Font, rgb, decor_get_bounds, decor_render, decor_handle_event, decor_show_default_menu,\n    MenuBar, MenuEntry, MenuEntrySeparator, MenuList, MenuEntrySubmenu)\nfrom yutani_mainloop import Window, AsyncMainloop\n\nlet app_version = '2.0.0'\nlet mainloop = AsyncMainloop()\n\ndef frgb(r,g,b):\n    '''RGB from float triplet'''\n    return rgb(int(255 * r),int(255 * g), int(255 * b))\n\ndef hsv_to_rgb(h,s,v):\n    '''HSV (radians and floats) to RGB (255/255/255) conversion.'''\n    let c  = v * s\n    let hp = math.fmod(h, 2 * math.pi)\n    let x = c * (1.0 - abs(math.fmod(hp / 1.0472, 2) - 1.0))\n    let m = v - c\n    let rp, gp, bp\n    if hp <= 1.0472:\n        rp = c; gp = x; bp = 0\n    else if hp <= 2.0944:\n        rp = x; gp = c; bp = 0\n    else if hp <= 3.1416:\n        rp = 0; gp = c; bp = x\n    else if hp <= 4.1888:\n        rp = 0; gp = x; bp = c\n    else if hp <= 5.2360:\n        rp = x; gp = 0; bp = c\n    else:\n        rp = c; gp = 0; bp = x\n    return rgb(int((rp + m) * 255), int((gp + m) * 255), int((bp + m) * 255))\n\ndef good_color(cnt):\n    '''Use neighbor count to pick a good color.'''\n    if cnt == 0:\n        return frgb(0.6,0.6,0.6)\n    let x = cnt / 8\n    let h = 1.95 - x * 3.145\n    return hsv_to_rgb(h,0.4,0.7)\n\nclass MineButton:\n    def __init__(self, action, r, c, is_mine, neighbor_mines):\n        self.row, self.col = r, c\n        self.is_mine = is_mine\n        self.font = Font(\"sans-serif\",13)\n        self.width = None\n        self.revealed = False\n        self.mines = neighbor_mines\n        self.flagged = False\n        self.hilight = 0\n        self.action = action\n        self.text = \"\"\n\n    def focus_enter(self):\n        self.hilight = 1\n\n    def focus_leave(self):\n        self.hilight = 0\n\n    def reveal(self):\n        if self.revealed: return\n        self.revealed = True\n        if self.is_mine:\n            self.text = \"✸\" # U+2738\n        else if self.mines == 0:\n            self.text = \"\"\n        else:\n            self.text = str(self.mines)\n\n    def set_flagged(self):\n        self.flagged = not self.flagged\n\n    def draw(self, window, x, y, w, h):\n        self.font.size = int(h * 0.75)\n        if self.width != w:\n            self.x, self.y, self.width, self.height = x, y, w, h\n        let color = rgb(255,255,255)\n        if self.revealed and self.is_mine:\n            color = frgb(0.4,0.4,0.4)\n        else if self.revealed:\n            color = good_color(self.mines)\n        else if self.hilight == 1:\n            color = frgb(0.7,0.7,0.7)\n        else if self.hilight == 2:\n            color = frgb(0.3,0.3,0.3)\n        window.rect(x+1, y+1, w-2, h-2, color, radius=3)\n        let text = self.text\n        if not self.revealed and self.flagged:\n            text = '⚑' # U+2691\n        if text:\n            self.font.draw_string(window,text,x+self.width//2-self.font.width(text)//2,int(y+self.height * 0.75))\n\ndef randrange(size):\n    return int(size * random.random())\n\nclass MinesWindow(Window):\n\n    base_width = 400\n    base_height = 440\n\n    def __init__(self):\n        let bounds = decor_get_bounds()\n        super().__init__(self.base_width + bounds['width'], self.base_height + bounds['height'], title=\"Mines\", icon=\"mines\", doublebuffer=True)\n        self.move(100,100)\n        self.button_width = {}\n        self.button_height = 0\n        self.subtitleText = 'Hello, world.'\n        self.subtitleFont = Font('sans-serif.bold',17)\n        self.mb = MenuBar(((\"Game\",'game'),(\"Help\",'help')))\n\n        let _menu_Game = MenuList()\n        _menu_Game.insert(MenuEntrySubmenu('New Game', icon='new', action='new-game'))\n        _menu_Game.insert(MenuEntrySeparator())\n        _menu_Game.insert(MenuEntry('Exit', lambda menu: self.close(), icon='exit'))\n        self.mb.insert('game',_menu_Game)\n        let _menu_Help = MenuList()\n        _menu_Help.insert(MenuEntry(\"About Mines\", self.launch_about, icon='star'))\n        self.mb.insert('help',_menu_Help)\n        let _menu_New_Game = MenuList()\n        _menu_New_Game.insert(MenuEntry(\"9×9, 10 mines\", lambda menu: self.basic_game()))\n        _menu_New_Game.insert(MenuEntry(\"16×16, 40 mines\", lambda menu: self.new_game((16,40))))\n        _menu_New_Game.insert(MenuEntry(\"20×20, 90 mines\", lambda menu: self.new_game((20,90))))\n        self.mb.insert('new-game',_menu_New_Game)\n        self.mb.callback = lambda x: self.draw()\n\n        self.hover_widget = None\n        self.down_button = None\n        self.top_height = bounds['top_height'] + 24 + 40\n\n        self.modifiers = 0\n\n        self.basic_game()\n\n    def launch_about(self, menu):\n        import os\n        os.system(f'about \"About Mines\" /usr/share/icons/48/mines.png \"Mines {app_version}\" \"© 2017-2023 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\" &')\n\n    def basic_game(self):\n        self.new_game((9,10))\n\n    def new_game(self, action):\n\n        self.first_click = True\n        self.field_size, self.mine_count = action\n        self.subtitleText = f'There are {self.mine_count} mines.'\n        self.mines = []\n        let i = 0\n        while len(self.mines) < self.mine_count:\n            let x, y = randrange(self.field_size), randrange(self.field_size)\n            i += 1\n            if (x, y) not in self.mines:\n                i = 0\n                self.mines.append((x,y))\n            if i > 50:\n                print(\"Board generation failed\")\n                return\n\n        def check_neighbors(r, c):\n            let n = []\n            if r > 0:\n                if c > 0:\n                    n.append((r-1,c-1))\n                n.append((r-1,c))\n                if c < self.field_size - 1:\n                    n.append((r-1,c+1))\n            if r < self.field_size - 1:\n                if c > 0:\n                    n.append((r+1,c-1))\n                n.append((r+1,c))\n                if c < self.field_size - 1:\n                    n.append((r+1,c+1))\n            if c > 0:\n                n.append((r,c-1))\n            if c < self.field_size - 1:\n                n.append((r,c+1))\n            return n\n\n        def check_neighbor_buttons(r,c):\n            return [self.buttons[x][y] for x, y in check_neighbors(r,c)]\n\n        def mine_func(b):\n            let button = b\n            if self.first_click:\n                let i = 0\n                while button.is_mine or button.mines:\n                    if i > 30:\n                        print(\"Failed to generate board\")\n                        return\n                    self.new_game(action)\n                    button = self.buttons[button.row][button.col]\n                    i += 1\n                self.first_click = False\n            if button.flagged:\n                return\n            if button.is_mine and not button.revealed:\n                self.subtitleText = \"You lose.\"\n                for row in self.buttons:\n                    for b in row:\n                        b.reveal()\n                self.draw()\n                return\n            else:\n                if not button.revealed:\n                    button.reveal()\n                    if button.mines == 0:\n                        let n = [x for x in check_neighbor_buttons(button.row,button.col) if not x.revealed]\n                        while n:\n                            b = n.pop()\n                            b.reveal()\n                            if b.mines == 0:\n                                n.extend([x for x in check_neighbor_buttons(b.row,b.col) if not x.revealed and not x in n])\n                    self.check_win()\n\n        self.buttons = []\n        for row in range(self.field_size):\n            let r = []\n            for col in range(self.field_size):\n                let is_mine = (row,col) in self.mines\n                let neighbor_mines = len([x for x in check_neighbors(row,col) if x in self.mines])\n                r.append(MineButton(mine_func, row, col, is_mine, neighbor_mines))\n            self.buttons.append(r)\n\n    def check_win(self):\n        let buttons = []\n        for row in self.buttons:\n            buttons.extend(row)\n        let n_flagged = len([x for x in buttons if x.flagged and not x.revealed])\n        let n_revealed = len([x for x in buttons if x.revealed])\n        if n_flagged == self.mine_count and n_revealed + n_flagged == self.field_size ** 2:\n            self.subtitleText = \"You win.\"\n            for b in buttons:\n                b.reveal()\n\n    def draw(self):\n        self.fill(rgb(204,204,204))\n        let bounds = decor_get_bounds(self)\n        self.subtitleFont.draw_string(self,self.subtitleText,\n            (self.width - self.subtitleFont.width(self.subtitleText)) // 2,\n            bounds['top_height'] + 24 + 25)\n        let offset_x = bounds['left_width']\n        let offset_y = self.top_height\n        self.button_height = (self.height - self.top_height - bounds['bottom_height']) // len(self.buttons)\n        let i = 0\n        for row in self.buttons:\n            self.button_width[i] = (self.width - bounds['width']) // len(row)\n            for button in row:\n                if button:\n                    button.draw(self,offset_x,offset_y,self.button_width[i],self.button_height)\n                offset_x += self.button_width[i]\n            offset_x = bounds['left_width']\n            offset_y += self.button_height\n            i += 1\n        self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self)\n        self.mb.render(self)\n        decor_render(self)\n        self.flip()\n\n    def flag(self, button):\n        button.set_flagged()\n        self.check_win()\n        self.draw()\n\n    def mouse_event(self, msg):\n        let decResponse = decor_handle_event(msg)\n        if decResponse == 2:\n            self.close()\n            return True\n        else if decResponse == 5:\n            decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y)\n\n        let bounds = decor_get_bounds(self)\n        let x, y = msg.new_x, msg.new_y\n        let w, h = self.width, self.height\n\n        if x >= 0 and x < w and y < self.top_height:\n            if self.hover_widget:\n                self.hover_widget.focus_leave()\n                self.hover_widget = None\n                self.draw()\n            self.mb.mouse_event(self, msg)\n            return\n\n        let redraw = False\n        if self.down_button:\n            if msg.command == 2 or msg.command == 0: # RAISE or CLICK\n                if not (msg.buttons & 1): # BUTTON_LEFT\n                    if x >= self.down_button.x and \\\n                        x < self.down_button.x + self.down_button.width and \\\n                        y >= self.down_button.y and \\\n                        y < self.down_button.y + self.down_button.height:\n                            self.down_button.focus_enter()\n                            if self.modifiers & 1: # LEFT_CTRL\n                                self.flag(self.down_button)\n                            else:\n                                self.down_button.action(self.down_button)\n                            self.down_button = None\n                            redraw = True\n                    else:\n                        self.down_button.focus_leave()\n                        self.down_button = None\n                        redraw = True\n        else:\n            # TOOD decors, menubar\n            if y >= self.top_height and y < h and x >= bounds['left_width'] and x < w:\n                let button\n                let xh = self.button_height * len(self.buttons)\n                let row = ((y - self.top_height) * len(self.buttons)) // xh\n                if row < len(self.buttons):\n                    let xw = self.button_width[row] * len(self.buttons[row])\n                    let col = ((x - bounds['left_width']) * len(self.buttons[row])) // xw\n                    if col < len(self.buttons[row]):\n                        button = self.buttons[row][col]\n                    else:\n                        button = None\n                else:\n                    button = None\n                if button is not self.hover_widget:\n                    if button:\n                        button.focus_enter()\n                        redraw = True\n                    if self.hover_widget:\n                        self.hover_widget.focus_leave()\n                        redraw = True\n                    self.hover_widget = button\n                if msg.command == 3: # DOWN\n                    if button:\n                        button.hilight = 2\n                        self.down_button = button\n                        redraw = True\n            else:\n                if self.hover_widget:\n                    self.hover_widget.focus_leave()\n                    redraw = True\n                self.hover_widget = None\n        if redraw:\n            self.draw()\n\n    def keyboard_event(self, msg):\n        self.modifiers = msg.modifiers\n\n    def close(self):\n        super().close()\n        mainloop.exit()\n\n\nlet window = MinesWindow()\nwindow.draw()\n\nmainloop.run()\n"
  },
  {
    "path": "apps/misaka-test.c",
    "content": "/**\n * @file  apps/misaka-test.c\n * @brief Test app for Misaka with a bunch of random stuff.\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sched.h>\n#include <sys/wait.h>\n\n#include <toaru/graphics.h>\n\n#include <kuroko/kuroko.h>\n#include <kuroko/vm.h>\n\nstatic void demo_runKurokoSnippet(void) {\n\tkrk_initVM(0);\n\tkrk_startModule(\"__main__\");\n\tkrk_interpret(\"import kuroko\\nprint('Kuroko',kuroko.version)\\n\", \"<stdin>\");\n\tkrk_freeVM();\n}\n\nstatic void demo_drawWallpaper(void) {\n\t/* Set up a wrapper context for the framebuffer */\n\tgfx_context_t * ctx = init_graphics_fullscreen();\n\n\t/* Load the wallpaper. */\n\tsprite_t wallpaper = { 0 };\n\tload_sprite(&wallpaper, \"/usr/share/wallpaper.jpg\");\n\twallpaper.alpha = ALPHA_EMBEDDED;\n\n\tprintf(\"wallpaper sprite info: %d x %d\\n\", wallpaper.width, wallpaper.height);\n\n\tdraw_sprite_scaled(ctx, &wallpaper, 0, 0, 1440, 900);\n\tflip(ctx);\n\t//blur_context_box(&ctx, 10);\n}\n\nint main(int argc, char * argv[]) {\n\tdemo_drawWallpaper();\n\tdemo_runKurokoSnippet();\n\n\t//execve(\"/bin/kuroko\",(char*[]){\"kuroko\",NULL},(char*[]){NULL});\n\tchar * args[] = {\n\t\t\"/bin/sh\",\n\t\t\"-c\",\n\t\t\"sleep 2; echo hi; echo glorp\",\n\t\tNULL,\n\t};\n\tpid_t pid = fork();\n\tif (!pid) {\n\t\tprintf(\"returned from fork in child\\n\");\n\t\texecvp(args[0], args);\n\t\texit(1);\n\t} else {\n\t\tprintf(\"returned from fork with pid = %d\\n\", pid);\n\t\tint status;\n\t\twaitpid(pid, &status, 0);\n\t\tprintf(\"done with waitpid, looping\\n\");\n\t\twhile (1) {\n\t\t\tsched_yield();\n\t\t}\n\t\treturn WEXITSTATUS(status);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/mixerctl.c",
    "content": "/**\n * @brief Control audio mixer knobs\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015 Mike Gerow\n */\n\n#include <fcntl.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/ioctl.h>\n#include <unistd.h>\n\n#include <kernel/mod/sound.h>\n\nstatic char usage[] =\n\"%s - Control audio mixer settings.\\n\"\n\"\\n\"\n\"Usage  %s [-d device_id] -l\\n\"\n\"       %s [-d device_id] [-k knob_id] -r\\n\"\n\"       %s [-d device_id] [-k knob_id] -w knob_value\\n\"\n\"       %s -h\\n\"\n\"\\n\"\n\" -d: \\033[3mDevice id to address. Defaults to the main sound device.\\033[0m\\n\"\n\" -l: \\033[3mList the knobs on a device.\\033[0m\\n\"\n\" -k: \\033[3mKnob id to address. Defaults to the device's master knob.\\033[0m\\n\"\n\" -r: \\033[3mPerform a read on the given device's knob. Defaults to the device's\\n\"\n\"     master knob.\\033[0m\\n\"\n\" -w: \\033[3mPerform a write on the given device's knob. The value should be a\\n\"\n\"     float from 0.0 to 1.0.\\033[0m\\n\"\n\" -h: \\033[3mPrint this help message and exit.\\033[0m\\n\";\n\nint main(int argc, char * argv[]) {\n\tuint32_t device_id = SND_DEVICE_MAIN;\n\tuint32_t knob_id   = SND_KNOB_MASTER;\n\tuint8_t list_flag = 0;\n\tuint8_t read_flag  = 0;\n\tuint8_t write_flag = 0;\n\tdouble write_value = 0.0;\n\n\tint c;\n\n\twhile ((c = getopt(argc, argv, \"d:lk:rw:h?\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'd':\n\t\t\t\tdevice_id = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tlist_flag = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'k':\n\t\t\t\tknob_id = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tread_flag = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\twrite_flag = 1;\n\t\t\t\twrite_value = atof(optarg);\n\t\t\t\tif (write_value < 0.0 || write_value > 1.0) {\n\t\t\t\t\tfprintf(stderr, \"argument -w value must be between 0.0 and 1.0\\n\");\n\t\t\t\t\texit(EXIT_FAILURE);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\tcase '?':\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, usage, argv[0], argv[0], argv[0], argv[0], argv[0]);\n\t\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t}\n\n\tint mixer = open(\"/dev/mixer\", O_RDONLY);\n\tif (mixer < 1) {\n\t\t//perror(\"open\");\n\t\texit(EXIT_FAILURE);\n\t}\n\n\tif (list_flag) {\n\t\tsnd_knob_list_t list = {0};\n\t\tlist.device = device_id;\n\t\tif (ioctl(mixer, SND_MIXER_GET_KNOBS, &list) < 0) {\n\t\t\tperror(\"ioctl\");\n\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t\tfor (uint32_t i = 0; i < list.num; i++) {\n\t\t\tsnd_knob_info_t info = {0};\n\t\t\tinfo.device = device_id;\n\t\t\tinfo.id = list.ids[i];\n\t\t\tif (ioctl(mixer, SND_MIXER_GET_KNOB_INFO, &info) < 0) {\n\t\t\t\tperror(\"ioctl\");\n\t\t\t\texit(EXIT_FAILURE);\n\t\t\t}\n\t\t\tfprintf(stdout, \"%d: %s\\n\", (unsigned int)info.id, info.name);\n\t\t}\n\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\tif (read_flag) {\n\t\tsnd_knob_value_t value = {0};\n\t\tvalue.device = device_id;\n\t\tvalue.id = knob_id;\n\t\tif (ioctl(mixer, SND_MIXER_READ_KNOB, &value) < 0) {\n\t\t\tperror(\"ioctl\");\n\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t\tdouble double_val = (double)value.val / SND_KNOB_MAX_VALUE;\n\t\tfprintf(stdout, \"%f\\n\", double_val);\n\t\texit(EXIT_FAILURE);\n\t}\n\n\tif (write_flag) {\n\t\tsnd_knob_value_t value = {0};\n\t\tvalue.device = device_id;\n\t\tvalue.id = knob_id;\n\t\tvalue.val = (uint32_t)(write_value * SND_KNOB_MAX_VALUE);\n\t\tif (ioctl(mixer, SND_MIXER_WRITE_KNOB, &value) < 0) {\n\t\t\tperror(\"ioctl\");\n\t\t\texit(EXIT_FAILURE);\n\t\t}\n\t\texit(EXIT_SUCCESS);\n\t}\n\n\tfprintf(stderr, \"No operation specified.\\n\");\n\texit(EXIT_FAILURE);\n}\n"
  },
  {
    "path": "apps/mkdir.c",
    "content": "/**\n * @brief Create directories\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2014 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <errno.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\nint makedir(const char * dir, int mask, int parents) {\n\tif (!parents) return mkdir(dir,mask);\n\n\tchar * tmp = strdup(dir);\n\tchar * c = tmp;\n\twhile ((c = strchr(c+1,'/'))) {\n\t\t*c = '\\0';\n\t\tif (mkdir(tmp,mask) < 0) {\n\t\t\tif (errno == EEXIST) {\n\t\t\t\t*c = '/';\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t\t*c = '/';\n\t\tcontinue;\n\t}\n\n\treturn mkdir(tmp, mask);\n}\n\nint main(int argc, char ** argv) {\n\tint retval = 0;\n\tint parents = 0;\n\tint opt;\n\n\twhile ((opt = getopt(argc, argv, \"m:p\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'm':\n\t\t\t\tfprintf(stderr, \"%s: -m unsupported\\n\", argv[0]);\n\t\t\t\treturn 1;\n\t\t\tcase 'p':\n\t\t\t\tparents = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind == argc) {\n\t\tfprintf(stderr, \"%s: expected argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tif (makedir(argv[i], 0777, parents) < 0) {\n\t\t\tif (parents && errno == EEXIST) continue;\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tretval = 1;\n\t\t}\n\t}\n\n\treturn retval;\n}\n"
  },
  {
    "path": "apps/mktemp.c",
    "content": "/**\n * @brief mktemp - create a temporary directory and print its name\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <getopt.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/stat.h>\n\nint main(int argc, char * argv[]) {\n\tint opt;\n\tint dry_run = 0;\n\tint quiet = 0;\n\tint directory = 0;\n\n\twhile ((opt = getopt(argc,argv,\"duq\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'd':\n\t\t\t\tdirectory = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'u':\n\t\t\t\tdry_run = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tquiet = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tchar * template;\n\tint i = optind;\n\n\tif (i == argc) {\n\t\ttemplate = strdup(\"/tmp/tmp.XXXXXX\");\n\t} else {\n\t\ttemplate = strdup(argv[i]);\n\t}\n\n\tchar * result = mktemp(template);\n\n\tif (!result) {\n\t\tfprintf(stderr, \"%s: %s\\n\", argv[0], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tif (!quiet) {\n\t\tfprintf(stdout, \"%s\\n\", result);\n\t}\n\n\tif (!dry_run) {\n\t\tif (directory) {\n\t\t\tif (mkdir(result,0777) < 0) {\n\t\t\t\tfprintf(stderr, \"%s: mkdir: %s: %s\\n\", argv[0], result, strerror(errno));\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t} else {\n\t\t\tFILE * f = fopen(result,\"w\");\n\t\t\tif (!f) {\n\t\t\t\tfprintf(stderr, \"%s: open: %s: %s\\n\", argv[0], result, strerror(errno));\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/more.c",
    "content": "/**\n * @brief Print piped input or files one screenful at a time.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <errno.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <string.h>\n#include <wchar.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/ioctl.h>\n\n#include <toaru/decodeutf8.h>\n\nstatic int term_width = 80;\nstatic int term_height = 25;\n\nstatic int term_x = 0;\nstatic int term_yish = 1;\n\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\nstatic void char_draw(int c) {\n\tif (c == '\\t') {\n\t\tint count = 8 - (term_x % 8);\n\t\tfor (int i = 0; i < count; ++i) {\n\t\t\tprintf(\" \");\n\t\t}\n\t} else if (c < 32 || c == 0x7F) {\n\t\tprintf(\"\\033[7m^%c\\033[0m\", (c < 32) ? ('@' + c) : '?');\n\t} else if (c > 0x7f && c < 0xa0) {\n\t\tprintf(\"\\033[7m<%02x>\\033[0m\", c);\n\t} else if (c == 0xa0) {\n\t\tprintf(\"\\033[7m \\033[0m\");\n\t} else if (c > 127) {\n\t\tif (wcwidth(c) >= 1) {\n\t\t\tchar tmp[8] = {0};\n\t\t\tto_eight(c,tmp);\n\t\t\tprintf(\"%s\", tmp);\n\t\t} else {\n\t\t\tif (c < 0x10000) {\n\t\t\t\tprintf(\"\\033[7m[U+%04x]\\033[0m\", c);\n\t\t\t} else {\n\t\t\t\tprintf(\"\\033[7m[U+%06x]\\033[0m\", c);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tprintf(\"%c\", c);\n\t}\n}\n\nstatic int char_width(int c) {\n\tif (c == '\\t') {\n\t\treturn 8 - (term_x % 8);\n\t} else if (c < 32 || c == 0x7F) {\n\t\treturn 2; /* ^@ */\n\t} else if (c > 0x7f && c < 0xa0) {\n\t\treturn 4; /* <XX> */\n\t} else if (c == 0xa0) {\n\t\treturn 1; /* nbsp */\n\t} else if (c > 127) {\n\t\tint out = wcwidth(c);\n\t\tif (out >= 1) return out;\n\t\treturn (c < 0x10000) ? 8 : 10;\n\t}\n\treturn 1;\n}\n\nstatic struct termios old;\nstatic void get_initial_termios(void) {\n\ttcgetattr(STDOUT_FILENO, &old);\n}\n\nstatic void set_unbuffered(void) {\n\tstruct termios new = old;\n\tnew.c_iflag &= (~ICRNL) & (~IXON);\n\tnew.c_lflag &= (~ICANON) & (~ECHO);\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);\n}\n\nstatic void set_buffered(void) {\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);\n}\n\nstatic void next_line(void) {\n\tterm_yish++;\n\tif (term_yish < term_height) {\n\t\tprintf(\"\\n\");\n\t\tterm_x = 0;\n\t} else {\n\t\tprintf(\"\\n\\033[7m--More--\\033[0m\");\n\t\tfflush(stdout);\n\t\tdo {\n\t\t\tchar buf[1];\n\t\t\tread(STDERR_FILENO, buf, 1);\n\t\t\tchar c = buf[0];\n\t\t\tswitch (c) {\n\t\t\t\tcase ' ':\n\t\t\t\t\tterm_yish = 1;\n\t\t\t\t\t/* fallthrough */\n\t\t\t\tcase '\\n':\n\t\t\t\tcase '\\r':\n\t\t\t\t\tprintf(\"\\r\\033[K\");\n\t\t\t\t\tfflush(stdout);\n\t\t\t\t\tterm_x = 0;\n\t\t\t\t\treturn;\n\t\t\t\tcase 'q':\n\t\t\t\t\tprintf(\"\\r\\033[K\");\n\t\t\t\t\tfflush(stdout);\n\t\t\t\t\tset_buffered();\n\t\t\t\t\texit(0);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t} while (1);\n\t}\n}\n\nstatic void do_file(char * name, FILE * f) {\n\tif (!f) {\n\t\tprintf(\"\\033[7m`%s`: %s\\033[0m\", name, strerror(errno));\n\t\tnext_line();\n\t\treturn;\n\t}\n\tuint32_t code, state = 0;\n\twhile (!feof(f)) {\n\t\tint c = fgetc(f);\n\t\tif (c < 0) break;\n\t\tif (!decode(&state, &code, c)) {\n\t\t\tif (code == '\\n') next_line();\n\t\t\telse {\n\t\t\t\tint width = char_width(code);\n\t\t\t\tif (term_x + width > term_width) {\n\t\t\t\t\tnext_line();\n\t\t\t\t}\n\t\t\t\tchar_draw(code);\n\t\t\t\tterm_x += width;\n\t\t\t}\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\tstate = 0;\n\t\t}\n\t}\n}\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2 && isatty(STDIN_FILENO)) {\n\t\tfprintf(stderr, \"usage: %s file...\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tstruct winsize w;\n\tioctl(STDOUT_FILENO, TIOCGWINSZ, &w);\n\tterm_width  = w.ws_col;\n\tterm_height = w.ws_row;\n\tget_initial_termios();\n\tset_unbuffered();\n\n\tif (argc < 2) {\n\t\tdo_file(\"stdin\",stdin);\n\t}\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tFILE * f = fopen(argv[i], \"r\");\n\t\tdo_file(argv[i], f);\n\t\tif (f) fclose(f);\n\t}\n\n\tset_buffered();\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/mount.c",
    "content": "/**\n * @brief Mount filesystems into the VFS\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n\n#include <stdio.h>\n#include <errno.h>\n#include <string.h>\n#include <sys/mount.h>\n\nint main(int argc, char ** argv) {\n\tif (argc < 4) {\n\t\tfprintf(stderr, \"Usage: %s type device mountpoint\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint ret = mount(argv[2], argv[3], argv[1], 0, NULL);\n\n\tif (ret < 0) {\n\t\tfprintf(stderr, \"%s: %s\\n\", argv[0], strerror(errno));\n\t\treturn ret;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/msk.c",
    "content": "/**\n * @brief Package Management Utility for ToaruOS\n *\n * This is a not-quite-faithful reconstruction of the original\n * Python msk. The supported package format is a bit different,\n * to avoid the need to implement a full JSON parser.\n *\n * Packages can optionally be uncompressed, which is also\n * important for bootstrapping at the moment.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n\n#include <toaru/confreader.h>\n#include <toaru/list.h>\n#include <toaru/hashmap.h>\n\n#define MSK_VERSION \"1.0.0\"\n#define VAR_PATH \"/var/msk\"\n#define LOCK_PATH \"/var/run/msk.lock\"\n\nstatic confreader_t * msk_config = NULL;\nstatic confreader_t * msk_manifest = NULL;\nstatic hashmap_t *    msk_installed = NULL;\nstatic int lock_fd = -1;\n\nstatic int verbose = 0;\n\nstatic void release_lock(void) {\n\tif (lock_fd != -1) {\n\t\tunlink(LOCK_PATH);\n\t}\n}\n\nstatic void needs_lock(void) {\n\tif (lock_fd == -1) {\n\t\tlock_fd = open(LOCK_PATH, O_RDWR|O_CREAT|O_EXCL);\n\t\tif (lock_fd < 0) {\n\t\t\tfprintf(stderr, \"msk: failed to obtain exclusive lock\\n\");\n\t\t\texit(1);\n\t\t}\n\t\tatexit(release_lock);\n\t}\n}\n\n/**\n * checks whether 'candidate' is newer than 'current'.\n *\n * Requires version strings to be of the form x.y.z\n *\n * > 0   candidate is newer\n * = 0   candidate is the same\n * < 0   candidate is older\n */\nstatic int compare_version_strings(char * current, char * candidate) {\n\tint current_x, current_y, current_z;\n\tint candidate_x, candidate_y, candidate_z;\n\n\tsscanf(current, \"%d.%d.%d\", &current_x, &current_y, &current_z);\n\tsscanf(candidate, \"%d.%d.%d\", &candidate_x, &candidate_y, &candidate_z);\n\n\tif (candidate_x > current_x) {\n\t\treturn 1;\n\t} else if (candidate_x == current_x) {\n\t\tif (candidate_y >current_y) {\n\t\t\treturn 1;\n\t\t} else if (candidate_y == current_y) {\n\t\t\tif (candidate_z > current_z) {\n\t\t\t\treturn 1;\n\t\t\t} else if (candidate_z == current_z) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nstatic void read_config(void) {\n\tconfreader_t * conf = confreader_load(\"/etc/msk.conf\");\n\tif (!conf) {\n\t\tfprintf(stderr, \"failed to read configuration file\\n\");\n\t\texit(1);\n\t}\n\n\tif (!strcmp(confreader_getd(conf, \"\", \"verbose\",\"\"), \"y\")) {\n\t\tverbose = 1;\n\t}\n\n\tmsk_config = conf;\n}\n\nstatic void read_manifest(int required) {\n\tconfreader_t * conf = confreader_load(VAR_PATH \"/manifest\");\n\tif (!conf) {\n\t\tif (required) {\n\t\t\tfprintf(stderr, \"no manifest; try `msk update` first\\n\");\n\t\t\texit(1);\n\t\t} else {\n\t\t\tconf = confreader_create_empty();\n\t\t}\n\t}\n\n\tmsk_manifest = conf;\n}\n\nstatic void read_installed(void) {\n\tmsk_installed = hashmap_create(10);\n\n\tFILE * installed = fopen(VAR_PATH \"/installed\", \"r\");\n\tif (!installed) return;\n\n\twhile (!feof(installed)) {\n\t\tchar tmp[128] = {0};\n\t\tif (!fgets(tmp, 128, installed)) break;\n\t\tchar * nl = strstr(tmp, \"\\n\");\n\t\tif (nl) *nl = '\\0';\n\n\t\tchar * eqeq = strstr(tmp, \"==\");\n\t\tif (!eqeq) {\n\t\t\tfprintf(stderr, \"Installation cache is malformed\\n\");\n\t\t\tfprintf(stderr, \"line was: [%s]\\n\", tmp);\n\t\t\texit(1);\n\t\t}\n\n\t\t*eqeq = '\\0';\n\t\tchar * version = eqeq+2;\n\n\t\thashmap_set(msk_installed, tmp, strdup(version));\n\t}\n}\n\nstatic void make_var(void) {\n\tstruct stat buf;\n\tif (stat(VAR_PATH, &buf)) {\n\t\tmkdir(VAR_PATH, 0755);\n\t}\n}\n\nstatic void needs_root(void) {\n\tif (geteuid() != 0) {\n\t\tfprintf(stderr, \"only root can install packages; try `sudo`\\n\");\n\t\texit(1);\n\t}\n}\n\nstatic int usage(int argc, char * argv[]) {\n#define _IT \"\\033[3m\"\n#define _END \"\\033[0m\\n\"\n\tfprintf(stderr,\n\t\t\t\"%s - package manager \" MSK_VERSION \"\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s update\\n\"\n\t\t\t\"       %s install [PACKAGE...]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" update  \" _IT \"update local manifest from remote\" _END\n\t\t\t\" install \" _IT \"install packages\" _END\n\t\t\t\"\\n\", argv[0], argv[0], argv[0]);\n\treturn 1;\n}\n\nstatic int update_stores(int argc, char * argv[]) {\n\tneeds_root();\n\tif (argc > 2) {\n\t\tfprintf(stderr,\"%s: %s: unexpected arguments in command\\n\", argv[0], argv[1]);\n\t\treturn usage(argc,argv);\n\t}\n\n\tconst char * manifest_prefix = \"\";\n#ifdef __aarch64__\n\tmanifest_prefix = \"aarch64.\";\n#endif\n\n\tneeds_lock();\n\n\tread_config();\n\tmake_var();\n\n\tconfreader_t * manifest_out = confreader_create_empty();\n\thashmap_t * remotes = hashmap_get(msk_config->sections, \"remotes\");\n\n\tif (!remotes) {\n\t\tfprintf(stderr, \"No remotes defined - bad msk.conf?\\n\");\n\t\treturn 1;\n\t}\n\n\tint one_success = 0;\n\n\tchar * order = strdup(confreader_getd(msk_config, \"\", \"remote_order\", \"\"));\n\tchar * save;\n\tchar * tok = strtok_r(order, \",\", &save);\n\tdo {\n\t\tchar * remote_name = strdup(tok);\n\t\tchar * remote_path = hashmap_get(remotes, remote_name);\n\t\tif (!remote_path) {\n\t\t\tfprintf(stderr, \"Undefined remote specified in remote_order: %s\\n\", remote_name);\n\t\t\tgoto _next;\n\t\t}\n\n\t\tconfreader_t * manifest;\n\n\t\tif (remote_path[0] == '/') {\n\t\t\tchar source[512];\n\t\t\tsprintf(source, \"%s/manifest\", remote_path);\n\t\t\tmanifest = confreader_load(source);\n\t\t\tif (!manifest) {\n\t\t\t\tfprintf(stderr, \"Skipping unavailable local manifest '%s'.\\n\", remote_name);\n\t\t\t\tgoto _next;\n\t\t\t}\n\t\t} else {\n\t\t\tchar cmd[512];\n\t\t\tsprintf(cmd, \"fetch -vo /tmp/.msk_remote_%s %s/%smanifest\", remote_name, remote_path, manifest_prefix);\n\t\t\tfprintf(stderr, \"Downloading remote manifest '%s'...\\n\", remote_name);\n\t\t\tif (system(cmd)) {\n\t\t\t\tfprintf(stderr, \"Skipping unavailable remote manifest '%s' (%s).\\n\", remote_name, remote_path);\n\t\t\t\tgoto _next;\n\t\t\t}\n\t\t\tsprintf(cmd, \"/tmp/.msk_remote_%s\", remote_name);\n\t\t\tmanifest = confreader_load(cmd);\n\t\t}\n\n\t\tlist_t * packages = hashmap_keys(manifest->sections);\n\t\tforeach(nnode, packages) {\n\t\t\tchar * package_name = (char*)nnode->value;\n\t\t\thashmap_t * package_data = (hashmap_t*)hashmap_get(manifest->sections, package_name);\n\t\t\tif (!strcmp(package_name,\"\")) continue; /* skip intro section - remote repo information */\n\n\t\t\thashmap_set(package_data, \"remote_path\", remote_path);\n\t\t\thashmap_set(package_data, \"remote_name\", remote_name);\n\n\t\t\tif (!hashmap_has(manifest_out->sections, package_name)) {\n\t\t\t\t/* Package not yet known */\n\t\t\t\thashmap_set(manifest_out->sections, package_name, package_data);\n\t\t\t} else {\n\t\t\t\t/* Package is known, keep the newer version */\n\t\t\t\tchar * old_version = confreader_get(manifest_out, package_name, \"version\");\n\t\t\t\tchar * new_version = confreader_get(manifest, package_name, \"version\");\n\n\t\t\t\tif (compare_version_strings(old_version, new_version) > 0) {\n\t\t\t\t\thashmap_set(manifest_out->sections, package_name, package_data);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tone_success = 1;\n\n_next:\n\t\ttok = strtok_r(NULL, \",\", &save);\n\t} while (tok);\n\tfree(order);\n\n\tif (!one_success) {\n\t\tfprintf(stderr, \"\\033[1;31merror\\033[0m: no remote succeeded, no packages are available\\n\");\n\t\treturn 1;\n\t}\n\n\treturn confreader_write(manifest_out, VAR_PATH \"/manifest\");\n}\n\nstatic int list_contains(list_t * list, char * key) {\n\tforeach(node, list) {\n\t\tchar * v = node->value;\n\t\tif (!strcmp(v,key)) return 1;\n\t}\n\treturn 0;\n}\n\nstatic int process_package(list_t * pkgs, char * name) {\n\tif (!strlen(name)) {\n\t\tfprintf(stderr, \"invalid package name\\n\");\n\t\treturn 1;\n\t}\n\tif (hashmap_has(msk_installed, name)) return 0;\n\tif (list_contains(pkgs, name)) return 0;\n\n\tif (!hashmap_has(msk_manifest->sections, name)) {\n\t\tfprintf(stderr, \"don't know how to install '%s'\\n\", name);\n\t\treturn 1;\n\t}\n\n\t/* Gather dependencies */\n\tchar * tmp  = confreader_get(msk_manifest, name, \"dependencies\");\n\tif (tmp && strlen(tmp)) {\n\t\tchar * deps = strdup(tmp);\n\t\tchar * save;\n\t\tchar * tok = strtok_r(deps, \" \", &save);\n\t\tdo {\n\t\t\tprocess_package(pkgs, tok);\n\t\t\ttok = strtok_r(NULL, \" \", &save);\n\t\t} while (tok);\n\t\tfree(deps);\n\t}\n\n\t/* Insert */\n\tlist_insert(pkgs, strdup(name));\n\treturn 0;\n}\n\nstatic int install_package(char * pkg) {\n\n\tchar * type = confreader_getd(msk_manifest, pkg, \"type\", \"\");\n\tchar * msk_remote = confreader_get(msk_manifest, pkg, \"remote_path\");\n\n\tif (strstr(msk_remote, \"http:\") == msk_remote || strstr(msk_remote, \"https:\") == msk_remote) {\n\t\tchar * source = confreader_get(msk_manifest, pkg, \"source\");\n\t\tif (source) {\n\t\t\tfprintf(stderr, \"Download %s...\\n\", pkg);\n\t\t\tchar cmd[1024];\n\t\t\tsprintf(cmd, \"fetch -o /tmp/msk.file -v %s/%s\", msk_remote,\n\t\t\t\t\tsource);\n\t\t\tint status;\n\t\t\tif ((status = system(cmd))) {\n\t\t\t\treturn status;\n\t\t\t}\n\t\t\thashmap_set(hashmap_get(msk_manifest->sections, pkg), \"source\", \"/tmp/msk.file\");\n\t\t}\n\t} else if (msk_remote[0] == '/') {\n\t\tchar * source = confreader_get(msk_manifest, pkg, \"source\");\n\t\tif (source) {\n\t\t\tchar * pkg_name = malloc(strlen(msk_remote) + strlen(source) + 2);\n\t\t\tsprintf(pkg_name, \"%s/%s\", msk_remote, source);\n\t\t\thashmap_set(hashmap_get(msk_manifest->sections, pkg), \"source\", pkg_name);\n\t\t}\n\t}\n\n\tfprintf(stderr, \"Install '%s'...\\n\", pkg);\n\n\tif (!strcmp(type, \"file\")) {\n\t\t/* Legacy single-file package, has a source and a destination */\n\n\t\tif (verbose) {\n\t\t\tfprintf(stderr, \"  - Copy file '%s' to '%s' and set its mask to '%s'\\n\",\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"),\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"),\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"mask\"));\n\t\t}\n\n\t\tchar cmd[1024];\n\t\tsprintf(cmd, \"cp %s %s; chmod 0%s %s\",\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"),\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"),\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"mask\"),\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"));\n\n\t\tint status;\n\t\tif ((status = system(cmd))) {\n\t\t\tfprintf(stderr, \"installation command returned %d\\n\", status);\n\t\t\treturn status;\n\t\t}\n\n\t} else if (!strcmp(type, \"tar\")) {\n\t\t/* Uncompressed archive */\n\n\t\tif (verbose) {\n\t\t\tfprintf(stderr, \"  - Extract '%s' to '%s'\\n\",\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"),\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"));\n\t\t}\n\n\t\tchar cmd[1024];\n\t\tsprintf(cmd, \"cd %s; tar -xf %s\",\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"),\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"));\n\n\t\tint status;\n\t\tif ((status = system(cmd))) {\n\t\t\tfprintf(stderr, \"installation command returned %d\\n\", status);\n\t\t\treturn status;\n\t\t}\n\n\t} else if (!strcmp(type, \"tgz\")) {\n\t\t/* Compressed archive */\n\n\t\tif (verbose) {\n\t\t\tfprintf(stderr, \"  - Extract (compressed) '%s' to '%s'\\n\",\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"),\n\t\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"));\n\t\t}\n\n\t\tchar cmd[1024];\n\t\tsprintf(cmd, \"cd %s; tar -xzf %s\",\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"destination\"),\n\t\t\t\tconfreader_get(msk_manifest, pkg, \"source\"));\n\n\t\tint status;\n\t\tif ((status = system(cmd))) {\n\t\t\tfprintf(stderr, \"installation command returned %d\\n\", status);\n\t\t\treturn status;\n\t\t}\n\n\t} else if (!strcmp(type, \"meta\")) {\n\t\t/* Do nothing */\n\n\t} else {\n\t\tfprintf(stderr, \"Unknown package type: %s\\n\", type);\n\t\treturn 1;\n\t}\n\n\tchar * source = confreader_get(msk_manifest, pkg, \"source\");\n\tif (source && !strcmp(source, \"/tmp/msk.file\")) {\n\t\tchar cmd[1024];\n\t\tsprintf(cmd, \"rm %s\", source);\n\t\tint status;\n\t\tif ((status = system(cmd))) {\n\t\t\tfprintf(stderr, \"cleanup command returned %d\\n\", status);\n\t\t\treturn status;\n\t\t}\n\t}\n\n\tchar * post = confreader_getd(msk_manifest, pkg, \"post\", \"\");\n\tif (strlen(post)) {\n\t\tint status;\n\t\tif ((status = system(post))) {\n\t\t\tfprintf(stderr, \"post-installation command returned %d\\n\", status);\n\t\t\treturn status;\n\t\t}\n\t}\n\n\t/* Mark as installed */\n\tFILE * installed = fopen(VAR_PATH \"/installed\", \"a\");\n\tfprintf(installed, \"%s==%s\\n\", pkg, confreader_get(msk_manifest, pkg, \"version\"));\n\tfclose(installed);\n\n\treturn 0;\n}\n\nstatic int install_packages(int argc, char * argv[]) {\n\tneeds_root();\n\tneeds_lock();\n\tread_config();\n\tread_manifest(1);\n\tread_installed();\n\n\t/* Go through each package and find its dependencies */\n\tlist_t * ordered = list_create();\n\n\tfor (int i = 2; i < argc; ++i) {\n\t\tif (process_package(ordered, argv[i])) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/* Additional packages must be installed, let's ask. */\n\tif (ordered->length != (unsigned int)(argc - 2) && !getenv(\"MSK_YES\")) {\n\t\tfprintf(stderr, \"The following packages will be installed:\\n\");\n\t\tfprintf(stderr, \"    \");\n\t\tint notfirst = 0;\n\t\tforeach(node, ordered) {\n\t\t\tfprintf(stderr, \"%s%s\", notfirst ? \" \" : \"\", (char*)node->value);\n\t\t\tnotfirst = 1;\n\t\t}\n\t\tfprintf(stderr, \"\\nContinue? [Y/n] \");\n\t\tfflush(stderr);\n\t\tchar resp[5];\n\t\tfgets(resp, 5, stdin);\n\t\tif (!(!strcmp(resp,\"\\n\") || !strcmp(resp,\"y\\n\") || !strcmp(resp,\"Y\\n\") || !strcmp(resp,\"yes\\n\"))) {\n\t\t\tfprintf(stderr, \"Aborting.\\n\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tforeach(node, ordered) {\n\t\tif (install_package(node->value)) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int list_packages(int argc, char * argv[]) {\n\tread_config();\n\tread_manifest(0);\n\tread_installed();\n\n\t/* Go through sections */\n\tlist_t * packages = hashmap_keys(msk_manifest->sections);\n\tforeach(node, packages) {\n\t\tchar * name = node->value;\n\t\tif (!strlen(name)) continue; /* skip empty section */\n\t\tchar * desc = confreader_get(msk_manifest, name, \"description\");\n\t\tfprintf(stderr, \" %c %20s %s\\n\", hashmap_has(msk_installed, name) ? 'I' : ' ', name, desc);\n\t\t/* TODO: Installation status */\n\t}\n\n\treturn 0;\n}\n\nstatic int count_packages(int argc, char * argv[]) {\n\tread_config();\n\tread_manifest(0);\n\tread_installed();\n\n\tint installed = 0;\n\tint available = 0;\n\n\t/* Go through sections */\n\tlist_t * packages = hashmap_keys(msk_manifest->sections);\n\tforeach(node, packages) {\n\t\tchar * name = node->value;\n\t\tif (!strlen(name)) continue; /* skip empty section */\n\t\tavailable++;\n\t\tif (hashmap_has(msk_installed, name)) {\n\t\t\tinstalled++;\n\t\t}\n\t}\n\n\tfprintf(stdout, \"%d installed; %d available\\n\", installed, available);\n\treturn 0;\n}\n\nstatic int version(void) {\n\tfprintf(stderr, \"msk \" MSK_VERSION \"\\n\");\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\n\tif (argc < 2) {\n\t\treturn usage(argc,argv);\n\t} else if (!strcmp(argv[1],\"--version\")) {\n\t\treturn version();\n\t} else if (!strcmp(argv[1],\"update\")) {\n\t\treturn update_stores(argc,argv);\n\t} else if (!strcmp(argv[1], \"install\")) {\n\t\treturn install_packages(argc,argv);\n\t} else if (!strcmp(argv[1], \"list\")) {\n\t\treturn list_packages(argc, argv);\n\t} else if (!strcmp(argv[1], \"count\")) {\n\t\treturn count_packages(argc, argv);\n\t} else {\n\t\tfprintf(stderr, \"%s: unknown command '%s'\\n\", argv[0], argv[1]);\n\t\treturn usage(argc,argv);\n\t}\n\n}\n\n"
  },
  {
    "path": "apps/mv.c",
    "content": "/**\n * @brief Move files\n *\n * Poor implementation, mostly just 'cp' and 'rm'.\n *\n * Ideally, should figure out if it can use 'rename'... and also\n * we should implement 'rename'...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <libgen.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/wait.h>\n\n#define APP_NAME \"mv\"\n#define IS_MV\n\nstatic int recursive = 1;\n\n#include \"cp.c\"\n#include \"rm.c\"\n\nint main(int argc, char * argv[]) {\n\tint opt;\n\tint interactive = 0;\n\tint force = 0;\n\n\twhile ((opt = getopt(argc, argv, \"if\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'i':\n\t\t\t\tforce = 0;\n\t\t\t\tinteractive = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tforce = 1;\n\t\t\t\tinteractive = 0;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"mv: unrecognized option '%c'\\n\", opt);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (optind + 1 >= argc) {\n\t\tfprintf(stderr, \"usage: %s [-if] source_file... destination\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tchar * destination = argv[argc-1];\n\n\tint target_is_dir = 0;\n\tstruct stat statbuf;\n\tint exists = 0;\n\tif ((exists = !stat(destination, &statbuf))) {\n\t\tif (S_ISDIR(statbuf.st_mode)) {\n\t\t\ttarget_is_dir = 1;\n\t\t}\n\t}\n\n\tint destination_has_trailing_slash = strlen(destination) && destination[strlen(destination)-1] == '/';\n\tint multiple_args = optind + 2 < argc;\n\n\tif ((multiple_args && !target_is_dir) ||\n\t    (exists && !target_is_dir && destination_has_trailing_slash)) {\n\t\tfprintf(stderr, \"%s: %s: Not a directory\\n\", argv[0], destination);\n\t\treturn 1;\n\t}\n\n\tint ret = 0;\n\n\tfor (int i = optind; i < argc - 1; ++i) {\n\n\t\tchar * target = destination;\n\n\t\tif (target_is_dir) {\n\t\t\tchar * tmp = strdup(argv[i]);\n\t\t\tchar * target_basename = basename(tmp);\n\t\t\tsize_t size = strlen(destination) + strlen(target_basename) + 2;\n\t\t\ttarget = malloc(size);\n\t\t\tsnprintf(target, size, \"%s%s%s\", destination, destination_has_trailing_slash ? \"\" : \"/\", target_basename);\n\t\t\tfree(tmp);\n\t\t}\n\n\t\tif (!force && !stat(target, &statbuf)) {\n\t\t\tif (interactive) { /* || (isatty(STDIN_FILENO) && some_check_for_writability...) */\n\t\t\t\tfprintf(stderr, \"%s: overwrite '%s'? \", argv[0], target);\n\t\t\t\tfflush(stderr); /* just in case */\n\t\t\t\tchar tmp[10] = {0};\n\t\t\t\tfgets(tmp, 10, stdin);\n\t\t\t\tif (tmp[0] != 'y' && tmp[0] != 'Y') {\n\t\t\t\t\tret |= 1;\n\t\t\t\t\tgoto _continue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (rename(argv[i], target) < 0) {\n\t\t\tif (errno != EXDEV && errno != ENOTSUP) {\n\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\t\tret |= 1;\n\t\t\t} else if (copy_thing(argv[i], target) || rm_thing(argv[i])) {\n\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\t\tret |= 1;\n\t\t\t}\n\t\t}\n\n\t_continue:\n\t\tif (target != destination) free(target);\n\t}\n\n\treturn ret;\n}\n"
  },
  {
    "path": "apps/netty.c",
    "content": "/**\n * @brief Provides a PTY over a reverse network socket.\n *\n * Pipes data into and out of a PTY from a TCP socket connected to a remote\n * server.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <pty.h>\n#include <pthread.h>\n#include <sys/wait.h>\n#include <sys/fswait.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n\nint fd_master, fd_slave, fd_serial;\nvolatile int _stop = 0;\n\nvoid * handle_in(void * unused) {\n\twhile (!_stop) {\n\t\tint index = fswait2(1,&fd_serial,200);\n\t\tchar buf[1];\n\t\tint r;\n\t\tswitch (index) {\n\t\t\tcase 0: /* fd_serial */\n\t\t\t\tr = read(fd_serial, buf, 1);\n\t\t\t\tif (r > 0) {\n\t\t\t\t\twrite(fd_master, buf, r);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nint main(int argc, char * argv[]) {\n\tchar * user = NULL;\n\n\tif (getuid() != 0) {\n\t\tfprintf(stderr, \"%s: only root can do that\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"a:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'a':\n\t\t\t\tuser = optarg;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind == argc) {\n\t\tfprintf(stderr, \"usage: %s remote:port\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tchar * remotehost = argv[optind];\n\tchar * colon = strstr(remotehost, \":\");\n\tif (!colon) {\n\t\tfprintf(stderr, \"usage: %s remote:port\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t*colon = '\\0'; colon++;\n\tint remoteport = atoi(colon);\n\n\topenpty(&fd_master, &fd_slave, NULL, NULL, NULL);\n\n\tint sock = socket(AF_INET, SOCK_STREAM, 0);\n\tif (sock < 0) {\n\t\tperror(\"socket\");\n\t\treturn 1;\n\t}\n\n\tstruct hostent * remote = gethostbyname(remotehost);\n\n\tif (!remote) {\n\t\tperror(\"gethostbyname\");\n\t\treturn 1;\n\t}\n\n\tstruct sockaddr_in addr;\n\taddr.sin_family = AF_INET;\n\tmemcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length);\n\taddr.sin_port = htons(remoteport);\n\n\tif (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {\n\t\tperror(\"connect\");\n\t\treturn 1;\n\t}\n\n\tfd_serial = sock; //open(file, O_RDWR);\n\tpthread_t input_buffer_thread;\n\tpthread_create(&input_buffer_thread, NULL, handle_in, NULL);\n\n\tpid_t child = fork();\n\n\tif (!child) {\n\t\tsetsid();\n\t\tdup2(fd_slave, 0);\n\t\tdup2(fd_slave, 1);\n\t\tdup2(fd_slave, 2);\n\t\tioctl(STDIN_FILENO, TIOCSCTTY, &(int){1});\n\t\ttcsetpgrp(STDIN_FILENO, getpid());\n\n\t\tsystem(\"ttysize -q\");\n\n\t\tchar * tokens[] = {\"/bin/login-loop\",NULL,NULL,NULL};\n\n\t\tif (user) {\n\t\t\ttokens[1] = \"-f\";\n\t\t\ttokens[2] = user;\n\t\t}\n\n\t\texecvp(tokens[0], tokens);\n\t\texit(1);\n\t} else {\n\n\t\twhile (1) {\n\t\t\tint index = fswait2(1,&fd_master,200);\n\t\t\tchar buf[1024];\n\t\t\tint r;\n\t\t\tswitch (index) {\n\t\t\t\tcase 0: /* fd_master */\n\t\t\t\t\tr = read(fd_master, buf, 1024);\n\t\t\t\t\twrite(fd_serial, buf, r);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault: /* timeout */\n\t\t\t\t\t{\n\t\t\t\t\t\tint result = waitpid(child, NULL, WNOHANG);\n\t\t\t\t\t\tif (result > 0) {\n\t\t\t\t\t\t\t/* Child login shell has returned (session ended) */\n\t\t\t\t\t\t\t_stop = 1;\n\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t}\n\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/nproc.c",
    "content": "/**\n * @brief Print the number of available processors.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <sys/sysfunc.h>\n\nint main(int argc, char * argv[]) {\n\tprintf(\"%d\\n\", sysfunc(TOARU_SYS_FUNC_NPROC, NULL));\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/nslookup.c",
    "content": "/**\n * @brief Perform DNS lookups.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2018 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <getopt.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) return 1;\n\n\tstruct hostent * host = gethostbyname(argv[1]);\n\n\tif (!host) {\n\t\tfprintf(stderr, \"%s: not found\\n\", argv[1]);\n\t\treturn 1;\n\t}\n\n\tchar * addr = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);\n\n\tfprintf(stderr, \"%s: %s\\n\", host->h_name, addr);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/nyancat/animation.h",
    "content": "/*\n * Pop Tart Cat animation frames\n */\n#ifndef ANIMATION_H\n#define ANIMATION_H\n\nconst char * frame0[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\".,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&'''++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++**''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"++########++++++++''**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"##========########====''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\"======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;.=======;;;;'''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'*','**',,,,,,,,,,,,,,,,,,,,,\",\n\";;,,,,,.,,;;;.;;;;,,,'''',,'',,,,,,,'',,'',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame1[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&+++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"++########++++++++'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"##========########==='''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\"======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;========;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,\",\n\";;,,,.,,,,;;;;;;;;,,,,''',,,'',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,..,,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame2[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"==#######========#'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;======'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,\",\n\";.;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\".,.;;;;;;,,,,,,,,;;;;;;'**',**',,,,,,**','**',,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame3[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,\",\n\">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"==#######========##****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;=='==='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\",,;;;;;;;,,,,,,,,;;;;;'**','*',,,,,,'*','**',,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame4[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&+++++'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"++########+++++++'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,\",\n\"##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"##========########==='''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;========;;;;''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,\",\n\";;,,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,..,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame5[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&+++++'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"++########++++++++'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,\",\n\"###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"##========########==='''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\"======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;========;;;;''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,.\",\n\";;;;;;;;;;;;;;;;;;;;;'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,.\",\n\";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,\",\n\";;,,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,..,.\",\n\",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame6[] = {\n\".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&'''&&'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'*''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"##+++++++########++'**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"==#######========#####''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\"======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;====='''@@@@@@@@@'*********',,,,,,,,,,,.,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,.,,,.,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,\",\n\",,;;;;;;;,,,,,,,,;;;;'''',,'',,,,,,,'',,'',,,,,,,,,,,.,,,,,.,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame7[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&&&&&&'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"##+++++++########+'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"==#######========####'''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\"======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;======''@@@@@@@@@@'*********',,,.,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,\",\n\",,;;;;;;;,,,,,,,,;;;;;''',,,'',,,,,,''',,''',,.,,,,.,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame8[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,..,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"++########++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"##========########'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;========;;;;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\";;,,,,,,,,;;;;;;;;,,,,,'**',**',,,,,,**'.'**',,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,\"};\n\nconst char * frame9[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,\",\n\"&&++++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,\",\n\"++########++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,\",\n\"#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,\",\n\"##========########=****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,\",\n\"=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,\",\n\"==;;;;;;;;========;';;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,\",\n\";;,,,,,,,,;;;;;;;;,,,,'**','*',,..,.**','**',,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,.,''',,''',,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,\"};\n\nconst char * frame10[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"##+++++++########'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,\",\n\"##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"==#######========####'''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;=====''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,\",\n\",,;;;;;;;,,,,,,,,;;;'**'.'**..,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,''',''',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\".,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char * frame11[] = {\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,\",\n\">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,\",\n\"&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,\",\n\"&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,\",\n\"++&&&&&&&++++++++&&&&&&'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,\",\n\"+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,\",\n\"++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,\",\n\"##+++++++########+'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,\",\n\"###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,\",\n\"###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,\",\n\"==#######========####'''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,\",\n\"======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,\",\n\";;=======;;;;;;;;=.===''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;.'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,,\",\n\";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,\",\n\",,;;;;;;;,,,,,,,.;;;'**','**,,,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\",\n\",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\"};\n\nconst char ** frames[] = {\n\tframe0,\n\tframe1,\n\tframe2,\n\tframe3,\n\tframe4,\n\tframe5,\n\tframe6,\n\tframe7,\n\tframe8,\n\tframe9,\n\tframe10,\n\tframe11,\n\tNULL\n};\n\n#define FRAME_WIDTH  64\n#define FRAME_HEIGHT 64\n\n#endif\n"
  },
  {
    "path": "apps/nyancat/telnet.h",
    "content": "#ifndef TELNET_H\n#define TELNET_H\n\n/* Telnet Defines */\n#define IAC   255\n#define DONT  254\n#define DO    253\n#define WONT  252\n#define WILL  251\n\n#define SE  240  // Subnegotiation End\n#define NOP 241  // No Operation\n#define DM  242  // Data Mark\n#define BRK 243  // Break\n#define IP  244  // Interrupt process\n#define AO  245  // Abort output\n#define AYT 246  // Are You There\n#define EC  247  // Erase Character\n#define EL  248  // Erase Line\n#define GA  249  // Go Ahead\n#define SB  250  // Subnegotiation Begin\n\n#define BINARY 0 // 8-bit data path\n#define ECHO 1 // echo\n#define RCP 2 // prepare to reconnect\n#define SGA 3 // suppress go ahead\n#define NAMS 4 // approximate message size\n#define STATUS 5 // give status\n#define TM 6 // timing mark\n#define RCTE 7 // remote controlled transmission and echo\n#define NAOL 8 // negotiate about output line width\n#define NAOP 9 // negotiate about output page size\n#define NAOCRD 10 // negotiate about CR disposition\n#define NAOHTS 11 // negotiate about horizontal tabstops\n#define NAOHTD 12 // negotiate about horizontal tab disposition\n#define NAOFFD 13 // negotiate about formfeed disposition\n#define NAOVTS 14 // negotiate about vertical tab stops\n#define NAOVTD 15 // negotiate about vertical tab disposition\n#define NAOLFD 16 // negotiate about output LF disposition\n#define XASCII 17 // extended ascii character set\n#define LOGOUT 18 // force logout\n#define BM 19 // byte macro\n#define DET 20 // data entry terminal\n#define SUPDUP 21 // supdup protocol\n#define SUPDUPOUTPUT 22 // supdup output\n#define SNDLOC 23 // send location\n#define TTYPE 24 // terminal type\n#define EOR 25 // end or record\n#define TUID 26 // TACACS user identification\n#define OUTMRK 27 // output marking\n#define TTYLOC 28 // terminal location number\n#define VT3270REGIME 29 // 3270 regime\n#define X3PAD 30 // X.3 PAD\n#define NAWS 31 // window size\n#define TSPEED 32 // terminal speed\n#define LFLOW 33 // remote flow control\n#define LINEMODE 34 // Linemode option\n#define XDISPLOC 35 // X Display Location\n#define OLD_ENVIRON 36 // Old - Environment variables\n#define AUTHENTICATION 37 // Authenticate\n#define ENCRYPT 38 // Encryption option\n#define NEW_ENVIRON 39 // New - Environment variables\n#define TN3270E 40 // TN3270E\n#define XAUTH 41 // XAUTH\n#define CHARSET 42 // CHARSET\n#define RSP 43 // Telnet Remote Serial Port\n#define COM_PORT_OPTION 44 // Com Port Control Option\n#define SUPPRESS_LOCAL_ECHO 45 // Telnet Suppress Local Echo\n#define TLS 46 // Telnet Start TLS\n#define KERMIT 47 // KERMIT\n#define SEND_URL 48 // SEND-URL\n#define FORWARD_X 49 // FORWARD_X\n#define PRAGMA_LOGON 138 // TELOPT PRAGMA LOGON\n#define SSPI_LOGON 139 // TELOPT SSPI LOGON\n#define PRAGMA_HEARTBEAT 140 // TELOPT PRAGMA HEARTBEAT\n#define EXOPL 255 // Extended-Options-List\n#define NOOPT 0\n\n#define IS 0\n#define SEND 1\n\n#endif\n"
  },
  {
    "path": "apps/nyancat.c",
    "content": "/*\n * Copyright (c) 2011-2018 K. Lange.  All rights reserved.\n *\n * Developed by:            K. Lange\n *                          http://github.com/klange/nyancat\n *                          http://nyancat.dakko.us\n *\n * 40-column support by:    Peter Hazenberg\n *                          http://github.com/Peetz0r/nyancat\n *                          http://peter.haas-en-berg.nl\n *\n * Build tools unified by:  Aaron Peschel\n *                          https://github.com/apeschel\n *\n * For a complete listing of contributors, please see the git commit history.\n *\n * This is a simple telnet server / standalone application which renders the\n * classic Nyan Cat (or \"poptart cat\") to your terminal.\n *\n * It makes use of various ANSI escape sequences to render color, or in the case\n * of a VT220, simply dumps text to the screen.\n *\n * For more information, please see:\n *\n *     http://nyancat.dakko.us\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal with the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *   1. Redistributions of source code must retain the above copyright notice,\n *      this list of conditions and the following disclaimers.\n *   2. Redistributions in binary form must reproduce the above copyright\n *      notice, this list of conditions and the following disclaimers in the\n *      documentation and/or other materials provided with the distribution.\n *   3. Neither the names of the Association for Computing Machinery, K.\n *      Lange, nor the names of its contributors may be used to endorse\n *      or promote products derived from this Software without specific prior\n *      written permission.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\n * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * WITH THE SOFTWARE.\n */\n\n#define _XOPEN_SOURCE 700\n#define _DARWIN_C_SOURCE 1\n#define _BSD_SOURCE\n#define _DEFAULT_SOURCE\n#define __BSD_VISIBLE 1\n#include <ctype.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <signal.h>\n#include <time.h>\n#include <setjmp.h>\n#include <getopt.h>\n\n#include <sys/ioctl.h>\n\n#ifndef TIOCGWINSZ\n#include <termios.h>\n#endif\n\n#ifdef ECHO\n#undef ECHO\n#endif\n\n/*\n * telnet.h contains some #defines for the various\n * commands, escape characters, and modes for telnet.\n * (it surprises some people that telnet is, really,\n *  a protocol, and not just raw text transmission)\n */\n#include \"nyancat/telnet.h\"\n\n/*\n * The animation frames are stored separately in\n * this header so they don't clutter the core source\n */\n#include \"nyancat/animation.h\"\n\n/*\n * Color palette to use for final output\n * Specifically, this should be either control sequences\n * or raw characters (ie, for vt220 mode)\n */\nconst char * colors[256] = {NULL};\n\n/*\n * For most modes, we output spaces, but for some\n * we will use block characters (or even nothing)\n */\nconst char * output = \"  \";\n\n/*\n * Are we currently in telnet mode?\n */\nint telnet = 0;\n\n/*\n * Whether or not to show the counter\n */\nint show_counter = 1;\n\n/*\n * Number of frames to show before quitting\n * or 0 to repeat forever (default)\n */\nunsigned int frame_count = 0;\n\n/*\n * Clear the screen between frames (as opposed to resetting\n * the cursor position)\n */\nint clear_screen = 1;\n\n/*\n * Force-set the terminal title.\n */\nint set_title = 1;\n\n/*\n * Environment to use for setjmp/longjmp\n * when breaking out of options handler\n */\njmp_buf environment;\n\n\n/*\n * I refuse to include libm to keep this low\n * on external dependencies.\n *\n * Count the number of digits in a number for\n * use with string output.\n */\nint digits(int val) {\n\tint d = 1, c;\n\tif (val >= 0) for (c = 10; c <= val; c *= 10) d++;\n\telse for (c = -10 ; c >= val; c *= 10) d++;\n\treturn (c < 0) ? ++d : d;\n}\n\n/*\n * These values crop the animation, as we have a full 64x64 stored,\n * but we only want to display 40x24 (double width).\n */\nint min_row = -1;\nint max_row = -1;\nint min_col = -1;\nint max_col = -1;\n\n/*\n * Actual width/height of terminal.\n */\nint terminal_width = 80;\nint terminal_height = 24;\n\n/*\n * Flags to keep track of whether width/height were automatically set.\n */\nchar using_automatic_width = 0;\nchar using_automatic_height = 0;\n\n/*\n * Print escape sequences to return cursor to visible mode\n * and exit the application.\n */\nvoid finish() {\n\tif (clear_screen) {\n\t\tprintf(\"\\033[?25h\\033[0m\\033[H\\033[2J\");\n\t} else {\n\t\tprintf(\"\\033[0m\\n\");\n\t}\n\texit(0);\n}\n\n/*\n * In the standalone mode, we want to handle an interrupt signal\n * (^C) so that we can restore the cursor and clear the terminal.\n */\nvoid SIGINT_handler(int sig){\n\t(void)sig;\n\tfinish();\n}\n\n/*\n * Handle the alarm which breaks us off of options\n * handling if we didn't receive a terminal\n */\nvoid SIGALRM_handler(int sig) {\n\t(void)sig;\n\talarm(0);\n\tlongjmp(environment, 1);\n\t/* Unreachable */\n}\n\n/*\n * Handle the loss of stdout, as would be the case when\n * in telnet mode and the client disconnects\n */\nvoid SIGPIPE_handler(int sig) {\n\t(void)sig;\n\tfinish();\n}\n\nvoid SIGWINCH_handler(int sig) {\n\t(void)sig;\n\tstruct winsize w;\n\tioctl(0, TIOCGWINSZ, &w);\n\tterminal_width = w.ws_col;\n\tterminal_height = w.ws_row;\n\n\tif (using_automatic_width) {\n\t\tmin_col = (FRAME_WIDTH - terminal_width/2) / 2;\n\t\tmax_col = (FRAME_WIDTH + terminal_width/2) / 2;\n\t}\n\n\tif (using_automatic_height) {\n\t\tmin_row = (FRAME_HEIGHT - (terminal_height-1)) / 2;\n\t\tmax_row = (FRAME_HEIGHT + (terminal_height-1)) / 2;\n\t}\n\n\tsignal(SIGWINCH, SIGWINCH_handler);\n}\n\n/*\n * Telnet requires us to send a specific sequence\n * for a line break (\\r\\000\\n), so let's make it happy.\n */\nvoid newline(int n) {\n\tint i = 0;\n\tfor (i = 0; i < n; ++i) {\n\t\t/* We will send `n` linefeeds to the client */\n\t\tif (telnet) {\n\t\t\t/* Send the telnet newline sequence */\n\t\t\tputc('\\r', stdout);\n\t\t\tputc(0, stdout);\n\t\t\tputc('\\n', stdout);\n\t\t} else {\n\t\t\t/* Send a regular line feed */\n\t\t\tputc('\\n', stdout);\n\t\t}\n\t}\n}\n\n/*\n * These are the options we want to use as\n * a telnet server. These are set in set_options()\n */\nunsigned char telnet_options[256] = { 0 };\nunsigned char telnet_willack[256] = { 0 };\n\n/*\n * These are the values we have set or\n * agreed to during our handshake.\n * These are set in send_command(...)\n */\nunsigned char telnet_do_set[256]  = { 0 };\nunsigned char telnet_will_set[256]= { 0 };\n\n/*\n * Set the default options for the telnet server.\n */\nvoid set_options() {\n\t/* We will not echo input */\n\ttelnet_options[ECHO] = WONT;\n\t/* We will set graphics modes */\n\ttelnet_options[SGA]  = WILL;\n\t/* We will not set new environments */\n\ttelnet_options[NEW_ENVIRON] = WONT;\n\n\t/* The client should echo its own input */\n\ttelnet_willack[ECHO]  = DO;\n\t/* The client can set a graphics mode */\n\ttelnet_willack[SGA]   = DO;\n\t/* The client should not change, but it should tell us its window size */\n\ttelnet_willack[NAWS]  = DO;\n\t/* The client should tell us its terminal type (very important) */\n\ttelnet_willack[TTYPE] = DO;\n\t/* No linemode */\n\ttelnet_willack[LINEMODE] = DONT;\n\t/* And the client can set a new environment */\n\ttelnet_willack[NEW_ENVIRON] = DO;\n}\n\n/*\n * Send a command (cmd) to the telnet client\n * Also does special handling for DO/DONT/WILL/WONT\n */\nvoid send_command(int cmd, int opt) {\n\t/* Send a command to the telnet client */\n\tif (cmd == DO || cmd == DONT) {\n\t\t/* DO commands say what the client should do. */\n\t\tif (((cmd == DO) && (telnet_do_set[opt] != DO)) ||\n\t\t\t((cmd == DONT) && (telnet_do_set[opt] != DONT))) {\n\t\t\t/* And we only send them if there is a disagreement */\n\t\t\ttelnet_do_set[opt] = cmd;\n\t\t\tprintf(\"%c%c%c\", IAC, cmd, opt);\n\t\t}\n\t} else if (cmd == WILL || cmd == WONT) {\n\t\t/* Similarly, WILL commands say what the server will do. */\n\t\tif (((cmd == WILL) && (telnet_will_set[opt] != WILL)) ||\n\t\t\t((cmd == WONT) && (telnet_will_set[opt] != WONT))) {\n\t\t\t/* And we only send them during disagreements */\n\t\t\ttelnet_will_set[opt] = cmd;\n\t\t\tprintf(\"%c%c%c\", IAC, cmd, opt);\n\t\t}\n\t} else {\n\t\t/* Other commands are sent raw */\n\t\tprintf(\"%c%c\", IAC, cmd);\n\t}\n}\n\n/*\n * Print the usage / help text describing options\n */\nvoid usage(char * argv[]) {\n\tprintf(\n\t\t\t\"Terminal Nyancat\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-hitn] [-f \\033[3mframes\\033[0m]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -i --intro      \\033[3mShow the introduction / about information at startup.\\033[0m\\n\"\n\t\t\t\" -t --telnet     \\033[3mTelnet mode.\\033[0m\\n\"\n\t\t\t\" -n --no-counter \\033[3mDo not display the timer\\033[0m\\n\"\n\t\t\t\" -s --no-title   \\033[3mDo not set the titlebar text\\033[0m\\n\"\n\t\t\t\" -e --no-clear   \\033[3mDo not clear the display between frames\\033[0m\\n\"\n\t\t\t\" -d --delay      \\033[3mDelay image rendering by anywhere between 10ms and 1000ms\\n\"\n\t\t\t\" -f --frames     \\033[3mDisplay the requested number of frames, then quit\\033[0m\\n\"\n\t\t\t\" -r --min-rows   \\033[3mCrop the animation from the top\\033[0m\\n\"\n\t\t\t\" -R --max-rows   \\033[3mCrop the animation from the bottom\\033[0m\\n\"\n\t\t\t\" -c --min-cols   \\033[3mCrop the animation from the left\\033[0m\\n\"\n\t\t\t\" -C --max-cols   \\033[3mCrop the animation from the right\\033[0m\\n\"\n\t\t\t\" -W --width      \\033[3mCrop the animation to the given width\\033[0m\\n\"\n\t\t\t\" -H --height     \\033[3mCrop the animation to the given height\\033[0m\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\",\n\t\t\targv[0]);\n}\n\nint main(int argc, char ** argv) {\n\n\tchar *term = NULL;\n\tunsigned int k;\n\n\tint ttype;\n#if 0\n\tuint32_t option = 0, done = 0, sb_mode = 0;\n\t/* Various pieces for the telnet communication */\n\tunsigned char  sb[1024] = {0};\n\tunsigned short sb_len   = 0;\n#endif\n\n\t/* Whether or not to show the MOTD intro */\n\tchar show_intro = 0;\n\tchar skip_intro = 0;\n\n\t/* Long option names */\n\tstatic struct option long_opts[] = {\n\t\t{\"help\",       no_argument,       0, 'h'},\n\t\t{\"telnet\",     no_argument,       0, 't'},\n\t\t{\"intro\",      no_argument,       0, 'i'},\n\t\t{\"skip-intro\", no_argument,       0, 'I'},\n\t\t{\"no-counter\", no_argument,       0, 'n'},\n\t\t{\"no-title\",   no_argument,       0, 's'},\n\t\t{\"no-clear\",   no_argument,       0, 'e'},\n\t\t{\"delay\",      required_argument, 0, 'd'},\n\t\t{\"frames\",     required_argument, 0, 'f'},\n\t\t{\"min-rows\",   required_argument, 0, 'r'},\n\t\t{\"max-rows\",   required_argument, 0, 'R'},\n\t\t{\"min-cols\",   required_argument, 0, 'c'},\n\t\t{\"max-cols\",   required_argument, 0, 'C'},\n\t\t{\"width\",      required_argument, 0, 'W'},\n\t\t{\"height\",     required_argument, 0, 'H'},\n\t\t{0,0,0,0}\n\t};\n\n\t/* Time delay in milliseconds */\n\tint delay_ms = 90; // Default to original value\n\n\t/* Process arguments */\n\tint index, c;\n\twhile ((c = getopt_long(argc, argv, \"eshiItnd:f:r:R:c:C:W:H:\", long_opts, &index)) != -1) {\n\t\tif (!c) {\n\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\tc = long_opts[index].val;\n\t\t\t}\n\t\t}\n\t\tswitch (c) {\n\t\t\tcase 'e':\n\t\t\t\tclear_screen = 0;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tset_title = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'i': /* Show introduction */\n\t\t\t\tshow_intro = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'I':\n\t\t\t\tskip_intro = 1;\n\t\t\t\tbreak;\n\t\t\tcase 't': /* Expect telnet bits */\n\t\t\t\ttelnet = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'h': /* Show help and exit */\n\t\t\t\tusage(argv);\n\t\t\t\texit(0);\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tshow_counter = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tif (10 <= atoi(optarg) && atoi(optarg) <= 1000)\n\t\t\t\t\tdelay_ms = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tframe_count = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tmin_row = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'R':\n\t\t\t\tmax_row = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tmin_col = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tmax_col = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'W':\n\t\t\t\tmin_col = (FRAME_WIDTH - atoi(optarg)) / 2;\n\t\t\t\tmax_col = (FRAME_WIDTH + atoi(optarg)) / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'H':\n\t\t\t\tmin_row = (FRAME_HEIGHT - atoi(optarg)) / 2;\n\t\t\t\tmax_row = (FRAME_HEIGHT + atoi(optarg)) / 2;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t(void)skip_intro;\n#if 0\n\tif (telnet) {\n\t\t/* Telnet mode */\n\n\t\t/* show_intro is implied unless skip_intro was set */\n\t\tshow_intro = (skip_intro == 0) ? 1 : 0;\n\n\t\t/* Set the default options */\n\t\tset_options();\n\n\t\t/* Let the client know what we're using */\n\t\tfor (option = 0; option < 256; option++) {\n\t\t\tif (telnet_options[option]) {\n\t\t\t\tsend_command(telnet_options[option], option);\n\t\t\t\tfflush(stdout);\n\t\t\t}\n\t\t}\n\t\tfor (option = 0; option < 256; option++) {\n\t\t\tif (telnet_willack[option]) {\n\t\t\t\tsend_command(telnet_willack[option], option);\n\t\t\t\tfflush(stdout);\n\t\t\t}\n\t\t}\n\n\t\t/* Set the alarm handler to execute the longjmp */\n\t\tsignal(SIGALRM, SIGALRM_handler);\n\n\t\t/* Negotiate options */\n\t\tif (!setjmp(environment)) {\n\t\t\t/* We will stop handling options after one second */\n\t\t\talarm(1);\n\n\t\t\t/* Let's do this */\n\t\t\twhile (!feof(stdin) && done < 2) {\n\t\t\t\t/* Get either IAC (start command) or a regular character (break, unless in SB mode) */\n\t\t\t\tunsigned char i = getchar();\n\t\t\t\tunsigned char opt = 0;\n\t\t\t\tif (i == IAC) {\n\t\t\t\t\t/* If IAC, get the command */\n\t\t\t\t\ti = getchar();\n\t\t\t\t\tswitch (i) {\n\t\t\t\t\t\tcase SE:\n\t\t\t\t\t\t\t/* End of extended option mode */\n\t\t\t\t\t\t\tsb_mode = 0;\n\t\t\t\t\t\t\tif (sb[0] == TTYPE) {\n\t\t\t\t\t\t\t\t/* This was a response to the TTYPE command, meaning\n\t\t\t\t\t\t\t\t * that this should be a terminal type */\n\t\t\t\t\t\t\t\talarm(2);\n\t\t\t\t\t\t\t\tterm = strndup((char *)&sb[2], sizeof(sb)-2);\n\t\t\t\t\t\t\t\tdone++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse if (sb[0] == NAWS) {\n\t\t\t\t\t\t\t\t/* This was a response to the NAWS command, meaning\n\t\t\t\t\t\t\t\t * that this should be a window size */\n\t\t\t\t\t\t\t\talarm(2);\n\t\t\t\t\t\t\t\tterminal_width = (sb[1] << 8) | sb[2];\n\t\t\t\t\t\t\t\tterminal_height = (sb[3] << 8) | sb[4];\n\t\t\t\t\t\t\t\tdone++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase NOP:\n\t\t\t\t\t\t\t/* No Op */\n\t\t\t\t\t\t\tsend_command(NOP, 0);\n\t\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase WILL:\n\t\t\t\t\t\tcase WONT:\n\t\t\t\t\t\t\t/* Will / Won't Negotiation */\n\t\t\t\t\t\t\topt = getchar();\n\t\t\t\t\t\t\tif (!telnet_willack[opt]) {\n\t\t\t\t\t\t\t\t/* We default to WONT */\n\t\t\t\t\t\t\t\ttelnet_willack[opt] = WONT;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsend_command(telnet_willack[opt], opt);\n\t\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t\t\tif ((i == WILL) && (opt == TTYPE)) {\n\t\t\t\t\t\t\t\t/* WILL TTYPE? Great, let's do that now! */\n\t\t\t\t\t\t\t\tprintf(\"%c%c%c%c%c%c\", IAC, SB, TTYPE, SEND, IAC, SE);\n\t\t\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase DO:\n\t\t\t\t\t\tcase DONT:\n\t\t\t\t\t\t\t/* Do / Don't Negotiation */\n\t\t\t\t\t\t\topt = getchar();\n\t\t\t\t\t\t\tif (!telnet_options[opt]) {\n\t\t\t\t\t\t\t\t/* We default to DONT */\n\t\t\t\t\t\t\t\ttelnet_options[opt] = DONT;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsend_command(telnet_options[opt], opt);\n\t\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase SB:\n\t\t\t\t\t\t\t/* Begin Extended Option Mode */\n\t\t\t\t\t\t\tsb_mode = 1;\n\t\t\t\t\t\t\tsb_len  = 0;\n\t\t\t\t\t\t\tmemset(sb, 0, sizeof(sb));\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase IAC: \n\t\t\t\t\t\t\t/* IAC IAC? That's probably not right. */\n\t\t\t\t\t\t\tdone = 2;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} else if (sb_mode) {\n\t\t\t\t\t/* Extended Option Mode -> Accept character */\n\t\t\t\t\tif (sb_len < sizeof(sb) - 1) {\n\t\t\t\t\t\t/* Append this character to the SB string,\n\t\t\t\t\t\t * but only if it doesn't put us over\n\t\t\t\t\t\t * our limit; honestly, we shouldn't hit\n\t\t\t\t\t\t * the limit, as we're only collecting characters\n\t\t\t\t\t\t * for a terminal type or window size, but better safe than\n\t\t\t\t\t\t * sorry (and vulnerable).\n\t\t\t\t\t\t */\n\t\t\t\t\t\tsb[sb_len] = i;\n\t\t\t\t\t\tsb_len++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\talarm(0);\n\t} else\n#endif\n\t{\n\t\t/* We are running standalone, retrieve the\n\t\t * terminal type from the environment. */\n\t\tterm = getenv(\"TERM\");\n\n\t\t/* Also get the number of columns */\n\t\tstruct winsize w;\n\t\tioctl(0, TIOCGWINSZ, &w);\n\t\tterminal_width = w.ws_col;\n\t\tterminal_height = w.ws_row;\n\t}\n\n\t/* Default ttype */\n\tttype = 2;\n\n\tif (term) {\n\t\t/* Convert the entire terminal string to lower case */\n\t\tfor (k = 0; k < strlen(term); ++k) {\n\t\t\tterm[k] = tolower(term[k]);\n\t\t}\n\n\t\t/* Do our terminal detection */\n\t\tif (strstr(term, \"xterm\")) {\n\t\t\tttype = 1; /* 256-color, spaces */\n\t\t} else if (strstr(term, \"toaru\")) {\n\t\t\tttype = 1; /* emulates xterm */\n\t\t} else if (strstr(term, \"linux\")) {\n\t\t\tttype = 3; /* Spaces and blink attribute */\n\t\t} else if (strstr(term, \"vtnt\")) {\n\t\t\tttype = 5; /* Extended ASCII fallback == Windows */\n\t\t} else if (strstr(term, \"cygwin\")) {\n\t\t\tttype = 5; /* Extended ASCII fallback == Windows */\n\t\t} else if (strstr(term, \"vt220\")) {\n\t\t\tttype = 6; /* No color support */\n\t\t} else if (strstr(term, \"fallback\")) {\n\t\t\tttype = 4; /* Unicode fallback */\n\t\t} else if (strstr(term, \"rxvt-256color\")) {\n\t\t\tttype = 1; /* xterm 256-color compatible */\n\t\t} else if (strstr(term, \"rxvt\")) {\n\t\t\tttype = 3; /* Accepts LINUX mode */\n\t\t} else if (strstr(term, \"vt100\") && terminal_width == 40) {\n\t\t\tttype = 7; /* No color support, only 40 columns */\n\t\t} else if (!strncmp(term, \"st\", 2)) {\n\t\t\tttype = 1; /* suckless simple terminal is xterm-256color-compatible */\n\t\t} else if (!strncmp(term, \"truecolor\", 9)) {\n\t\t\tttype = 8;\n\t\t}\n\t}\n\n\tint always_escape = 0; /* Used for text mode */\n\n\t/* Accept ^C -> restore cursor */\n\tsignal(SIGINT, SIGINT_handler);\n\n\t/* Handle loss of stdout */\n\tsignal(SIGPIPE, SIGPIPE_handler);\n\n\t/* Handle window changes */\n\tif (!telnet) {\n\t\tsignal(SIGWINCH, SIGWINCH_handler);\n\t}\n\n\tswitch (ttype) {\n\t\tcase 1:\n\t\t\tcolors[',']  = \"\\033[48;5;17m\";  /* Blue background */\n\t\t\tcolors['.']  = \"\\033[48;5;231m\"; /* White stars */\n\t\t\tcolors['\\''] = \"\\033[48;5;16m\";  /* Black border */\n\t\t\tcolors['@']  = \"\\033[48;5;230m\"; /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[48;5;175m\"; /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[48;5;162m\"; /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[48;5;196m\"; /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[48;5;214m\"; /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[48;5;226m\"; /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[48;5;118m\"; /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[48;5;33m\";  /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[48;5;19m\";  /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[48;5;240m\"; /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[48;5;175m\"; /* Pink cheeks */\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tcolors[',']  = \"\\033[104m\";      /* Blue background */\n\t\t\tcolors['.']  = \"\\033[107m\";      /* White stars */\n\t\t\tcolors['\\''] = \"\\033[40m\";       /* Black border */\n\t\t\tcolors['@']  = \"\\033[47m\";       /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[105m\";      /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[101m\";      /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[101m\";      /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[43m\";       /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[103m\";      /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[102m\";      /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[104m\";      /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[44m\";       /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[100m\";      /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[105m\";      /* Pink cheeks */\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tcolors[',']  = \"\\033[25;44m\";    /* Blue background */\n\t\t\tcolors['.']  = \"\\033[5;47m\";     /* White stars */\n\t\t\tcolors['\\''] = \"\\033[25;40m\";    /* Black border */\n\t\t\tcolors['@']  = \"\\033[5;47m\";     /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[5;45m\";     /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[5;41m\";     /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[5;41m\";     /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[25;43m\";    /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[5;43m\";     /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[5;42m\";     /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[25;44m\";    /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[5;44m\";     /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[5;40m\";     /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[5;45m\";     /* Pink cheeks */\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tcolors[',']  = \"\\033[0;34;44m\";  /* Blue background */\n\t\t\tcolors['.']  = \"\\033[1;37;47m\";  /* White stars */\n\t\t\tcolors['\\''] = \"\\033[0;30;40m\";  /* Black border */\n\t\t\tcolors['@']  = \"\\033[1;37;47m\";  /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[1;35;45m\";  /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[1;31;41m\";  /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[1;31;41m\";  /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[0;33;43m\";  /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[1;33;43m\";  /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[1;32;42m\";  /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[1;34;44m\";  /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[0;34;44m\";  /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[1;30;40m\";  /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[1;35;45m\";  /* Pink cheeks */\n\t\t\toutput = \"██\";\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tcolors[',']  = \"\\033[0;34;44m\";  /* Blue background */\n\t\t\tcolors['.']  = \"\\033[1;37;47m\";  /* White stars */\n\t\t\tcolors['\\''] = \"\\033[0;30;40m\";  /* Black border */\n\t\t\tcolors['@']  = \"\\033[1;37;47m\";  /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[1;35;45m\";  /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[1;31;41m\";  /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[1;31;41m\";  /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[0;33;43m\";  /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[1;33;43m\";  /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[1;32;42m\";  /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[1;34;44m\";  /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[0;34;44m\";  /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[1;30;40m\";  /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[1;35;45m\";  /* Pink cheeks */\n\t\t\toutput = \"\\333\\333\";\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\tcolors[',']  = \"::\";             /* Blue background */\n\t\t\tcolors['.']  = \"@@\";             /* White stars */\n\t\t\tcolors['\\''] = \"  \";             /* Black border */\n\t\t\tcolors['@']  = \"##\";             /* Tan poptart */\n\t\t\tcolors['$']  = \"??\";             /* Pink poptart */\n\t\t\tcolors['-']  = \"<>\";             /* Red poptart */\n\t\t\tcolors['>']  = \"##\";             /* Red rainbow */\n\t\t\tcolors['&']  = \"==\";             /* Orange rainbow */\n\t\t\tcolors['+']  = \"--\";             /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"++\";             /* Green rainbow */\n\t\t\tcolors['=']  = \"~~\";             /* Light blue rainbow */\n\t\t\tcolors[';']  = \"$$\";             /* Dark blue rainbow */\n\t\t\tcolors['*']  = \";;\";             /* Gray cat face */\n\t\t\tcolors['%']  = \"()\";             /* Pink cheeks */\n\t\t\talways_escape = 1;\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\tcolors[',']  = \".\";             /* Blue background */\n\t\t\tcolors['.']  = \"@\";             /* White stars */\n\t\t\tcolors['\\''] = \" \";             /* Black border */\n\t\t\tcolors['@']  = \"#\";             /* Tan poptart */\n\t\t\tcolors['$']  = \"?\";             /* Pink poptart */\n\t\t\tcolors['-']  = \"O\";             /* Red poptart */\n\t\t\tcolors['>']  = \"#\";             /* Red rainbow */\n\t\t\tcolors['&']  = \"=\";             /* Orange rainbow */\n\t\t\tcolors['+']  = \"-\";             /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"+\";             /* Green rainbow */\n\t\t\tcolors['=']  = \"~\";             /* Light blue rainbow */\n\t\t\tcolors[';']  = \"$\";             /* Dark blue rainbow */\n\t\t\tcolors['*']  = \";\";             /* Gray cat face */\n\t\t\tcolors['%']  = \"o\";             /* Pink cheeks */\n\t\t\talways_escape = 1;\n\t\t\tterminal_width = 40;\n\t\t\tbreak;\n\t\tcase 8:\n\t\t\tcolors[',']  = \"\\033[48;2;0;49;105m\";    /* Blue background */\n\t\t\tcolors['.']  = \"\\033[48;2;255;255;255m\"; /* White stars */\n\t\t\tcolors['\\''] = \"\\033[48;2;0;0;0m\";       /* Black border */\n\t\t\tcolors['@']  = \"\\033[48;2;255;205;152m\"; /* Tan poptart */\n\t\t\tcolors['$']  = \"\\033[48;2;255;169;255m\"; /* Pink poptart */\n\t\t\tcolors['-']  = \"\\033[48;2;255;76;152m\";  /* Red poptart */\n\t\t\tcolors['>']  = \"\\033[48;2;255;25;0m\";    /* Red rainbow */\n\t\t\tcolors['&']  = \"\\033[48;2;255;154;0m\";   /* Orange rainbow */\n\t\t\tcolors['+']  = \"\\033[48;2;255;240;0m\";   /* Yellow Rainbow */\n\t\t\tcolors['#']  = \"\\033[48;2;40;220;0m\";    /* Green rainbow */\n\t\t\tcolors['=']  = \"\\033[48;2;0;144;255m\";   /* Light blue rainbow */\n\t\t\tcolors[';']  = \"\\033[48;2;104;68;255m\";  /* Dark blue rainbow */\n\t\t\tcolors['*']  = \"\\033[48;2;153;153;153m\"; /* Gray cat face */\n\t\t\tcolors['%']  = \"\\033[48;2;255;163;152m\"; /* Pink cheeks */\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\tif (min_col == max_col) {\n\t\tmin_col = (FRAME_WIDTH - terminal_width/2) / 2;\n\t\tmax_col = (FRAME_WIDTH + terminal_width/2) / 2;\n\t\tusing_automatic_width = 1;\n\t}\n\n\tif (min_row == max_row) {\n\t\tmin_row = (FRAME_HEIGHT - (terminal_height-1)) / 2;\n\t\tmax_row = (FRAME_HEIGHT + (terminal_height-1)) / 2;\n\t\tusing_automatic_height = 1;\n\t}\n\n\t/* Attempt to set terminal title */\n\tif (set_title) {\n\t\tprintf(\"\\033kNyanyanyanyanyanyanya...\\033\\134\");\n\t\tprintf(\"\\033]1;Nyanyanyanyanyanyanya...\\007\");\n\t\tprintf(\"\\033]2;Nyanyanyanyanyanyanya...\\007\");\n\t}\n\n\tif (clear_screen) {\n\t\t/* Clear the screen */\n\t\tprintf(\"\\033[H\\033[2J\\033[?25l\");\n\t} else {\n\t\tprintf(\"\\033[s\");\n\t}\n\n\tif (show_intro) {\n\t\t/* Display the MOTD */\n\t\tunsigned int countdown_clock = 5;\n\t\tfor (k = 0; k < countdown_clock; ++k) {\n\t\t\tnewline(3);\n\t\t\tprintf(\"                             \\033[1mNyancat Telnet Server\\033[0m\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"                   written and run by \\033[1;32mK. Lange\\033[1;34m @_klange\\033[0m\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"        If things don't look right, try:\");\n\t\t\tnewline(1);\n\t\t\tprintf(\"                TERM=fallback telnet ...\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"        Or on Windows:\");\n\t\t\tnewline(1);\n\t\t\tprintf(\"                telnet -t vtnt ...\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"        Problems? Check the website:\");\n\t\t\tnewline(1);\n\t\t\tprintf(\"                \\033[1;34mhttp://nyancat.dakko.us\\033[0m\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"        This is a telnet server, remember your escape keys!\");\n\t\t\tnewline(1);\n\t\t\tprintf(\"                \\033[1;31m^]quit\\033[0m to exit\");\n\t\t\tnewline(2);\n\t\t\tprintf(\"        Starting in %d...                \\n\", countdown_clock-k);\n\n\t\t\tfflush(stdout);\n\t\t\tusleep(400000);\n\t\t\tif (clear_screen) {\n\t\t\t\tprintf(\"\\033[H\"); /* Reset cursor */\n\t\t\t} else {\n\t\t\t\tprintf(\"\\033[u\");\n\t\t\t}\n\t\t}\n\n\t\tif (clear_screen) {\n\t\t\t/* Clear the screen again */\n\t\t\tprintf(\"\\033[H\\033[2J\\033[?25l\");\n\t\t}\n\t}\n\n\t/* Store the start time */\n\ttime_t start, current;\n\ttime(&start);\n\n\tint playing = 1;    /* Animation should continue [left here for modifications] */\n\tsize_t i = 0;       /* Current frame # */\n\tunsigned int f = 0; /* Total frames passed */\n\tchar last = 0;      /* Last color index rendered */\n\tint y, x;        /* x/y coordinates of what we're drawing */\n\twhile (playing) {\n\t\t/* Reset cursor */\n\t\tif (clear_screen) {\n\t\t\tprintf(\"\\033[H\");\n\t\t} else {\n\t\t\tprintf(\"\\033[u\");\n\t\t}\n\t\t/* Render the frame */\n\t\tfor (y = min_row; y < max_row; ++y) {\n\t\t\tfor (x = min_col; x < max_col; ++x) {\n\t\t\t\tchar color;\n\t\t\t\tif (y > 23 && y < 43 && x < 0) {\n\t\t\t\t\t/*\n\t\t\t\t\t * Generate the rainbow tail.\n\t\t\t\t\t *\n\t\t\t\t\t * This is done with a pretty simplistic square wave.\n\t\t\t\t\t */\n\t\t\t\t\tint mod_x = ((-x+2) % 16) / 8;\n\t\t\t\t\tif ((i / 2) % 2) {\n\t\t\t\t\t\tmod_x = 1 - mod_x;\n\t\t\t\t\t}\n\t\t\t\t\t/*\n\t\t\t\t\t * Our rainbow, with some padding.\n\t\t\t\t\t */\n\t\t\t\t\tconst char *rainbow = \",,>>&&&+++###==;;;,,\";\n\t\t\t\t\tcolor = rainbow[mod_x + y-23];\n\t\t\t\t\tif (color == 0) color = ',';\n\t\t\t\t} else if (x < 0 || y < 0 || y >= FRAME_HEIGHT || x >= FRAME_WIDTH) {\n\t\t\t\t\t/* Fill all other areas with background */\n\t\t\t\t\tcolor = ',';\n\t\t\t\t} else {\n\t\t\t\t\t/* Otherwise, get the color from the animation frame. */\n\t\t\t\t\tcolor = frames[i][y][x];\n\t\t\t\t}\n\t\t\t\tif (always_escape) {\n\t\t\t\t\t/* Text mode (or \"Always Send Color Escapes\") */\n\t\t\t\t\tprintf(\"%s\", colors[(int)color]);\n\t\t\t\t} else {\n\t\t\t\t\tif (color != last && colors[(int)color]) {\n\t\t\t\t\t\t/* Normal Mode, send escape (because the color changed) */\n\t\t\t\t\t\tlast = color;\n\t\t\t\t\t\tprintf(\"%s%s\", colors[(int)color], output);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t/* Same color, just send the output characters */\n\t\t\t\t\t\tprintf(\"%s\", output);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t/* End of row, send newline */\n\t\t\tnewline(1);\n\t\t}\n\t\tif (show_counter) {\n\t\t\t/* Get the current time for the \"You have nyaned...\" string */\n\t\t\ttime(&current);\n\t\t\tdouble diff = difftime(current, start);\n\t\t\t/* Now count the length of the time difference so we can center */\n\t\t\tint nLen = digits((int)diff);\n\t\t\t/*\n\t\t\t * 29 = the length of the rest of the string;\n\t\t\t * XXX: Replace this was actually checking the written bytes from a\n\t\t\t * call to sprintf or something\n\t\t\t */\n\t\t\tint width = (terminal_width - 29 - nLen) / 2;\n\t\t\t/* Spit out some spaces so that we're actually centered */\n\t\t\twhile (width > 0) {\n\t\t\t\tprintf(\" \");\n\t\t\t\twidth--;\n\t\t\t}\n\t\t\t/* You have nyaned for [n] seconds!\n\t\t\t * The \\033[J ensures that the rest of the line has the dark blue\n\t\t\t * background, and the \\033[1;37m ensures that our text is bright white.\n\t\t\t * The \\033[0m prevents the Apple ][ from flipping everything, but\n\t\t\t * makes the whole nyancat less bright on the vt220\n\t\t\t */\n\t\t\tprintf(\"\\033[1;37mYou have nyaned for %d seconds!\\033[J\\033[0m\", (int)diff);\n\t\t}\n\t\t/* Reset the last color so that the escape sequences rewrite */\n\t\tlast = 0;\n\t\t/* Update frame count */\n\t\t++f;\n\t\tif (frame_count != 0 && f == frame_count) {\n\t\t\tfinish();\n\t\t}\n\t\t++i;\n\t\tif (!frames[i]) {\n\t\t\t/* Loop animation */\n\t\t\ti = 0;\n\t\t}\n\t\t/* Wait */\n\t\tusleep(1000 * delay_ms);\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/package-manager.c",
    "content": "/**\n * @brief Graphical interface to msk\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <math.h>\n#include <pthread.h>\n\n#include <sys/time.h>\n#include <sys/stat.h>\n\n#include <sys/fswait.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/confreader.h>\n#include <toaru/icon_cache.h>\n\n#define APPLICATION_TITLE \"Package Manager\"\n#define SCROLL_AMOUNT 120\n#define VAR_PATH \"/var/msk\"\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * main_window;\nstatic gfx_context_t * ctx;\n\nstatic int application_running = 1;\n\nstatic gfx_context_t * contents = NULL;\nstatic sprite_t * contents_sprite = NULL;\n\nstatic int available_height = 0; /* How much space is available in the main window for the icon view */\nstatic int scroll_offset = 0; /* How far the icon view should be scrolled */\nstatic int hilighted_offset = -1; /* Which file is hovered by the mouse */\nstatic uint64_t last_click = 0; /* For double click */\nstatic int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */\n\nstatic int installation_done = 0;\nstatic int currently_installing = 0;\nstatic pthread_t _waiter_thread;\n\nstruct TT_Font * tt_font_thin = NULL;\nstruct TT_Font * tt_font_bold = NULL;\n\nstruct Package {\n\tchar name[256];\n\tchar friendly_name[256];\n\tchar description[1024];\n\tchar version[256]; /* Really doesn't need to be that long */\n\tint selected;\n\tint installed;\n};\n\nstatic struct Package ** pkg_pointers = NULL; /* List of package pointers */\nstatic ssize_t pkg_pointers_len = 0; /* How many packages are in the current list */\n\nstatic struct menu_bar menu_bar = {0};\nstatic struct menu_bar_entries menu_entries[] = {\n\t{\"File\", \"file\"},\n\t{\"Index\", \"index\"},\n\t{\"Help\", \"help\"},\n\t{NULL, NULL},\n};\n\nstatic void _menu_action_exit(struct MenuEntry * entry) {\n\tapplication_running = 0;\n}\n\n/**\n * Accurate time comparison.\n *\n * These methods were taken from the compositor and\n * allow us to time double-clicks accurately.\n */\nstatic uint64_t precise_current_time(void) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttime_t sec_diff = t.tv_sec;\n\tsuseconds_t usec_diff = t.tv_usec;\n\n\treturn (uint64_t)((uint64_t)sec_diff * 1000LL + usec_diff / 1000);\n}\n\nstatic uint64_t precise_time_since(uint64_t start_time) {\n\n\tuint64_t now = precise_current_time();\n\tuint64_t diff = now - start_time; /* Milliseconds */\n\n\treturn diff;\n}\n\nstatic int _close_enough(struct yutani_msg_window_mouse_event * me) {\n\tif (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n#define BUTTON_HEIGHT 28\n#define BUTTON_WIDTH 86\n#define BUTTON_PADDING 14\n#define HILIGHT_BORDER_TOP rgb(54,128,205)\n#define HILIGHT_GRADIENT_TOP rgb(93,163,236)\n#define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220)\n#define HILIGHT_BORDER_BOTTOM rgb(47,106,167)\n\n#define PKG_HEIGHT 70\nstatic void draw_package(struct Package * package, int index) {\n\tint offset_y = index * PKG_HEIGHT;\n\n\tif (package->selected) {\n\t\tif (main_window->focused) {\n\t\t\tdraw_rectangle_solid(contents, 0, offset_y, contents->width, PKG_HEIGHT, rgb(93,163,236));\n\t\t\tdraw_line(contents, 0, contents->width, offset_y, offset_y, HILIGHT_BORDER_TOP);\n\t\t\tdraw_line(contents, 0, contents->width, offset_y + PKG_HEIGHT-1, offset_y + PKG_HEIGHT-1, HILIGHT_BORDER_BOTTOM);\n\t\t\tfor (int i = 1; i < PKG_HEIGHT - 2; ++i) {\n\t\t\t\tint thing = ((i - 1) * 256) / (PKG_HEIGHT - 3);\n\t\t\t\tif (thing > 255) thing = 255;\n\t\t\t\tif (thing < 0) thing = 0;\n\t\t\t\tuint32_t c = interp_colors(HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM, thing);\n\t\t\t\tdraw_line(contents, 0, contents->width, offset_y + i, offset_y + i, c);\n\t\t\t}\n\t\t} else {\n\t\t\tdraw_rectangle_solid(contents, 0, offset_y, contents->width, PKG_HEIGHT, rgb(180,180,180));\n\t\t}\n\t}\n\n\tsprite_t * icon = package->installed ? icon_get_48(\"package\") : icon_get_48(\"package-uninstalled\");\n\tdraw_sprite(contents, icon, 8, offset_y + 11);\n\n\tuint32_t text_color = package->selected ? rgb(255,255,255) : rgb(0,0,0);\n\n\tchar tmp[2048];\n\tsprintf(tmp, \"%s - %s\", package->friendly_name, package->version);\n\ttt_set_size(tt_font_bold, 18);\n\ttt_draw_string(contents, tt_font_bold, 64, offset_y + 4 + 18, tmp, text_color);\n\tsprintf(tmp, \"%s - %s\", package->name, package->description);\n\ttt_set_size(tt_font_thin, 13);\n\tint x = tt_draw_string(contents, tt_font_thin, 65, offset_y + 24 + 13, package->name, rgb(150,150,150));\n\ttt_draw_string(contents, tt_font_thin, 64 + x + 4, offset_y + 24 + 13, package->description, text_color);\n\n}\n\nstatic void redraw_packages(void) {\n\tdraw_fill(contents, rgba(0,0,0,0));\n\n\tfor (int i = 0; i < pkg_pointers_len; ++i) {\n\t\tdraw_package(pkg_pointers[i], i);\n\t}\n}\n\nstatic void load_manifest(void) {\n\tif (pkg_pointers) {\n\t\tfor (int i = 0; i < pkg_pointers_len; ++i) {\n\t\t\tfree(pkg_pointers[i]);\n\t\t}\n\t\tfree(pkg_pointers);\n\t\tpkg_pointers_len = 0;\n\t}\n\n\tconfreader_t * conf = confreader_load(VAR_PATH \"/manifest\");\n\tif (conf) {\n\t\thashmap_t * msk_installed = hashmap_create(10);\n\n\t\tFILE * installed = fopen(VAR_PATH \"/installed\", \"r\");\n\t\tif (installed) {\n\t\t\twhile (!feof(installed)) {\n\t\t\t\tchar tmp[128] = {0};\n\t\t\t\tif (!fgets(tmp, 128, installed)) break;\n\t\t\t\tchar * nl = strstr(tmp, \"\\n\");\n\t\t\t\tif (nl) *nl = '\\0';\n\n\t\t\t\tchar * eqeq = strstr(tmp, \"==\");\n\t\t\t\tif (!eqeq) {\n\t\t\t\t\t/* show error */\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\t*eqeq = '\\0';\n\t\t\t\tchar * version = eqeq+2;\n\n\t\t\t\thashmap_set(msk_installed, tmp, strdup(version));\n\t\t\t}\n\t\t}\n\n\t\tlist_t * package_list = list_create();\n\n\t\t/* Go through sections */\n\t\tlist_t * packages = hashmap_keys(conf->sections);\n\t\tforeach(node, packages) {\n\t\t\tchar * name = node->value;\n\t\t\tif (!strlen(name)) continue; /* skip empty section */\n\t\t\tchar * desc = confreader_get(conf, name, \"description\");\n\t\t\tchar * version = confreader_get(conf, name, \"version\");\n\t\t\tchar * friendly_name = confreader_get(conf, name, \"friendly-name\");\n\n\t\t\tstruct Package * p = malloc(sizeof(struct Package));\n\t\t\tsprintf(p->name, name);\n\t\t\tsprintf(p->friendly_name, friendly_name);\n\t\t\tsprintf(p->description, desc);\n\t\t\tsprintf(p->version, version);\n\t\t\tp->selected = 0;\n\t\t\tp->installed = hashmap_has(msk_installed, name);\n\n\t\t\tlist_insert(package_list, p);\n\t\t}\n\t\tlist_free(packages);\n\t\tfree(packages);\n\n\t\thashmap_free(msk_installed);\n\t\tfree(msk_installed);\n\n\t\tpkg_pointers = malloc(sizeof(struct Package *) * package_list->length);\n\t\tpkg_pointers_len = package_list->length;\n\t\tint i = 0;\n\t\tforeach (node, package_list) {\n\t\t\tpkg_pointers[i] = node->value;\n\t\t\ti++;\n\t\t}\n\n\t\tlist_free(package_list);\n\t\tfree(package_list);\n\n\t\tint comparator(const void * c1, const void * c2) {\n\t\t\tconst struct Package * f1 = *(const struct Package **)(c1);\n\t\t\tconst struct Package * f2 = *(const struct Package **)(c2);\n\t\t\treturn strcmp(f1->name, f2->name);\n\t\t}\n\t\tqsort(pkg_pointers, pkg_pointers_len, sizeof(struct Package *), comparator);\n\t}\n\n\tif (!pkg_pointers_len) {\n\t\tsystem(\"showdialog 'Package Manager' '/usr/share/icons/48/package.png' 'No packages are available.' &\");\n\t}\n}\n\nstatic struct Package * get_package_at_offset(int offset) {\n\tif (offset >= 0 && offset < pkg_pointers_len) {\n\t\treturn pkg_pointers[offset];\n\t}\n\treturn NULL;\n}\n\nstatic void clear_offset(int offset) {\n\tdraw_rectangle_solid(contents, 0, offset * PKG_HEIGHT, contents->width, PKG_HEIGHT, rgba(0,0,0,0));\n}\n\nstatic void reinitialize_contents(void) {\n\tif (contents) {\n\t\tfree(contents);\n\t}\n\n\tif (contents_sprite) {\n\t\tsprite_free(contents_sprite);\n\t}\n\n\t/* Calculate height for current directory */\n\tint calculated_height = pkg_pointers_len * PKG_HEIGHT;\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\n\tcontents_sprite = create_sprite(main_window->width - bounds.width, calculated_height, ALPHA_EMBEDDED);\n\tcontents = init_graphics_sprite(contents_sprite);\n\n\t/* Draw packages */\n\tredraw_packages();\n}\n\nstatic void redraw_window(void) {\n\tdraw_fill(ctx, rgb(255,255,255));\n\n\trender_decorations(main_window, ctx, APPLICATION_TITLE);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\n\tmenu_bar.x = bounds.left_width;\n\tmenu_bar.y = bounds.top_height;\n\tmenu_bar.width = ctx->width - bounds.width;\n\tmenu_bar.window = main_window;\n\tmenu_bar_render(&menu_bar, ctx);\n\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT, ctx->width - bounds.width, ctx->height - MENU_BAR_HEIGHT - bounds.height);\n\tdraw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT - scroll_offset);\n\tgfx_clear_clip(ctx);\n\tgfx_add_clip(ctx, 0, 0, ctx->width, ctx->height);\n\n\tflip(ctx);\n\tyutani_flip(yctx, main_window);\n}\n\nstatic void resize_finish(int w, int h) {\n\tint width_changed = (main_window->width != (unsigned int)w);\n\n\tyutani_window_resize_accept(yctx, main_window, w, h);\n\treinit_graphics_yutani(ctx, main_window);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\n\t/* Recalculate available size */\n\tavailable_height = ctx->height - MENU_BAR_HEIGHT - bounds.height;\n\n\t/* If the width changed, we need to rebuild the icon view */\n\tif (width_changed) {\n\t\treinitialize_contents();\n\t}\n\n\t/* Make sure we're not scrolled weirdly after resizing */\n\tif (available_height > contents->height) {\n\t\tscroll_offset = 0;\n\t} else {\n\t\tif (scroll_offset > contents->height - available_height) {\n\t\t\tscroll_offset = contents->height - available_height;\n\t\t}\n\t}\n\n\t/* Redraw */\n\tredraw_window();\n\tyutani_window_resize_done(yctx, main_window);\n\n\tyutani_flip(yctx, main_window);\n}\n\nstatic void _menu_action_refresh(struct MenuEntry * entry) {\n\t/* refresh msk manifest */\n\tsystem(\"terminal msk update\");\n\n\tload_manifest();\n\treinitialize_contents();\n\tredraw_window();\n}\n\n\nstatic void * package_installer_thread(void * arg) {\n\tchar * packages = arg;\n\tputenv(\"MSK_YES=1\");\n\tchar tmp[1024];\n\tsprintf(tmp, \"terminal msk install %s\", packages);\n\tfree(packages);\n\tsystem(tmp);\n\tinstallation_done = 1;\n\treturn NULL;\n}\n\nstatic void install_packages(void) {\n\tif (currently_installing) return;\n\n\t/* Figure out what packages to install */\n\tsize_t c_len = 0;\n\tfor (int i = 0; i <pkg_pointers_len; ++i) {\n\t\tif (pkg_pointers[i]->selected) {\n\t\t\tc_len += strlen(pkg_pointers[i]->name) + 2;\n\t\t}\n\t}\n\n\tif (!c_len) return; /* Nothing selected? */\n\n\tchar * packages = malloc(c_len);\n\tchar * cursor = packages;\n\n\tfor (int i = 0; i <pkg_pointers_len; ++i) {\n\t\tif (pkg_pointers[i]->selected) {\n\t\t\tcursor += sprintf(cursor, \"%s \", pkg_pointers[i]->name);\n\t\t}\n\t}\n\n\tinstallation_done = 0;\n\tcurrently_installing = 1;\n\n\tpthread_create(&_waiter_thread, NULL, package_installer_thread, packages);\n}\n\nstatic void _menu_action_about(struct MenuEntry * entry) {\n\t/* Show About dialog */\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About \" APPLICATION_TITLE \"\\\" /usr/share/icons/48/package.png \\\"ToaruOS \" APPLICATION_TITLE \"\\\" \\\"© 2018 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\tredraw_window();\n}\n\nstatic void _menu_action_help(struct MenuEntry * entry) {\n\t/* show help documentation */\n\tsystem(\"help-browser package-manager.trt &\");\n\tredraw_window();\n}\n\nstatic void toggle_selected(int hilighted_offset, int modifiers) {\n\tstruct Package * f = get_package_at_offset(hilighted_offset);\n\n\t/* No file at this offset, do nothing. */\n\tif (!f) return;\n\n\t/* Toggle selection of the current file */\n\tf->selected = !f->selected;\n\n\t/* If Ctrl wasn't held, unselect everything else. */\n\tif (!(modifiers & KEY_MOD_LEFT_CTRL)) {\n\t\tfor (int i = 0; i < pkg_pointers_len; ++i) {\n\t\t\tif (pkg_pointers[i] != f && pkg_pointers[i]->selected) {\n\t\t\t\tpkg_pointers[i]->selected = 0;\n\t\t\t\tclear_offset(i);\n\t\t\t\tdraw_package(pkg_pointers[i], i);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Redraw the pkg */\n\tclear_offset(hilighted_offset);\n\tdraw_package(f, hilighted_offset);\n\n\t/* And repaint the window */\n\tredraw_window();\n}\n\nstatic void _scroll_up(void) {\n\tscroll_offset -= SCROLL_AMOUNT;\n\tif (scroll_offset < 0) {\n\t\tscroll_offset = 0;\n\t}\n}\n\nstatic void _scroll_down(void) {\n\tif (available_height > contents->height) {\n\t\tscroll_offset = 0;\n\t} else {\n\t\tscroll_offset += SCROLL_AMOUNT;\n\t\tif (scroll_offset > contents->height - available_height) {\n\t\t\tscroll_offset = contents->height - available_height;\n\t\t}\n\t}\n}\n\nstatic void arrow_select(int y) {\n\tif (!pkg_pointers_len) return;\n\n\t/* Find first selected */\n\tint selected = -1;\n\tfor (int i = 0; i <pkg_pointers_len; ++i) {\n\t\tif (pkg_pointers[i]->selected) {\n\t\t\tselected = i;\n\t\t\tpkg_pointers[i]->selected = 0;\n\t\t\tclear_offset(i);\n\t\t\tdraw_package(pkg_pointers[i], i);\n\t\t}\n\t}\n\n\tif (selected == -1) {\n\t\tselected = 0;\n\t} else {\n\t\tselected += y;\n\t\tif (selected >= pkg_pointers_len) selected = pkg_pointers_len - 1;\n\t\tif (selected < 0) selected = 0;\n\t}\n\n\tif (selected * PKG_HEIGHT < scroll_offset) {\n\t\tscroll_offset = selected * PKG_HEIGHT;\n\t}\n\tif (selected * PKG_HEIGHT + PKG_HEIGHT > scroll_offset + available_height) {\n\t\tscroll_offset = selected * PKG_HEIGHT + PKG_HEIGHT - available_height;\n\t}\n\n\tpkg_pointers[selected]->selected = 1;\n\tclear_offset(selected);\n\tdraw_package(pkg_pointers[selected], selected);\n\tredraw_window();\n}\n\nstatic void redraw_window_callback(struct menu_bar * self) {\n\t(void)self;\n\tredraw_window();\n}\n\nint main(int argc, char * argv[]) {\n\n\tif (geteuid() != 0) {\n\t\tchar * args[] = {\n\t\t\t\"showdialog\",\n\t\t\t\"Package Manager\",\n\t\t\t\"/usr/share/icons/48/package.png\",\n\t\t\t\"Only root can manage packages.\",\n\t\t\tNULL,\n\t\t};\n\t\treturn execvp(\"showdialog\", args);\n\t}\n\n\tyctx = yutani_init();\n\tinit_decorations();\n\tmain_window = yutani_window_create(yctx, 640, 480);\n\tyutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2);\n\tctx = init_graphics_yutani_double_buffer(main_window);\n\n\ttt_font_thin = tt_font_from_shm(\"sans-serif\");\n\ttt_font_bold = tt_font_from_shm(\"sans-serif.bold\");\n\n\tyutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, \"package\");\n\n\tmenu_bar.entries = menu_entries;\n\tmenu_bar.redraw_callback = redraw_window_callback;\n\n\tmenu_bar.set = menu_set_create();\n\n\tstruct MenuList * m = menu_create(); /* File */\n\tmenu_insert(m, menu_create_normal(\"exit\",NULL,\"Exit\", _menu_action_exit));\n\tmenu_set_insert(menu_bar.set, \"file\", m);\n\n\tm = menu_create(); /* Go */\n\tmenu_insert(m, menu_create_normal(\"refresh\",NULL,\"Refresh\",_menu_action_refresh));\n\tmenu_set_insert(menu_bar.set, \"index\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(\"help\",\"help_browser.trt\",\"Contents\",_menu_action_help));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"star\",NULL,\"About \" APPLICATION_TITLE,_menu_action_about));\n\tmenu_set_insert(menu_bar.set, \"help\", m);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(main_window, &bounds);\n\tavailable_height = ctx->height - MENU_BAR_HEIGHT - bounds.height;\n\n\tstruct stat statbuf;\n\tif (!stat(VAR_PATH \"/manifest\",&statbuf)) {\n\t\tload_manifest();\n\t\treinitialize_contents();\n\t\tredraw_window();\n\t} else {\n\t\t_menu_action_refresh(NULL);\n\t\t/* also redraws window */\n\t}\n\n\tint fds[1] = {fileno(yctx->sock)};\n\n\twhile (application_running) {\n\t\tint index = fswait2(1,fds,200);\n\n\t\t/* Were we waiting for a package to install? */\n\t\tif (currently_installing) {\n\t\t\tif (installation_done) {\n\t\t\t\tcurrently_installing = 0;\n\t\t\t\tinstallation_done = 0;\n\t\t\t\tload_manifest();\n\t\t\t\treinitialize_contents();\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t}\n\n\t\tif (index != 0) continue;\n\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw_window();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->wid == main_window->wid) {\n\t\t\t\t\t\t\tswitch (ke->event.keycode) {\n\t\t\t\t\t\t\t\tcase KEY_PAGE_UP:\n\t\t\t\t\t\t\t\t\t_scroll_up();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_PAGE_DOWN:\n\t\t\t\t\t\t\t\t\t_scroll_down();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\t\t\t\t\t\tarrow_select(1);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase KEY_ARROW_UP:\n\t\t\t\t\t\t\t\t\tarrow_select(-1);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase '\\n':\n\t\t\t\t\t\t\t\t\tinstall_packages();\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'f':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'i':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'h':\n\t\t\t\t\t\t\t\t\tif (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) {\n\t\t\t\t\t\t\t\t\t\tmenu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw_packages();\n\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == main_window->wid) {\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid);\n\n\t\t\t\t\t\tif (win == main_window) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/* Menu bar */\n\t\t\t\t\t\t\tmenu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y);\n\n\t\t\t\t\t\t\tif (me->new_y > (int)(bounds.top_height + MENU_BAR_HEIGHT) &&\n\t\t\t\t\t\t\t\tme->new_y < (int)(main_window->height - bounds.bottom_height) &&\n\t\t\t\t\t\t\t\tme->new_x > (int)(bounds.left_width) &&\n\t\t\t\t\t\t\t\tme->new_x < (int)(main_window->width - bounds.right_width)) {\n\t\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\t\t\t_scroll_up();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\t\t\t_scroll_down();\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t/* Get offset into contents */\n\t\t\t\t\t\t\t\tint y_into = me->new_y - bounds.top_height - MENU_BAR_HEIGHT + scroll_offset;\n\t\t\t\t\t\t\t\tint offset = (y_into / PKG_HEIGHT);\n\t\t\t\t\t\t\t\tif (offset != hilighted_offset) {\n\t\t\t\t\t\t\t\t\tint old_offset = hilighted_offset;\n\t\t\t\t\t\t\t\t\thilighted_offset = offset;\n\t\t\t\t\t\t\t\t\tif (old_offset != -1) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tstruct Package * f = get_package_at_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\t\tdraw_package(f, old_offset);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tstruct Package * f = get_package_at_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\t\tdraw_package(f, hilighted_offset);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) {\n\t\t\t\t\t\t\t\t\tstruct Package * f = get_package_at_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tif (last_click_offset == hilighted_offset && precise_time_since(last_click) < 400) {\n\t\t\t\t\t\t\t\t\t\t\tinstall_packages();\n\t\t\t\t\t\t\t\t\t\t\tlast_click = 0;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tlast_click = precise_current_time();\n\t\t\t\t\t\t\t\t\t\t\tlast_click_offset = hilighted_offset;\n\t\t\t\t\t\t\t\t\t\t\ttoggle_selected(hilighted_offset, me->modifiers);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tif (!(me->modifiers & YUTANI_KEY_MODIFIER_CTRL)) {\n\t\t\t\t\t\t\t\t\t\t\tfor (int i = 0; i < pkg_pointers_len; ++i) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (pkg_pointers[i]->selected) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tpkg_pointers[i]->selected = 0;\n\t\t\t\t\t\t\t\t\t\t\t\t\tclear_offset(i);\n\t\t\t\t\t\t\t\t\t\t\t\t\tdraw_package(pkg_pointers[i], i);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {\n#if 0\n\t\t\t\t\t\t\t\t\tif (!context_menu->window) {\n\t\t\t\t\t\t\t\t\t\tstruct Package * f = get_package_at_offset(hilighted_offset);\n\t\t\t\t\t\t\t\t\t\tif (f && !f->selected) {\n\t\t\t\t\t\t\t\t\t\t\ttoggle_selected(hilighted_offset, me->modifiers);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tmenu_show(context_menu, main_window->ctx);\n\t\t\t\t\t\t\t\t\t\tyutani_window_move(main_window->ctx, context_menu->window, me->new_x + main_window->x, me->new_y + main_window->y);\n\t\t\t\t\t\t\t\t\t}\n#endif\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tint old_offset = hilighted_offset;\n\t\t\t\t\t\t\t\thilighted_offset = -1;\n\t\t\t\t\t\t\t\tif (old_offset != -1) {\n\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\tstruct Package * f = get_package_at_offset(old_offset);\n\t\t\t\t\t\t\t\t\tif (f) {\n\t\t\t\t\t\t\t\t\t\tclear_offset(old_offset);\n\t\t\t\t\t\t\t\t\t\tdraw_package(f, old_offset);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tredraw_window();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t_menu_action_exit(NULL);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "apps/panel.c",
    "content": "/**\n * @file apps/panel.c\n * @brief Panel with widgets. Main desktop interface.\n *\n * Provides the panel shown at the top of the screen, which\n * presents application windows, useful widgets, and a menu\n * for launching new apps.\n *\n * Also provides Alt-Tab app switching and a few other goodies.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <time.h>\n#include <unistd.h>\n#include <signal.h>\n#include <pthread.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n#include <sys/fswait.h>\n#include <sys/shm.h>\n\n/* auto-dep: export-dynamic */\n#include <dlfcn.h>\n\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/icon_cache.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n\n/* Several theming defines are in here */\n#include <toaru/panel.h>\n\n/* These are local to the core panel, so we don't need to put them in the header */\n#define ALTTAB_WIDTH  250\n#define ALTTAB_HEIGHT 200\n#define ALTTAB_BACKGROUND premultiply(rgba(0,0,0,150))\n#define ALTTAB_OFFSET 10\n#define ALTTAB_WIN_SIZE 140\n\n#define ALTF2_WIDTH 400\n#define ALTF2_HEIGHT 200\n\n/* How many windows we can support in the advertisement lift before truncating it */\n#define MAX_WINDOW_COUNT 100\n/* Height of the panel window */\n#define PANEL_HEIGHT 27\n/* How far down dropdown menus should be shown */\n#define DROPDOWN_OFFSET PANEL_HEIGHT\n/* How much padding should be assured on the left and right of the screen for menus */\n#define MENU_PAD 4\n\nstatic struct PanelContext panel_context;\n\nstatic gfx_context_t * ctx = NULL;\nstatic yutani_window_t * panel = NULL;\n\nstatic gfx_context_t * actx = NULL;\nstatic yutani_window_t * alttab = NULL;\n\nstatic gfx_context_t * a2ctx = NULL;\nstatic yutani_window_t * alt_f2 = NULL;\n\nstatic size_t bg_size;\nstatic char * bg_blob;\n\n/* External interface for widgets */\nlist_t * window_list = NULL;\nyutani_t * yctx;\nint width;\nint height;\n\nlist_t * widgets_enabled = NULL;\n\n/* Windows, indexed by z-order */\nstruct window_ad * ads_by_z[MAX_WINDOW_COUNT+1] = {NULL};\n\nint active_window = -1;\n\nstatic int was_tabbing = 0;\nstatic int new_focused = -1;\n\nstatic void widgets_layout(void);\n\nstatic int _close_enough(struct yutani_msg_window_mouse_event * me) {\n\tif (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int center_x(int x) {\n\treturn (width - x) / 2;\n}\n\nstatic int center_y(int y) {\n\treturn (height - y) / 2;\n}\n\nstatic int center_x_a(int x) {\n\treturn (alttab->width - x) / 2;\n}\n\nstatic int center_x_a2(int x) {\n\treturn (ALTF2_WIDTH - x) / 2;\n}\n\nstatic volatile int _continue = 1;\n\nstatic void toggle_hide_panel(void) {\n\tstatic int panel_hidden = 0;\n\n\tif (panel_hidden) {\n\t\t/* Unhide the panel */\n\t\tfor (int i = PANEL_HEIGHT-1; i >= 0; i--) {\n\t\t\tyutani_window_move(yctx, panel, 0, -i);\n\t\t\tusleep(3000);\n\t\t}\n\t\tpanel_hidden = 0;\n\t} else {\n\t\t/* Hide the panel */\n\t\tfor (int i = 1; i <= PANEL_HEIGHT-1; i++) {\n\t\t\tyutani_window_move(yctx, panel, 0, -i);\n\t\t\tusleep(3000);\n\t\t}\n\t\tpanel_hidden = 1;\n\t}\n}\n\n/* Handle SIGINT by telling other threads (clock) to shut down */\nstatic void sig_int(int sig) {\n\tprintf(\"Received shutdown signal in panel!\\n\");\n\t_continue = 0;\n\tsignal(SIGINT, sig_int);\n}\n\nvoid launch_application(char * app) {\n\tif (!fork()) {\n\t\tprintf(\"Starting %s\\n\", app);\n\t\tchar * args[] = {\"/bin/sh\", \"-c\", app, NULL};\n\t\texecvp(args[0], args);\n\t\texit(1);\n\t}\n}\n\n/* Callback for mouse events */\nstatic void panel_check_click(struct yutani_msg_window_mouse_event * evt) {\n\tstatic struct PanelWidget * old_target = NULL;\n\n\tif (evt->wid == panel->wid) {\n\n\t\t/* Figure out what widget this belongs to */\n\t\tstruct PanelWidget * target = NULL;\n\t\tif (evt->new_y >= 0 && evt->new_y < PANEL_HEIGHT) {\n\t\t\tforeach(widget_node, widgets_enabled) {\n\t\t\t\tstruct PanelWidget * widget = widget_node->value;\n\t\t\t\tif (evt->new_x >= widget->left && evt->new_x < widget->left + widget->width) {\n\t\t\t\t\ttarget = widget;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tint needs_redraw = 0;\n\n\t\tif (evt->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(evt)) {\n\t\t\tif (target) needs_redraw |= target->click(target, evt);\n\t\t} else if (evt->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {\n\t\t\tif (target) needs_redraw |= target->right_click(target, evt);\n\t\t} else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) {\n\t\t\tif (old_target && target != old_target) needs_redraw |= old_target->leave(old_target, evt);\n\t\t\tif (target && target != old_target) needs_redraw |= target->enter(target, evt);\n\t\t\tif (target) needs_redraw |= target->move(target, evt);\n\t\t\told_target = target;\n\t\t} else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) {\n\t\t\tif (old_target) needs_redraw |= old_target->leave(old_target, evt);\n\t\t\told_target = NULL;\n\t\t}\n\n\t\tif (needs_redraw) redraw();\n\t}\n}\n\nstatic char altf2_buffer[1024] = {0};\nstatic unsigned int altf2_collected = 0;\n\nstatic void close_altf2(void) {\n\tfree(a2ctx->backbuffer);\n\tfree(a2ctx);\n\n\taltf2_buffer[0] = 0;\n\taltf2_collected = 0;\n\n\tyutani_close(yctx, alt_f2);\n\talt_f2 = NULL;\n}\n\nstatic void redraw_altf2(void) {\n\tdraw_fill(a2ctx, 0);\n\tdraw_rounded_rectangle(a2ctx,0,0, ALTF2_WIDTH, ALTF2_HEIGHT, 11, premultiply(rgba(120,120,120,150)));\n\tdraw_rounded_rectangle(a2ctx,1,1, ALTF2_WIDTH-2, ALTF2_HEIGHT-2, 10, ALTTAB_BACKGROUND);\n\n\ttt_set_size(panel_context.font, 20);\n\tint t = tt_string_width(panel_context.font, altf2_buffer);\n\ttt_draw_string(a2ctx, panel_context.font, center_x_a2(t), 80, altf2_buffer, rgb(255,255,255));\n\n\tflip(a2ctx);\n\tyutani_flip(yctx, alt_f2);\n}\n\nstatic void redraw_alttab(void) {\n\tif (!actx) return;\n\twhile (new_focused > -1 && !ads_by_z[new_focused]) {\n\t\tnew_focused--;\n\t}\n\tif (new_focused == -1) {\n\t\t/* Stop tabbing */\n\t\twas_tabbing = 0;\n\t\tfree(actx->backbuffer);\n\t\tfree(actx);\n\t\tactx = NULL;\n\t\tyutani_close(yctx, alttab);\n\t\treturn;\n\t}\n\n\t/* How many windows do we have? */\n\tunsigned int window_count = 0;\n\twhile (ads_by_z[window_count]) window_count++;\n\n#define ALTTAB_COLUMNS 5\n\n\t/* How many rows should that be? */\n\tint rows = (window_count - 1) / ALTTAB_COLUMNS + 1;\n\n\t/* How many columns? */\n\tint columns = (rows == 1) ? window_count : ALTTAB_COLUMNS;\n\n\t/* How much padding on the last row? */\n\tint last_row = (window_count % columns) ? ((ALTTAB_WIN_SIZE + 20) * (columns - (window_count % columns))) / 2  : 0;\n\n\t/* Is the window the right size? */\n\tunsigned int expected_width = columns * (ALTTAB_WIN_SIZE + 20) + 40;\n\tunsigned int expected_height = rows * (ALTTAB_WIN_SIZE + 20) + 60;\n\n\tif (alttab->width != expected_width || alttab->height != expected_height) {\n\t\tyutani_window_resize(yctx, alttab, expected_width, expected_height);\n\t\treturn;\n\t}\n\n\t/* Draw the background, right now just a dark semi-transparent box */\n\tdraw_fill(actx, 0);\n\tdraw_rounded_rectangle(actx,0,0, alttab->width, alttab->height, 11, premultiply(rgba(120,120,120,150)));\n\tdraw_rounded_rectangle(actx,1,1, alttab->width-2, alttab->height-2, 10, ALTTAB_BACKGROUND);\n\n\tfor (unsigned int i = 0; i < window_count; ++i) {\n\t\tif (!ads_by_z[i]) continue;\n\t\tstruct window_ad * ad = ads_by_z[i];\n\n\t\t/* Figure out grid alignment for this element */\n\t\tint pos_x = ((window_count - i - 1) % ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20;\n\t\tint pos_y = ((window_count - i - 1) / ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20;\n\n\t\tif ((window_count - i - 1) / ALTTAB_COLUMNS == (unsigned int)rows - 1) {\n\t\t\tpos_x += last_row;\n\t\t}\n\n\t\tif (i == (unsigned int)new_focused) {\n\t\t\tdraw_rounded_rectangle(actx, pos_x, pos_y, ALTTAB_WIN_SIZE + 20, ALTTAB_WIN_SIZE + 20, 7, premultiply(rgba(170,170,170,150)));\n\t\t}\n\n\t\t/* try very hard to get a window texture */\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(yctx->server_ident, key, 1024, ad->bufid);\n\t\tsize_t size = 0;\n\t\tuint32_t * buf =  shm_obtain(key, &size);\n\n\t\tif (buf && size >= ad->width * ad->height * 4) {\n\t\t\tsprite_t tmp;\n\t\t\ttmp.width = ad->width;\n\t\t\ttmp.height = ad->height;\n\t\t\ttmp.bitmap = buf;\n\n\t\t\tint ox = 0;\n\t\t\tint oy = 0;\n\t\t\tint sw, sh;\n\t\t\tif (tmp.width > tmp.height) {\n\t\t\t\tsw = ALTTAB_WIN_SIZE;\n\t\t\t\tsh = tmp.height * ALTTAB_WIN_SIZE / tmp.width;\n\t\t\t\toy = (ALTTAB_WIN_SIZE - sh) / 2;\n\t\t\t} else {\n\t\t\t\tsh = ALTTAB_WIN_SIZE;\n\t\t\t\tsw = tmp.width * ALTTAB_WIN_SIZE / tmp.height;\n\t\t\t\tox = (ALTTAB_WIN_SIZE - sw) / 2;\n\t\t\t}\n\t\t\tdraw_sprite_scaled(actx, &tmp,\n\t\t\t\tpos_x + ox + 10,\n\t\t\t\tpos_y + oy + 10,\n\t\t\t\tsw, sh);\n\n\t\t\tshm_release(key);\n\n\t\t\tsprite_t * icon = icon_get_48(ad->icon);\n\t\t\tdraw_sprite(actx, icon, pos_x + 10 + ALTTAB_WIN_SIZE - 50, pos_y + 10 + ALTTAB_WIN_SIZE - 50);\n\t\t} else {\n\t\t\tsprite_t * icon = icon_get_48(ad->icon);\n\t\t\tdraw_sprite(actx, icon, pos_x + 10 + (ALTTAB_WIN_SIZE - 48) / 2, pos_y + 10 + (ALTTAB_WIN_SIZE - 48) / 2);\n\t\t}\n\n\t}\n\n\t{\n\t\tstruct window_ad * ad = ads_by_z[new_focused];\n\t\tint t;\n\t\tchar * title = tt_ellipsify(ad->name, 16, panel_context.font, alttab->width - 20, &t);\n\t\ttt_set_size(panel_context.font, 16);\n\t\ttt_draw_string(actx, panel_context.font, center_x_a(t), rows * (ALTTAB_WIN_SIZE + 20) + 44, title, rgb(255,255,255));\n\t\tfree(title);\n\t}\n\n\tflip(actx);\n\tyutani_window_move(yctx, alttab, center_x(alttab->width), center_y(alttab->height));\n\tyutani_flip(yctx, alttab);\n}\n\nstatic pthread_t _waiter_thread;\nstatic void * logout_prompt_waiter(void * arg) {\n\tif (system(\"showdialog \\\"Log Out\\\" /usr/share/icons/48/exit.png \\\"Are you sure you want to log out?\\\"\") == 0) {\n\t\tyutani_session_end(yctx);\n\t\t_continue = 0;\n\t}\n\treturn NULL;\n}\n\nvoid launch_application_menu(struct MenuEntry * self) {\n\tstruct MenuEntry_Normal * _self = (void *)self;\n\n\tif (!strcmp((char *)_self->action,\"log-out\")) {\n\t\t/* Spin off a thread for this */\n\t\tpthread_create(&_waiter_thread, NULL, logout_prompt_waiter, NULL);\n\t} else {\n\t\tlaunch_application((char *)_self->action);\n\t}\n}\n\nstatic void handle_key_event(struct yutani_msg_key_event * ke) {\n\t/**\n\t * Is the Alt+F2 command entry window open?\n\t * Then we should capture all typing and use\n\t * direct it to the 'input box'.\n\t */\n\tif (alt_f2 && ke->wid == alt_f2->wid) {\n\t\tif (ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t/* Escape = cancel */\n\t\t\tif (ke->event.keycode == KEY_ESCAPE) {\n\t\t\t\tclose_altf2();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* Backspace */\n\t\t\tif (ke->event.key == '\\b') {\n\t\t\t\tif (altf2_collected) {\n\t\t\t\t\taltf2_buffer[altf2_collected-1] = '\\0';\n\t\t\t\t\taltf2_collected--;\n\t\t\t\t\tredraw_altf2();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* Enter */\n\t\t\tif (ke->event.key == '\\n') {\n\t\t\t\t/* execute */\n\t\t\t\tlaunch_application(altf2_buffer);\n\t\t\t\tclose_altf2();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* Some other key */\n\t\t\tif (!ke->event.key) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t/* Try to add it */\n\t\t\tif (altf2_collected < sizeof(altf2_buffer) - 1) {\n\t\t\t\taltf2_buffer[altf2_collected] = ke->event.key;\n\t\t\t\taltf2_collected++;\n\t\t\t\taltf2_buffer[altf2_collected] = 0;\n\t\t\t\tredraw_altf2();\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Ctrl-Alt-T = Open a new terminal */\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&\n\t\t(ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t(ke->event.keycode == 't') &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\n\t\tlaunch_application(\"exec terminal\");\n\t\treturn;\n\t}\n\n\t/* Ctrl-F11 = Toggle visibility of the panel */\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) &&\n\t\t(ke->event.keycode == KEY_F11) &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\n\t\tfprintf(stderr, \"[panel] Toggling visibility.\\n\");\n\t\ttoggle_hide_panel();\n\t\treturn;\n\t}\n\n\t/* Alt-F2 = Show the command entry window */\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t(ke->event.keycode == KEY_F2) &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\t\t/* show menu */\n\t\tif (!alt_f2) {\n\t\t\talt_f2 = yutani_window_create_flags(yctx, ALTF2_WIDTH, ALTF2_HEIGHT, YUTANI_WINDOW_FLAG_BLUR_BEHIND);\n\t\t\tyutani_window_update_shape(yctx, alt_f2, 5);\n\t\t\tyutani_window_move(yctx, alt_f2, center_x(ALTF2_WIDTH), center_y(ALTF2_HEIGHT));\n\t\t\ta2ctx = init_graphics_yutani_double_buffer(alt_f2);\n\t\t\tredraw_altf2();\n\t\t}\n\t}\n\n\t/* Maybe a plugin wants to handle this key bind */\n\tforeach(widget_node, widgets_enabled) {\n\t\tstruct PanelWidget * widget = widget_node->value;\n\t\twidget->onkey(widget, ke);\n\t}\n\n\t/* Releasing Alt when the Alt-Tab switcher is visible */\n\tif ((was_tabbing) && (ke->event.keycode == 0 || ke->event.keycode == KEY_LEFT_ALT) &&\n\t\t(ke->event.modifiers == 0) && (ke->event.action == KEY_ACTION_UP)) {\n\n\t\tfprintf(stderr, \"[panel] Stopping focus new_focused = %d\\n\", new_focused);\n\n\t\tstruct window_ad * ad = ads_by_z[new_focused];\n\n\t\tif (!ad) return;\n\n\t\tyutani_focus_window(yctx, ad->wid);\n\t\twas_tabbing = 0;\n\t\tnew_focused = -1;\n\n\t\tfree(actx->backbuffer);\n\t\tfree(actx);\n\t\tactx = NULL;\n\n\t\tyutani_close(yctx, alttab);\n\n\t\treturn;\n\t}\n\n\t/* Alt-Tab = Switch windows */\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t(ke->event.keycode == '\\t') &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\n\t\t/* Alt-Tab and Alt-Shift-Tab should go in alternate directions */\n\t\tint direction = (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) ? 1 : -1;\n\n\t\t/* Are there no windows to switch? */\n\t\tif (window_list->length < 1) return;\n\n\t\t/* Figure out the new focused window */\n\t\tif (was_tabbing) {\n\t\t\tnew_focused = new_focused + direction;\n\t\t} else {\n\t\t\tnew_focused = active_window + direction;\n\t\t\t/* Create tab window */\n\t\t\talttab = yutani_window_create_flags(yctx, ALTTAB_WIDTH, ALTTAB_HEIGHT,\n\t\t\t\tYUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_NO_ANIMATION | YUTANI_WINDOW_FLAG_BLUR_BEHIND);\n\t\t\tyutani_window_update_shape(yctx, alttab, 5);\n\n\t\t\tyutani_set_stack(yctx, alttab, YUTANI_ZORDER_OVERLAY);\n\n\t\t\t/* Initialize graphics context against the window */\n\t\t\tactx = init_graphics_yutani_double_buffer(alttab);\n\t\t}\n\n\t\t/* Handle wraparound */\n\t\tif (new_focused < 0) {\n\t\t\tnew_focused = 0;\n\t\t\tfor (int i = 0; i < MAX_WINDOW_COUNT; i++) {\n\t\t\t\tif (ads_by_z[i+1] == NULL) {\n\t\t\t\t\tnew_focused = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (ads_by_z[new_focused] == NULL) {\n\t\t\tif (ads_by_z[0]) {\n\t\t\t\tnew_focused = 0;\n\t\t\t} else {\n\t\t\t\tnew_focused = -1;\n\t\t\t}\n\t\t}\n\n\t\twas_tabbing = 1;\n\t\tredraw_alttab();\n\t}\n}\n\nvoid redraw(void) {\n\tmemcpy(ctx->backbuffer, bg_blob, bg_size);\n\n\tforeach(widget_node, widgets_enabled) {\n\t\tstruct PanelWidget * widget = widget_node->value;\n\t\tgfx_context_t * inner = init_graphics_subregion(ctx, widget->left, 0, widget->width, PANEL_HEIGHT);\n\t\twidget->draw(widget, inner);\n\t\tfree(inner);\n\t}\n\n\t/* Flip */\n\tflip(ctx);\n\tyutani_flip(yctx, panel);\n}\n\nstatic void update_window_list(void) {\n\tyutani_query_windows(yctx);\n\n\tlist_t * new_window_list = list_create();\n\n\tads_by_z[0] = NULL;\n\n\tint i = 0;\n\twhile (1) {\n\t\t/* We wait for a series of WINDOW_ADVERTISE messsages */\n\t\tyutani_msg_t * m = yutani_wait_for(yctx, YUTANI_MSG_WINDOW_ADVERTISE);\n\t\tstruct yutani_msg_window_advertise * wa = (void*)m->data;\n\n\t\tif (wa->size == 0) {\n\t\t\t/* A sentinal at the end will have a size of 0 */\n\t\t\tfree(m);\n\t\t\tbreak;\n\t\t}\n\n\t\t/* Store each window advertisement */\n\t\tstruct window_ad * ad = malloc(sizeof(struct window_ad));\n\n\t\tchar * s = malloc(wa->size);\n\t\tmemcpy(s, wa->strings, wa->size);\n\t\tad->name = &s[0];\n\t\tad->icon = &s[wa->icon];\n\t\tad->strings = s;\n\t\tad->flags = wa->flags;\n\t\tad->wid = wa->wid;\n\t\tad->bufid = wa->bufid;\n\t\tad->width = wa->width;\n\t\tad->height = wa->height;\n\n\t\tads_by_z[i] = ad;\n\t\ti++;\n\t\tads_by_z[i] = NULL;\n\n\t\tnode_t * next = NULL;\n\n\t\t/* And insert it, ordered by wid, into the window list */\n\t\tforeach(node, new_window_list) {\n\t\t\tstruct window_ad * n = node->value;\n\n\t\t\tif (n->wid > ad->wid) {\n\t\t\t\tnext = node;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (next) {\n\t\t\tlist_insert_before(new_window_list, next, ad);\n\t\t} else {\n\t\t\tlist_insert(new_window_list, ad);\n\t\t}\n\t\tfree(m);\n\t}\n\tactive_window = i-1;\n\n\tif (window_list) {\n\t\tforeach(node, window_list) {\n\t\t\tstruct window_ad * ad = (void*)node->value;\n\t\t\tfree(ad->strings);\n\t\t\tfree(ad);\n\t\t}\n\t\tlist_free(window_list);\n\t\tfree(window_list);\n\t}\n\twindow_list = new_window_list;\n\n\t/* And redraw the panel */\n\tredraw();\n}\n\nstatic void redraw_panel_background(gfx_context_t * ctx, int width, int height) {\n\tdraw_fill(ctx, rgba(0,0,0,0xF2));\n}\n\nstatic void resize_finish(int xwidth, int xheight) {\n\tyutani_window_resize_accept(yctx, panel, xwidth, xheight);\n\n\treinit_graphics_yutani(ctx, panel);\n\tyutani_window_resize_done(yctx, panel);\n\n\twidth = xwidth;\n\n\tredraw_panel_background(ctx, xwidth, xheight);\n\n\t/* Copy the prerendered background so we can redraw it quickly */\n\tbg_size = panel->width * panel->height * sizeof(uint32_t);\n\tbg_blob = realloc(bg_blob, bg_size);\n\tmemcpy(bg_blob, ctx->backbuffer, bg_size);\n\n\twidgets_layout();\n\tupdate_window_list();\n}\n\nstatic void bind_keys(void) {\n\n\t/* Cltr-Alt-T = launch terminal */\n\tyutani_key_bind(yctx, 't', KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);\n\n\t/* Alt+Tab = app switcher*/\n\tyutani_key_bind(yctx, '\\t', KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);\n\tyutani_key_bind(yctx, '\\t', KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT, YUTANI_BIND_STEAL);\n\n\t/* Ctrl-F11 = toggle panel visibility */\n\tyutani_key_bind(yctx, KEY_F11, KEY_MOD_LEFT_CTRL, YUTANI_BIND_STEAL);\n\n\t/* Alt+F2 = show app runner */\n\tyutani_key_bind(yctx, KEY_F2, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);\n\n\t/* This lets us receive all just-modifier key releases */\n\tyutani_key_bind(yctx, KEY_LEFT_ALT, 0, YUTANI_BIND_PASSTHROUGH);\n\n}\n\nstatic void sig_usr2(int sig) {\n\tyutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP);\n\tyutani_flip(yctx, panel);\n\tbind_keys();\n\tsignal(SIGUSR2, sig_usr2);\n}\n\nstatic int mouse_event_ignore(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\treturn 0;\n}\n\nstatic int widget_enter_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tthis->highlighted = 1; /* Highlighted */\n\treturn 1;\n}\n\nstatic int widget_leave_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tthis->highlighted = 0; /* Not highlighted */\n\treturn 1;\n}\n\nvoid panel_highlight_widget(struct PanelWidget * this, gfx_context_t * ctx, int active) {\n\tif (this->highlighted || active) {\n\t\tdraw_rounded_rectangle(ctx, 3, 3, ctx->width - 6, ctx->height - 6, 11, premultiply(rgba(120,120,120,active ? 180 : 150)));\n\t}\n}\n\nstatic int widget_draw_generic(struct PanelWidget * this, gfx_context_t * ctx) {\n\tdraw_rounded_rectangle(\n\t\tctx, 0, 0, ctx->width, ctx->height, 7, premultiply(rgba(120,120,120,150)));\n\tif (this->highlighted) {\n\t\tdraw_rounded_rectangle(\n\t\t\tctx, 1, 1, ctx->width-2, ctx->height-2, 6, premultiply(rgba(120,160,230,220)));\n\t} else {\n\t\tdraw_rounded_rectangle(\n\t\t\tctx, 1, 1, ctx->width-2, ctx->height-2, 6, ALTTAB_BACKGROUND);\n\t}\n\treturn 0;\n}\n\nstatic int widget_update_generic(struct PanelWidget * this, int * force_updates) {\n\treturn 0;\n}\n\nstatic int widget_onkey_generic(struct PanelWidget * this, struct yutani_msg_key_event * evt) {\n\treturn 0;\n}\n\nstruct PanelWidget * widget_new(void) {\n\tstruct PanelWidget * out = calloc(1, sizeof(struct PanelWidget));\n\tout->pctx = &panel_context;\n\tout->draw = widget_draw_generic;\n\tout->click = mouse_event_ignore; /* click_generic */\n\tout->right_click = mouse_event_ignore; /* right_click_generic */\n\tout->leave = widget_leave_generic;\n\tout->enter = widget_enter_generic;\n\tout->update = widget_update_generic;\n\tout->onkey = widget_onkey_generic;\n\tout->move = mouse_event_ignore; /* move_generic */\n\tout->highlighted = 0;\n\tout->fill = 0;\n\treturn out;\n}\n\nstatic void widgets_layout(void) {\n\tint total_width = 0;\n\tint flexible_widgets = 0;\n\tforeach(node, widgets_enabled) {\n\t\tstruct PanelWidget * widget = node->value;\n\t\tif (widget->fill) {\n\t\t\tflexible_widgets++;\n\t\t} else {\n\t\t\ttotal_width += widget->width;\n\t\t}\n\t}\n\n\t/* Now lay out the widgets */\n\tint x = 0;\n\tint available = width;\n\tforeach(node, widgets_enabled) {\n\t\tstruct PanelWidget * widget = node->value;\n\t\twidget->left = x;\n\t\tif (widget->fill) {\n\t\t\twidget->width = (available - total_width) / flexible_widgets;\n\t\t}\n\t\tx += widget->width;\n\t}\n}\n\nstatic void update_periodic_widgets(int * force_updates) {\n\t*force_updates = 0;\n\tint needs_layout = 0;\n\tforeach(widget_node, widgets_enabled) {\n\t\tstruct PanelWidget * widget = widget_node->value;\n\t\tneeds_layout |= widget->update(widget, force_updates);\n\t}\n\tif (needs_layout) widgets_layout();\n\tredraw();\n}\n\nint panel_menu_show_at(struct MenuList * menu, int x) {\n\tint mwidth, mheight, offset;\n\n\t/* Calculate the expected size of the menu window. */\n\tmenu_calculate_dimensions(menu, &mheight, &mwidth);\n\n\tif (x - mwidth / 2 < MENU_PAD) {\n\t\toffset = MENU_PAD;\n\t\tmenu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_LEFT;\n\t} else if (x + mwidth / 2 > width - MENU_PAD) {\n\t\toffset = width - MENU_PAD - mwidth;\n\t\tmenu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_RIGHT;\n\t} else {\n\t\toffset = x - mwidth / 2;\n\t\tmenu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_CENTER;\n\t}\n\n\tmenu->flags |= MENU_FLAG_TAIL_POSITION;\n\tmenu->tail_offset = x - offset;\n\n\t/* Prepare the menu, which creates the window. */\n\tmenu_show_at(menu, panel, offset, DROPDOWN_OFFSET);\n\n\t/* If we succeeded, move it to the final offset and display it */\n\tif (menu->window) return 0;\n\treturn 1;\n}\n\nint panel_menu_show(struct PanelWidget * this, struct MenuList * menu) {\n\treturn panel_menu_show_at(menu, this->left + this->width / 2);\n}\n\nint main (int argc, char ** argv) {\n\tif (argc < 2 || strcmp(argv[1],\"--really\")) {\n\t\tfprintf(stderr,\n\t\t\t\t\"%s: Desktop environment panel / dock\\n\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\" Renders the application menu, window list, widgets,\\n\"\n\t\t\t\t\" alt-tab window switcher, clock, etc.\\n\"\n\t\t\t\t\" You probably don't want to run this directly - it is\\n\"\n\t\t\t\t\" started automatically by the session manager.\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* Connect to window server */\n\tyctx = yutani_init();\n\n\t/* Shared fonts */\n\tpanel_context.font           = tt_font_from_shm(\"sans-serif\");\n\tpanel_context.font_bold      = tt_font_from_shm(\"sans-serif.bold\");\n\tpanel_context.font_mono      = tt_font_from_shm(\"monospace\");\n\tpanel_context.font_mono_bold = tt_font_from_shm(\"monospace.bold\");\n\n\t/* For convenience, store the display size */\n\twidth  = yctx->display_width;\n\theight = yctx->display_height;\n\n\tpanel_context.color_text_normal    = rgb(230,230,230);\n\tpanel_context.color_text_hilighted = rgb(142,216,255);\n\tpanel_context.color_text_focused   = rgb(255,255,255);\n\tpanel_context.color_icon_normal    = rgb(230,230,230);\n\tpanel_context.color_special        = rgb(93,163,236);\n\tpanel_context.font_size_default    = 14;\n\tpanel_context.extra_widget_spacing = 12;\n\n\t/* Create the panel window */\n\tpanel = yutani_window_create_flags(yctx, width, PANEL_HEIGHT, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION);\n\tpanel_context.basewindow = panel;\n\n\t/* And move it to the top layer */\n\tyutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP);\n\tyutani_window_update_shape(yctx, panel, YUTANI_SHAPE_THRESHOLD_CLEAR);\n\n\t/* Initialize graphics context against the window */\n\tctx = init_graphics_yutani_double_buffer(panel);\n\n\t/* Draw the background */\n\tredraw_panel_background(ctx, panel->width, panel->height);\n\n\t/* Copy the prerendered background so we can redraw it quickly */\n\tbg_size = panel->width * panel->height * sizeof(uint32_t);\n\tbg_blob = malloc(bg_size);\n\tmemcpy(bg_blob, ctx->backbuffer, bg_size);\n\n\t/* Catch SIGINT */\n\tsignal(SIGINT, sig_int);\n\tsignal(SIGUSR2, sig_usr2);\n\n\twidgets_enabled = list_create();\n\n\t/* Initialize requested widgets */\n\tconst char * widgets_to_load[] = {\n\t\t\"appmenu\",\n\t\t\"windowlist\",\n\t\t\"volume\",\n\t\t\"network\",\n\t\t\"weather\",\n\t\t\"date\",\n\t\t\"clock\",\n\t\t\"logout\",\n\t\tNULL\n\t};\n\n\tfor (const char ** widget = widgets_to_load; *widget; widget++) {\n\t\tchar lib_name[200];\n\t\tchar func_name[200];\n\n\t\tsnprintf(lib_name, 200, \"libtoaru_panel_%s.so\", *widget);\n\t\tsnprintf(func_name, 200, \"widget_init_%s\", *widget);\n\n\t\tvoid * lib = dlopen(lib_name, 0);\n\t\tif (!lib) {\n\t\t\tfprintf(stderr, \"panel: failed to load widget '%s'\\n\", *widget);\n\t\t} else {\n\t\t\tvoid (*widget_init)(void) = dlsym(lib, func_name);\n\t\t\tif (!widget_init) {\n\t\t\t\tfprintf(stderr, \"panel: failed to initialize widget '%s'\\n\", *widget);\n\t\t\t} else {\n\t\t\t\twidget_init();\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Lay out the widgets */\n\tint force_updates = 0;\n\tupdate_periodic_widgets(&force_updates);\n\twidgets_layout();\n\n\t/* Subscribe to window updates */\n\tyutani_subscribe_windows(yctx);\n\n\t/* Ask compositor for window list */\n\tupdate_window_list();\n\n\t/* Key bindings */\n\tbind_keys();\n\n\ttime_t last_tick = 0;\n\n\tint fds[1] = {fileno(yctx->sock)};\n\n\tfprintf(stderr, \"entering loop?\\n\");\n\n\twhile (_continue) {\n\n\t\tint index = fswait2(1,fds,force_updates ? 50 : 200); /* ~20 fps? */\n\n\t\tif (index == 0) {\n\t\t\t/* Respond to Yutani events */\n\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\twhile (m) {\n\t\t\t\tmenu_process_event(yctx, m);\n\t\t\t\tswitch (m->type) {\n\t\t\t\t\t/* New window information is available */\n\t\t\t\t\tcase YUTANI_MSG_NOTIFY:\n\t\t\t\t\t\tupdate_window_list();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t/* Mouse movement / click */\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\tpanel_check_click((struct yutani_msg_window_mouse_event *)m->data);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\thandle_key_event((struct yutani_msg_key_event *)m->data);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WELCOME:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_welcome * mw = (void*)m->data;\n\t\t\t\t\t\t\twidth = mw->display_width;\n\t\t\t\t\t\t\theight = mw->display_height;\n\t\t\t\t\t\t\tyutani_window_resize(yctx, panel, mw->display_width, PANEL_HEIGHT);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\t\tif (wr->wid == panel->wid) {\n\t\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t\t} else if (alttab && wr->wid == alttab->wid) {\n\t\t\t\t\t\t\t\tyutani_window_resize_accept(yctx, alttab, wr->width, wr->height);\n\t\t\t\t\t\t\t\treinit_graphics_yutani(actx, alttab);\n\t\t\t\t\t\t\t\tredraw_alttab();\n\t\t\t\t\t\t\t\tyutani_window_resize_done(yctx, alttab);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t}\n\t\t}\n\n\t\tstruct timeval now;\n\t\tgettimeofday(&now, NULL);\n\t\tif (now.tv_sec != last_tick || force_updates) {\n\t\t\tlast_tick = now.tv_sec;\n\t\t\twaitpid(-1, NULL, WNOHANG);\n\t\t\tif (was_tabbing) {\n\t\t\t\tredraw_alttab();\n\t\t\t}\n\t\t\tupdate_periodic_widgets(&force_updates);\n\t\t}\n\t}\n\n\t/* Close the panel window */\n\tyutani_close(yctx, panel);\n\n\t/* Stop notifying us of window changes */\n\tyutani_unsubscribe_windows(yctx);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/path_demo.krk",
    "content": "#!/bin/kuroko\nfrom _yutani2 import (YutaniCtx, Font, rgb, MenuBar, decor_get_bounds, decor_render,\n                      MenuList, MenuEntry, decor_handle_event, decor_show_default_menu,\n                      Sprite, TTContour, TransformMatrix, MenuEntrySubmenu, TTShape,\n                      MenuEntryToggle)\n\nfrom yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep\nimport math\nimport random\n\nlet mainloop = AsyncMainloop()\n\nlet texture_star = Sprite(\"/usr/share/icons/48/star.png\")\nlet texture_flowers = Sprite('/usr/share/wallpaper.jpg')\n\nlet tt_texture = texture_star\n\ndef scale_by(tm,val):\n    tm.scale(val/1000,val/1000)\n\ndef rotate_by(tm,val):\n    tm.rotate(val/1000)\n\ndef shear_x_by(tm,val):\n    tm.shear(val/1000,0.0)\n\ndef shear_y_by(tm,val):\n    tm.shear(0.0,val/1000)\n\nlet x_thing = scale_by\nlet y_thing = rotate_by\n\ndef draw_text():\n    let font = Font('sans-serif.bold',128)\n    let Rect, w = font.prepare_string(0,128,\"ToaruOS\")\n    return Rect\n\nlet cursor = 0,0\ndef draw_cursor_position():\n    let font = Font('sans-serif.bold',128)\n    let Rect, w = font.prepare_string(0,128,f'{cursor[0]},{cursor[1]}')\n    return Rect\n\ndef draw_box():\n    let Rect = TTContour(20,20)\n    Rect.line_to(520,20)\n    Rect.line_to(520,520)\n    Rect.line_to(20,520)\n    return Rect\n\nlet base_graphic = draw_text\n\nlet tt_filter = TTShape.TT_PATH_FILTER_BILINEAR\nlet tt_wrap = TTShape.TT_PATH_WRAP_REPEAT\n\nlet warp_shape = False\n\ndef paint_demo(ctx, cursor):\n    let tm = TransformMatrix()\n    let x, y = cursor\n    x_thing(tm,x)\n    y_thing(tm,y)\n    let Rect = base_graphic()\n    if warp_shape:\n        Rect.transform(tm)\n    let SRect = Rect.finish()\n    Rect.free()\n    SRect.paint_sprite(ctx,tt_texture,tm,tt_filter,tt_wrap)\n    SRect.free()\n\ndef set_menu(menu, action):\n    for sibling in menu.group:\n        sibling.state = False\n    menu.state = True\n    action()\n\ndef check_list(entries, default=0):\n    let ml = MenuList()\n    let group = []\n    for i, e in enumerate(entries):\n        let name, action = e\n        let me = MenuEntryToggle(name, lambda menu: set_menu(menu, action), i == default)\n        me.group = group\n        group.append(me)\n        ml.insert(me)\n    return ml\n\nclass MyWindow(Window):\n    def __init__(self):\n        super().__init__(640, 480, title=\"Path Demo\", doublebuffer=True)\n        self.bgc = rgb(255,255,255)\n        self.mb = MenuBar(((\"File\",'file'),(\"View\",'view'),(\"Help\",'help')))\n        let _menu_File = MenuList()\n        _menu_File.insert(MenuEntry(\"Quit\", lambda menu: self.close()))\n        self.mb.insert('file', _menu_File)\n        let _menu_View = MenuList()\n\n        let _menu_View_Horizontal = check_list((\n            (\"Scale\",   lambda: x_thing = scale_by),\n            (\"Rotate\",  lambda: x_thing = rotate_by),\n            (\"Shear X\", lambda: x_thing = shear_x_by),\n            (\"Shear Y\", lambda: x_thing = shear_y_by),\n        ))\n        self.mb.insert('view-horizontal', _menu_View_Horizontal)\n        _menu_View.insert(MenuEntrySubmenu(\"Horizontal\",\"view-horizontal\"))\n    \n        let _menu_View_Vertical = check_list((\n            (\"Scale\",   lambda: y_thing = scale_by),\n            (\"Rotate\",  lambda: y_thing = rotate_by),\n            (\"Shear X\", lambda: y_thing = shear_x_by),\n            (\"Shear Y\", lambda: y_thing = shear_y_by),\n        ), default=1)\n        self.mb.insert('view-vertical', _menu_View_Vertical)\n        _menu_View.insert(MenuEntrySubmenu(\"Vertical\",\"view-vertical\"))\n\n        let _menu_View_Filter = check_list((\n            (\"Bilinear\", lambda: tt_filter = TTShape.TT_PATH_FILTER_BILINEAR),\n            (\"Nearest\",  lambda: tt_filter = TTShape.TT_PATH_FILTER_NEAREST)\n        ))\n        self.mb.insert('view-filter', _menu_View_Filter)\n        _menu_View.insert(MenuEntrySubmenu(\"Filter\",\"view-filter\"))\n\n        let _menu_View_Wrap = check_list((\n            ('Repeat', lambda: tt_wrap = TTShape.TT_PATH_WRAP_REPEAT),\n            ('None',   lambda: tt_wrap = TTShape.TT_PATH_WRAP_NONE),\n            ('Pad',    lambda: tt_wrap = TTShape.TT_PATH_WRAP_PAD)\n        ))\n        self.mb.insert('view-wrap', _menu_View_Wrap)\n        _menu_View.insert(MenuEntrySubmenu(\"Wrap\",'view-wrap'))\n\n        let _menu_View_Texture = check_list((\n            ('Star',    lambda: tt_texture = texture_star),\n            ('Flowers', lambda: tt_texture = texture_flowers),\n        ))\n        self.mb.insert('view-texture', _menu_View_Texture)\n        _menu_View.insert(MenuEntrySubmenu('Texture','view-texture'))\n\n        let _menu_View_Shape = check_list((\n            ('Text', lambda: base_graphic = draw_text),\n            ('Cursor', lambda: base_graphic = draw_cursor_position),\n            ('Box',  lambda: base_graphic = draw_box),\n        ))\n        self.mb.insert('view-shape', _menu_View_Shape)\n        _menu_View.insert(MenuEntrySubmenu('Shape','view-shape'))\n\n        let _menu_View_Warp_Shape = check_list((\n            ('No',  lambda: warp_shape = False),\n            ('Yes', lambda: warp_shape = True),\n        ))\n        self.mb.insert('view-warp-shape', _menu_View_Warp_Shape)\n        _menu_View.insert(MenuEntrySubmenu('Warp shape','view-warp-shape'))\n\n        self.mb.insert('view', _menu_View)\n\n        let _menu_Help = MenuList()\n        let _menu_Help_help = MenuEntry(\"Help\",lambda menu: print(\"Should open some help here I guess\"))\n        _menu_Help.insert(_menu_Help_help)\n        self.mb.insert('help', _menu_Help)\n        self.mb.callback = lambda x: self.pending_updates = True\n        cursor = 0,0\n        self.redrawer = Task(self.redraw_loop())\n        self.pending_updates = True\n\n    async def redraw_loop(self):\n        while not self.closed:\n            if self.pending_updates:\n                self.draw()\n                self.pending_updates = False\n            await sleep(0.016)\n\n    def draw(self):\n        self.fill(self.bgc)\n        decor_render(self)\n        let bounds = decor_get_bounds(self)\n        self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self)\n        self.mb.render(self)\n        self.mb.height = 24 # Compatibility\n        let sprite = Sprite(width=self.width-bounds['width'],height=self.height-bounds['height']-self.mb.height)\n        sprite.fill(rgb(255,255,255))\n        paint_demo(sprite,cursor)\n        self.draw_sprite(sprite,bounds['left_width'],bounds['top_height']+self.mb.height)\n        sprite.free()\n        self.flip()\n\n    def mouse_event(self, msg):\n        let decResponse = decor_handle_event(msg)\n        if decResponse == 2:\n            self.close()\n            return True\n        else if decResponse == 5:\n            decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y)\n        self.mb.mouse_event(self, msg)\n        let nc = msg.new_x, msg.new_y\n        if cursor != nc:\n            cursor = nc\n            self.pending_updates = True\n\n    def keyboard_event(self, msg):\n        if msg.keycode == 113 and msg.action == 1:\n            self.close()\n\n    def close(self):\n        super().close()\n        mainloop.exit()\n\n    def window_moved(self, msg):\n        self.pending_updates = True\n\n    def menu_close(self):\n        self.pending_updates = True\n\n    def finish_resize(self, msg):\n        if msg.width < 100 or msg.height < 100:\n            self.resize_offer(100 if msg.width < 100 else msg.width, 100 if msg.height < 100 else msg.height)\n            return\n        super().finish_resize(msg)\n\nmainloop.activate()\n\nlet w = MyWindow()\nw.move(200,200)\nw.draw()\n\nmainloop.menu_closed_callback = w.menu_close\nmainloop.run()\n"
  },
  {
    "path": "apps/piano.c",
    "content": "/**\n * @brief piano - Interactively make beeping noises\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <termios.h>\n\nint spkr = 0;\n\nstruct spkr {\n\tint length;\n\tint frequency;\n};\n\nvoid note(int length, int frequency) {\n\tstruct spkr s = {\n\t\t.length = length,\n\t\t.frequency = frequency,\n\t};\n\n\twrite(spkr, &s, sizeof(s));\n}\n\nstruct termios old;\n\nvoid set_unbuffered() {\n\ttcgetattr(fileno(stdin), &old);\n\tstruct termios new = old;\n\tnew.c_lflag &= (~ICANON & ~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\n\nint main(int argc, char * argv[]) {\n\n\tspkr = open(\"/dev/spkr\", O_WRONLY);\n\tif (spkr == -1) {\n\t\tfprintf(stderr, \"%s: could not open speaker\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tset_unbuffered();\n\n\tchar c;\n\twhile ((c = fgetc(stdin))) {\n\t\tswitch (c) {\n\t\t\tcase 'q': note(0, 1000); return 0;\n\t\t\tcase 'z': note(0, 1000); return 0;\n\t\t\tcase ' ': note(0, 1000); break;\n\t\t\tcase 'a': note(-1, 1308); break;\n\t\t\tcase 'w': note(-1, 1386); break;\n\t\t\tcase 's': note(-1, 1468); break;\n\t\t\tcase 'e': note(-1, 1556); break;\n\t\t\tcase 'd': note(-1, 1648); break;\n\t\t\tcase 'f': note(-1, 1746); break;\n\t\t\tcase 't': note(-1, 1850); break;\n\t\t\tcase 'g': note(-1, 1960); break;\n\t\t\tcase 'y': note(-1, 2077); break;\n\t\t\tcase 'h': note(-1, 2200); break;\n\t\t\tcase 'u': note(-1, 2331); break;\n\t\t\tcase 'j': note(-1, 2469); break;\n\t\t\tcase 'k': note(-1, 2616); break;\n\t\t\tcase 'o': note(-1, 2772); break;\n\t\t\tcase 'l': note(-1, 2937); break;\n\t\t\tcase 'p': note(-1, 3111); break;\n\t\t\tcase ';': note(-1, 3296); break;\n\t\t\tcase '\\'': note(-1, 3492);break;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/pidof.c",
    "content": "/**\n * @brief Look up the PIDs of all processes with a particular name\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n\ntypedef struct process {\n\tint pid;\n\tint ppid;\n\tint tgid;\n\tchar name[100];\n\tchar path[200];\n} p_t;\n\n#define LINE_LEN 4096\n\np_t * build_entry(struct dirent * dent) {\n\tchar tmp[300];\n\tFILE * f;\n\tchar line[LINE_LEN];\n\n\tsprintf(tmp, \"/proc/%s/status\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\n\tp_t * proc = malloc(sizeof(p_t));\n\n\twhile (fgets(line, LINE_LEN, f) != NULL) {\n\t\tchar * n = strstr(line,\"\\n\");\n\t\tif (n) { *n = '\\0'; }\n\t\tchar * tab = strstr(line,\"\\t\");\n\t\tif (tab) {\n\t\t\t*tab = '\\0';\n\t\t\ttab++;\n\t\t}\n\t\tif (strstr(line, \"Pid:\") == line) {\n\t\t\tproc->pid = atoi(tab);\n\t\t} else if (strstr(line, \"PPid:\") == line) {\n\t\t\tproc->ppid = atoi(tab);\n\t\t} else if (strstr(line, \"Tgid:\") == line) {\n\t\t\tproc->tgid = atoi(tab);\n\t\t} else if (strstr(line, \"Name:\") == line) {\n\t\t\tstrcpy(proc->name, tab);\n\t\t} else if (strstr(line, \"Path:\") == line) {\n\t\t\tstrcpy(proc->path, tab);\n\t\t}\n\t}\n\n\tif (strstr(proc->name,\"python\") == proc->name) {\n\t\tchar * name = proc->path + strlen(proc->path) - 1;\n\n\t\twhile (1) {\n\t\t\tif (*name == '/') {\n\t\t\t\tname++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (name == proc->name) break;\n\t\t\tname--;\n\t\t}\n\n\t\tmemcpy(proc->name, name, strlen(name)+1);\n\t}\n\n\tif (proc->tgid != proc->pid) {\n\t\tchar tmp[100] = {0};\n\t\tsprintf(tmp, \"{%s}\", proc->name);\n\t\tmemcpy(proc->name, tmp, strlen(tmp)+1);\n\t}\n\n\tfclose(f);\n\n\treturn proc;\n}\n\nint main (int argc, char * argv[]) {\n\n\tif (argc < 2) return 1;\n\n\tint space = 0;\n\n\t/* Open the directory */\n\tDIR * dirp = opendir(\"/proc\");\n\n\tint found_something = 0;\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {\n\t\t\tp_t * proc = build_entry(ent);\n\n\t\t\tif (!strcmp(proc->name, argv[optind])) {\n\t\t\t\tif (space++) printf(\" \");\n\t\t\t\tprintf(\"%d\", proc->pid);\n\t\t\t\tfound_something = 1;\n\t\t\t}\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tif (!found_something) {\n\t\treturn 1;\n\t}\n\tprintf(\"\\n\");\n\treturn 0;\n}\n\n\n"
  },
  {
    "path": "apps/ping.c",
    "content": "/**\n * @brief Send ICMP pings\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <getopt.h>\n#include <time.h>\n#include <termios.h>\n#include <unistd.h>\n#include <poll.h>\n#include <signal.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n\n#define BYTES_TO_SEND 64\n\nstruct ICMP_Header {\n\tuint8_t type, code;\n\tuint16_t checksum;\n\tuint16_t identifier;\n\tuint16_t sequence_number;\n\tuint8_t payload[];\n};\n\nstatic unsigned long clocktime(void) {\n\tstruct timeval tv;\n\tgettimeofday(&tv,NULL);\n\treturn tv.tv_sec * 1000000 + tv.tv_usec;\n}\n\nstatic uint16_t icmp_checksum(char * payload, size_t len) {\n\tuint32_t sum = 0;\n\tuint16_t * s = (uint16_t *)payload;\n\tfor (size_t i = 0; i < (len) / 2; ++i) {\n\t\tsum += ntohs(s[i]);\n\t}\n\tif (sum > 0xFFFF) {\n\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t}\n\treturn ~(sum & 0xFFFF) & 0xFFFF;\n}\n\nstatic int break_from_loop = 0;\n\nstatic void sig_break_loop(int sig) {\n\t(void)sig;\n\tbreak_from_loop = 1;\n}\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) return 1;\n\n\tint pings_sent = 0;\n\n\tstruct hostent * host = gethostbyname(argv[1]);\n\n\tif (!host) {\n\t\tfprintf(stderr, \"%s: not found\\n\", argv[1]);\n\t\treturn 1;\n\t}\n\n\tchar * addr = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]);\n\n\tint sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);\n\n\tif (sock < 0) {\n\t\tfprintf(stderr, \"%s: No socket: %s\\n\", argv[1], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tint yes = 1;\n\tsetsockopt(sock, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(int));\n\n\tsignal(SIGINT, sig_break_loop);\n\n\tstruct sockaddr_in dest;\n\tdest.sin_family = AF_INET;\n\tmemcpy(&dest.sin_addr.s_addr, host->h_addr, host->h_length);\n\n\tprintf(\"PING %s (%s) %d data bytes\\n\", argv[1], addr, BYTES_TO_SEND - 8);\n\n\tstruct ICMP_Header * ping = malloc(BYTES_TO_SEND);\n\tping->type = 8; /* request */\n\tping->code = 0;\n\tping->identifier = 0;\n\tping->sequence_number = 0;\n\t/* Fill in data */\n\tfor (int i = 0; i < BYTES_TO_SEND - 8; ++i) {\n\t\tping->payload[i] = i;\n\t}\n\n\tint responses_received = 0;\n\n\twhile (!break_from_loop) {\n\t\tping->sequence_number = htons(pings_sent+1);\n\t\tping->checksum = 0;\n\t\tping->checksum = htons(icmp_checksum((void*)ping, BYTES_TO_SEND));\n\n\t\t/* Send it and wait */\n\t\tunsigned long sent_at = clocktime();\n\t\tif (sendto(sock, (void*)ping, BYTES_TO_SEND, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) {\n\t\t\tfprintf(stderr, \"sendto: %s\\n\", strerror(errno));\n\t\t}\n\n\t\tpings_sent++;\n\n\t\tstruct pollfd fds[1];\n\t\tfds[0].fd = sock;\n\t\tfds[0].events = POLLIN;\n\t\tint ret = poll(fds,1,1000);\n\n\t\tif (ret > 0) {\n\t\t\tchar data[4096];\n\t\t\tchar control[4096];\n\t\t\tstruct sockaddr_in source;\n\t\t\tsocklen_t source_size = sizeof(struct sockaddr_in);\n\t\t\tstruct iovec _iovec = {\n\t\t\t\tdata, 4096\n\t\t\t};\n\t\t\tstruct msghdr msg = {\n\t\t\t\t&source,\n\t\t\t\tsource_size,\n\t\t\t\t&_iovec,\n\t\t\t\t1,\n\t\t\t\tcontrol,\n\t\t\t\t4096,\n\t\t\t\t0\n\t\t\t};\n\t\t\tssize_t len = recvmsg(sock, &msg, 0);\n\t\t\tunsigned long rcvd_at = clocktime();\n\t\t\tif (len > 0) {\n\t\t\t\t/* Is it actually a PING response ? */\n\n\t\t\t\tstruct ICMP_Header * icmp = (void*)data;\n\t\t\t\tunsigned char ttl = 0;\n\n\t\t\t\tif (msg.msg_controllen) {\n\t\t\t\t\tchar * control_msg = control;\n\t\t\t\t\twhile (control_msg - control + sizeof(struct cmsghdr) <= msg.msg_controllen) {\n\t\t\t\t\t\tstruct cmsghdr * cmsg = (void*)control_msg;\n\t\t\t\t\t\tif (cmsg->cmsg_level == IPPROTO_IP && (cmsg->cmsg_type == IP_RECVTTL || cmsg->cmsg_type == IP_TTL)) {\n\t\t\t\t\t\t\tmemcpy(&ttl, CMSG_DATA(cmsg), 1);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontrol_msg += cmsg->cmsg_len;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (icmp->type == 0) {\n\t\t\t\t\t/* How much data, minus the header? */\n\t\t\t\t\t/* Get the address */\n\t\t\t\t\tchar * from = inet_ntoa(source.sin_addr);\n\t\t\t\t\tint time_taken = (rcvd_at - sent_at);\n\t\t\t\t\tprintf(\"%zd bytes from %s: icmp_seq=%d ttl=%d time=%d\",\n\t\t\t\t\t\tlen, from, ntohs(icmp->sequence_number), (unsigned char)ttl,\n\t\t\t\t\t\ttime_taken / 1000);\n\t\t\t\t\tif (time_taken < 1000) {\n\t\t\t\t\t\tprintf(\".%03d\", time_taken % 1000);\n\t\t\t\t\t} else if (time_taken < 10000) {\n\t\t\t\t\t\tprintf(\".%02d\", (time_taken / 10) % 100);\n\t\t\t\t\t} else if (time_taken < 100000) {\n\t\t\t\t\t\tprintf(\".%01d\", (time_taken / 100) % 10);\n\t\t\t\t\t}\n\t\t\t\t\tprintf(\" ms\\n\");\n\t\t\t\t\tresponses_received++;\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\n\t\tif (!break_from_loop) {\n\t\t\tsleep(1);\n\t\t}\n\t}\n\n\tprintf(\"--- %s statistics ---\\n\", argv[1]);\n\tprintf(\"%d packets transmitted, %d received, %d%% packet loss\\n\",\n\t\tpings_sent, responses_received, 100*(pings_sent-responses_received)/pings_sent);\n\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/plasma.c",
    "content": "/**\n * @brief Threaded graphical demo that draws animated plasma.\n *\n * Good for burning CPU.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <math.h>\n#include <wait.h>\n#include <pthread.h>\n#include <sched.h>\n#include <signal.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/spinlock.h>\n#include <toaru/menu.h>\n\n#define dist(a,b,c,d) sqrt((double)(((a) - (c)) * ((a) - (c)) + ((b) - (d)) * ((b) - (d))))\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic int should_exit = 0;\n\nuint16_t win_width;\nuint16_t win_height;\n\nuint16_t off_x;\nuint16_t off_y;\n\nstatic int volatile draw_lock = 0;\n\ngfx_context_t * ctx;\npthread_t thread;\n\nvoid sigint_handler() {\n\tshould_exit = 1;\n\tpthread_join(thread, NULL);\n\texit(1);\n}\n\nvoid redraw_borders() {\n\trender_decorations(wina, ctx, \"Plasma\");\n}\n\nuint32_t hsv_to_rgb(int h, float s, float v) {\n\tfloat c  = v * s;\n\tfloat hp = (float)h / 42.6666666f;\n\tfloat x = c * (1.0 - fabs(fmod(hp, 2) - 1.0));\n\tfloat m = v - c;\n\tfloat rp, gp, bp;\n\tif (hp < 1.0)      { rp = c; gp = x; bp = 0; }\n\telse if (hp < 2.0) { rp = x; gp = c; bp = 0; }\n\telse if (hp < 3.0) { rp = 0; gp = c; bp = x; }\n\telse if (hp < 4.0) { rp = 0; gp = x; bp = c; }\n\telse if (hp < 5.0) { rp = x; gp = 0; bp = c; }\n\telse if (hp < 6.0) { rp = c; gp = 0; bp = x; }\n\telse               { rp = 0; gp = 0; bp = 0; }\n\treturn rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255);\n}\n\nvoid * draw_thread(void * garbage) {\n\t(void)garbage;\n\n\tdouble time = 0;\n\n\t/* Generate a palette */\n\tuint32_t palette[256];\n\tfor (int x = 0; x < 256; ++x) {\n\t\tpalette[x] = hsv_to_rgb(x,1.0,1.0);\n\t}\n\n\twhile (!should_exit) {\n\n\t\ttime += 1.0;\n\n\t\tspin_lock(&draw_lock);\n\t\tfor (int x = 0; x < win_width; ++x) {\n\t\t\tfor (int y = 0; y < win_height; ++y) {\n\t\t\t\tdouble value = sin(dist(x + time, y, 128.0, 128.0) / 8.0)\n\t\t\t\t\t+ sin(dist(x, y, 64.0, 64.0) / 8.0)\n\t\t\t\t\t+ sin(dist(x, y + time / 7, 192.0, 64) / 7.0)\n\t\t\t\t\t+ sin(dist(x, y, 192.0, 100.0) / 8.0);\n\t\t\t\tGFX(ctx, x + off_x, y + off_y) = palette[(unsigned int)((value + 4) * 32) & 0xFF];\n\t\t\t}\n\t\t}\n\t\tredraw_borders();\n\t\tflip(ctx);\n\t\tyutani_flip(yctx, wina);\n\t\tspin_unlock(&draw_lock);\n\t\tsched_yield();\n\t}\n\treturn NULL;\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, wina, w, h);\n\treinit_graphics_yutani(ctx, wina);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(wina, &bounds);\n\n\twin_width  = w - bounds.width;\n\twin_height = h - bounds.height;\n\toff_x = bounds.left_width;\n\toff_y = bounds.top_height;\n\n\tyutani_window_resize_done(yctx, wina);\n}\n\nint main (int argc, char ** argv) {\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\twin_width  = 300;\n\twin_height = 300;\n\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\t/* Do something with a window */\n\twina = yutani_window_create(yctx, win_width + bounds.width, win_height + bounds.height);\n\tyutani_window_move(yctx, wina, 300, 300);\n\n\tdecor_get_bounds(wina, &bounds);\n\toff_x = bounds.left_width;\n\toff_y = bounds.top_height;\n\twin_width  = wina->width - bounds.width;\n\twin_height = wina->height - bounds.height;\n\n\tctx = init_graphics_yutani_double_buffer(wina);\n\n\tdraw_fill(ctx, rgb(0,0,0));\n\tredraw_borders();\n\tflip(ctx);\n\tyutani_flip(yctx, wina);\n\n\tyutani_window_advertise_icon(yctx, wina, \"Plasma\", \"plasma\");\n\n\tpthread_create(&thread, NULL, draw_thread, NULL);\n\n\tsignal(SIGINT, sigint_handler);\n\twhile (!should_exit) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tmenu_process_event(yctx, m);\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win && win == wina) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == wina->wid) {\n\t\t\t\t\t\t\tspin_lock(&draw_lock);\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t\tspin_unlock(&draw_lock);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tswitch (decor_handle_event(yctx, m)) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\tdecor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\twait(NULL);\n\n\tyutani_close(yctx, wina);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/play.c",
    "content": "/**\n * @brief play - Play back PCM samples\n *\n * This needs very specifically-formatted PCM data to function\n * properly - 16-bit, signed, stereo, little endian, and 48KHz.\n *\n * TODO This should be fixed up to play back WAV files properly.\n *      We have a sample rate convert in the out-of-repo playmp3\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <errno.h>\n\n#include <sys/ioctl.h>\n\n#define DSP_PATH \"/dev/dsp\"\n\nstatic int usage(char * argv[]) {\n\tfprintf(stderr, \"usage: %s [-d dsp_path] /path/to/48ks16le.wav\\n\", argv[0]);\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tchar buf[0x1000];\n\tint spkr, song;\n\tssize_t r;\n\tint opt;\n\tchar * dsp_path = DSP_PATH;\n\n\twhile ((opt = getopt(argc, argv, \"d:s:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'd': /* DSP path */\n\t\t\t\tdsp_path = optarg;\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\treturn usage(argv);\n\t\t}\n\t}\n\n\tif (optind == argc) return usage(argv);\n\n\tspkr = open(dsp_path, O_WRONLY);\n\tif (spkr < 0) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], dsp_path, strerror(errno));\n\t\treturn 1;\n\t}\n\n\tif (!strcmp(argv[optind], \"-\")) {\n\t\tsong = STDIN_FILENO;\n\t} else {\n\t\tsong = open(argv[optind], O_RDONLY);\n\t\tif (song < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\twhile ((r = read(song, buf, sizeof(buf))) > 0) {\n\t\tif (write(spkr, buf, r) < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], dsp_path, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (r < 0) {\n\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/polygons.c",
    "content": "/**\n * @brief Draw filled polygons from line segments.\n *\n * This is an older version of the polygon rasterizer that turned\n * into the TrueType gylph rasterizer. Still makes for a neat\n * little graphical demo. Should probably be updated to use\n * the glyph rasterization code instead of its own oudated\n * copy though...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n\n#include <stdio.h>\n\n#include <sys/fswait.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\n#define min(a,b) ((a) < (b) ? (a) : (b))\n\nstatic int left, top, width, height;\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx;\nstatic int should_exit = 0;\n\nstruct coord {\n\tfloat x;\n\tfloat y;\n};\n\nstruct edge {\n\tstruct coord start;\n\tstruct coord end;\n\tint direction;\n};\n\nstruct contour {\n\tsize_t edgeCount;\n\tsize_t nextAlloc;\n\tsize_t flags;\n\tsize_t last_start;\n\tstruct edge edges[];\n};\n\nstruct intersection {\n\tfloat x;\n\tint affect;\n};\n\nstruct shape {\n\tsize_t edgeCount;\n\tint lastY;\n\tstruct edge edges[];\n};\n\nstatic int edge_sorter_high_scanline(const void * a, const void * b) {\n\tconst struct edge * left  = a;\n\tconst struct edge * right = b;\n\n\tif (left->start.y < right->start.y) return -1;\n\tif (left->start.y > right->start.y) return 1;\n\treturn 0;\n}\n\nstatic void sort_edges(size_t edgeCount, struct edge edges[edgeCount]) {\n\tqsort(edges, edgeCount, sizeof(struct edge), edge_sorter_high_scanline);\n}\n\nstatic int intersection_sorter(const void * a, const void * b) {\n\tconst struct intersection * left  = a;\n\tconst struct intersection * right = b;\n\n\tif (left->x < right->x) return -1;\n\tif (left->x > right->x) return 1;\n\treturn 0;\n}\n\nstatic void sort_intersections(size_t cnt, struct intersection intersections[cnt]) {\n\tqsort(intersections, cnt, sizeof(struct intersection), intersection_sorter);\n}\n\nstatic size_t prune_edges(size_t edgeCount, float y, struct edge edges[edgeCount], struct edge into[edgeCount]) {\n\tsize_t outWriter = 0;\n\tfor (size_t i = 0; i < edgeCount; ++i) {\n\t\tif (y > edges[i].start.y && y > edges[i].end.y) continue;\n\t\tif (y <= edges[i].start.y && y <= edges[i].end.y) break;\n\t\tinto[outWriter++] = edges[i];\n\t}\n\treturn outWriter;\n}\n\nstatic float edge_at(float y, struct edge * edge) {\n\tfloat u = (y - edge->start.y) / (edge->end.y - edge->start.y);\n\treturn edge->start.x + u * (edge->end.x - edge->start.x);\n}\n\nstruct shape * path_finish(struct contour * in) {\n\tsize_t size = in->edgeCount + 1;\n\tstruct shape * tmp = malloc(sizeof(struct shape) + sizeof(struct edge) * size);\n\tmemcpy(tmp->edges, in->edges, sizeof(struct edge) * in->edgeCount);\n\n\tif (in->flags & 1) {\n\t\tsize--;\n\t} else {\n\t\ttmp->edges[in->edgeCount].start.x = in->edges[in->edgeCount-1].end.x;\n\t\ttmp->edges[in->edgeCount].start.y = in->edges[in->edgeCount-1].end.y;\n\t\ttmp->edges[in->edgeCount].end.x   = in->edges[in->last_start].start.x;\n\t\ttmp->edges[in->edgeCount].end.y   = in->edges[in->last_start].start.y;\n\t}\n\n\tfor (size_t i = 0; i < size; ++i) {\n\t\tif (tmp->edges[i].start.y < tmp->edges[i].end.y) {\n\t\t\ttmp->edges[i].direction = 1;\n\t\t} else {\n\t\t\ttmp->edges[i].direction = -1;\n\t\t\tstruct coord j = tmp->edges[i].start;\n\t\t\ttmp->edges[i].start = tmp->edges[i].end;\n\t\t\ttmp->edges[i].end = j;\n\t\t}\n\t}\n\n\tsort_edges(size, tmp->edges);\n\ttmp->edgeCount = size;\n\ttmp->lastY = 0;\n\tfor (size_t i = 0; i < size; ++i) {\n\t\tif (tmp->edges[i].end.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].end.y + 1;\n\t}\n\n\treturn tmp;\n}\n\nvoid path_paint(gfx_context_t * ctx, struct shape * shape, uint32_t color) {\n\tsize_t size = shape->edgeCount;\n\tstruct edge * intersects = malloc(sizeof(struct edge) * size);\n\tstruct intersection * crosses = malloc(sizeof(struct intersection) * size);\n\tfloat * subsamples = malloc(sizeof(float) * width);\n\tmemset(subsamples, 0, sizeof(float) * width);\n\n\t/* We have sorted by the scanline at which the line becomes active, so we should be able to do this... */\n\tint yres = 4;\n\tfor (int y = shape->edges[0].start.y; y < shape->lastY; ++y) {\n\t\t/* Figure out which ones fit here */\n\t\tfloat _y = y;\n\t\tint start_x = ctx->width;\n\t\tint max_x = 0;\n\t\tfor (int l = 0; l < yres; ++l) {\n\t\t\tsize_t cnt = prune_edges(size, _y, shape->edges, intersects);\n\t\t\tif (cnt) {\n\t\t\t\t/* Get intersections */\n\t\t\t\tfor (size_t j = 0; j < cnt; ++j) {\n\t\t\t\t\tcrosses[j].x = edge_at(_y,&intersects[j]);\n\t\t\t\t\tcrosses[j].affect = intersects[j].direction;\n\t\t\t\t}\n\n\t\t\t\t/* Now sort the intersections */\n\t\t\t\tsort_intersections(cnt, crosses);\n\n\t\t\t\tif (crosses[0].x < start_x) start_x = crosses[0].x;\n\t\t\t\tif (crosses[cnt-1].x+1 > max_x) max_x = crosses[cnt-1].x+1;\n\n\t\t\t\tint wind = 0;\n\t\t\t\tsize_t j = 0;\n\t\t\t\tfor (int x = 0; x < width && j < cnt; ++x) {\n\t\t\t\t\twhile (j < cnt && x > crosses[j].x) {\n\t\t\t\t\t\twind += crosses[j].affect;\n\t\t\t\t\t\tj++;\n\t\t\t\t\t}\n\t\t\t\t\tfloat last = x;\n\t\t\t\t\twhile (j < cnt && (x+1) > crosses[j].x) {\n\t\t\t\t\t\tif (wind != 0) {\n\t\t\t\t\t\t\tsubsamples[x] += crosses[j].x - last;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlast = crosses[j].x;\n\t\t\t\t\t\twind += crosses[j].affect;\n\t\t\t\t\t\tj++;\n\t\t\t\t\t}\n\t\t\t\t\tif (wind != 0) {\n\t\t\t\t\t\tsubsamples[x] += (x+1) - last;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t_y += 1.0/(float)yres;\n\t\t}\n\t\tfor (int x = start_x; x < max_x && x < ctx->width; ++x) {\n\t\t\tunsigned int c = subsamples[x] / (float)yres * (float)_ALP(color);\n\t\t\tuint32_t nc = premultiply((color & 0xFFFFFF) | ((c & 0xFF) << 24));\n\t\t\tGFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), nc);\n\t\t\tsubsamples[x] = 0;\n\t\t}\n\t}\n\n\tfree(subsamples);\n\tfree(crosses);\n\tfree(intersects);\n}\n\nstruct contour * shape = NULL;\nstruct shape * finalizedShape = NULL;\nstatic void move_to(float x, float y);\nstatic uint32_t myColor = 0;\nstatic void add_point(float x, float y) {\n\tmyColor = rgb(rand() % 255,rand() % 255,rand() % 255);\n\tif (!shape) {\n\t\tmove_to(x,y);\n\t} else if (shape->flags & 1) {\n\t\tshape->edges[shape->edgeCount].end.x = x;\n\t\tshape->edges[shape->edgeCount].end.y = y;\n\t\tshape->edgeCount++;\n\t\tshape->flags &= ~1;\n\t} else {\n\t\tif (shape->edgeCount + 1 == shape->nextAlloc) {\n\t\t\tshape->nextAlloc *= 2;\n\t\t\tshape = realloc(shape, sizeof(struct contour) + sizeof(struct edge) * (shape->nextAlloc));\n\t\t}\n\t\tshape->edges[shape->edgeCount].start.x = shape->edges[shape->edgeCount-1].end.x;\n\t\tshape->edges[shape->edgeCount].start.y = shape->edges[shape->edgeCount-1].end.y;\n\t\tshape->edges[shape->edgeCount].end.x = x;\n\t\tshape->edges[shape->edgeCount].end.y = y;\n\t\tshape->edgeCount++;\n\t\tshape->flags &= ~1;\n\t}\n}\n\nstatic void move_to(float x, float y) {\n\tif (!shape) {\n\t\tshape = malloc(sizeof(struct contour) + sizeof(struct edge) * 2);\n\t\tshape->edgeCount = 0;\n\t\tshape->nextAlloc = 2;\n\t\tshape->flags = 0;\n\t\tshape->last_start = 0;\n\t} else if (!(shape->flags & 1) && shape->edgeCount) {\n\t\tadd_point(shape->edges[shape->last_start].start.x, shape->edges[shape->last_start].start.y);\n\t}\n\tif (shape->edgeCount + 1 == shape->nextAlloc) {\n\t\tshape->nextAlloc *= 2;\n\t\tshape = realloc(shape, sizeof(struct contour) + sizeof(struct edge) * (shape->nextAlloc));\n\t}\n\tshape->edges[shape->edgeCount].start.x = x;\n\tshape->edges[shape->edgeCount].start.y = y;\n\tshape->last_start = shape->edgeCount;\n\tshape->flags |= 1;\n}\n\nstatic void draw(void) {\n\tdraw_fill(ctx, rgba(0,0,0,10));\n\tif (shape) {\n\n\t\tif (shape->last_start + 1 == shape->edgeCount) {\n\t\t\tdraw_line(ctx, shape->edges[shape->last_start].start.x, shape->edges[shape->last_start].end.x, shape->edges[shape->last_start].start.y, shape->edges[shape->last_start].end.y, rgb(255,255,255));\n\t\t}\n\n\t\tif (finalizedShape) {\n\t\t\t/* Oh boy */\n\t\t\tpath_paint(ctx, finalizedShape, myColor);\n\t\t}\n\t}\n}\n\nstatic void finish_draw(void) {\n\tflip(ctx);\n\tyutani_flip(yctx, wina);\n}\n\nint main (int argc, char ** argv) {\n\tleft   = 100;\n\ttop    = 100;\n\twidth  = 500;\n\theight = 500;\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\twina = yutani_window_create(yctx, width, height);\n\tyutani_window_move(yctx, wina, left, top);\n\tyutani_window_advertise_icon(yctx, wina, \"polygons\", \"polygons\");\n\n\tctx = init_graphics_yutani_double_buffer(wina);\n\tdraw();\n\tfinish_draw();\n\n\twhile (!should_exit) {\n\t\tint fds[1] = {fileno(yctx->sock)};\n\t\tint index = fswait2(1,fds,20);\n\t\tif (index == 0) {\n\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\twhile (m) {\n\t\t\t\tswitch (m->type) {\n\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\t\tfloat x = (float)me->new_x;\n\t\t\t\t\t\t\tfloat y = (float)me->new_y;\n\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\tadd_point(x, y);\n\t\t\t\t\t\t\t\tif (finalizedShape) free(finalizedShape);\n\t\t\t\t\t\t\t\tfinalizedShape = path_finish(shape);\n\t\t\t\t\t\t\t\tdraw();\n\t\t\t\t\t\t\t\tfinish_draw();\n\t\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {\n\t\t\t\t\t\t\t\tmove_to(x, y);\n\t\t\t\t\t\t\t\tdraw();\n\t\t\t\t\t\t\t\tfinish_draw();\n\t\t\t\t\t\t\t} else if (shape && (shape->flags & 1)) {\n\t\t\t\t\t\t\t\tdraw();\n\t\t\t\t\t\t\t\tdraw_line(ctx,\n\t\t\t\t\t\t\t\t\tshape->edges[shape->edgeCount].start.x,\n\t\t\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\t\t\tshape->edges[shape->edgeCount].start.y,\n\t\t\t\t\t\t\t\t\ty,\n\t\t\t\t\t\t\t\t\trgb(0,200,0));\n\t\t\t\t\t\t\t\tfinish_draw();\n\t\t\t\t\t\t\t} else if (shape && !(shape->flags & 1)) {\n\t\t\t\t\t\t\t\tdraw();\n\t\t\t\t\t\t\t\tdraw_line(ctx,\n\t\t\t\t\t\t\t\t\tshape->edges[shape->edgeCount-1].end.x,\n\t\t\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\t\t\tshape->edges[shape->edgeCount-1].end.y,\n\t\t\t\t\t\t\t\t\ty,\n\t\t\t\t\t\t\t\t\trgb(0,200,0));\n\t\t\t\t\t\t\t\tfinish_draw();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t}\n\t\t}\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/pong.c",
    "content": "/**\n * @brief pong - Window Manager Pong\n *\n * Play pong where the paddles and ball are all windows.\n * Use the WM bindings to drag the left paddle to play.\n * Press `q` to quit.\n *\n * Rendering updates are all done by the compositor, while the game\n * only renders to the windows once at start up.\n *\n * Window movement tracking keeps the game logic aware of the paddle\n * position, and window moves for the ball and other paddle keep\n * things in the right place visually.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <assert.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <time.h>\n#include <math.h>\n#include <sched.h>\n#include <sys/time.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\n#define GAME_PATH \"/usr/share/games/pong\"\n\n#define PADDLE_WIDTH  50\n#define PADDLE_HEIGHT 300\n#define BALL_SIZE     50\n\nstatic yutani_t * yctx;\nstatic int spkr = 0;\n\nstruct object {\n\tdouble x;\n\tdouble y;\n\tint width;\n\tint height;\n\tdouble vel_x;\n\tdouble vel_y;\n\tsprite_t sprite;\n};\n\nstatic yutani_window_t * paddle_left;\nstatic yutani_window_t * paddle_right;\nstatic yutani_window_t * ball_win;\n\nstatic struct object left;\nstatic struct object right;\nstatic struct object ball;\n\nstatic gfx_context_t * paddle_left_ctx;\nstatic gfx_context_t * paddle_right_ctx;\nstatic gfx_context_t * ball_ctx;\n\nstatic int should_exit = 0;\n\nstatic int left_score = 0;\nstatic int right_score = 0;\n\nstruct spkr {\n\tint length;\n\tint frequency;\n};\n\nstatic void note(int frequency) {\n\tstruct spkr s = {\n\t\t.length = 2,\n\t\t.frequency = frequency,\n\t};\n\n\twrite(spkr, &s, sizeof(s));\n}\n\n\nstatic uint32_t current_time() {\n\tstatic uint32_t start_time = 0;\n\tstatic uint32_t start_subtime = 0;\n\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\tif (!start_time) {\n\t\tstart_time = t.tv_sec;\n\t\tstart_subtime = t.tv_usec;\n\t}\n\n\tuint32_t sec_diff = t.tv_sec - start_time;\n\tuint32_t usec_diff = t.tv_usec - start_subtime;\n\n\tif (t.tv_usec < (int)start_subtime) {\n\t\tsec_diff -= 1;\n\t\tusec_diff = (1000000 + t.tv_usec) - start_subtime;\n\t}\n\n\treturn (uint32_t)(sec_diff * 1000 + usec_diff / 1000);\n}\n\nstatic int colliding(struct object * a, struct object * b) {\n\tif (a->x >= b->x + b->width) return 0;\n\tif (a->y >= b->y + b->height) return 0;\n\tif (b->x >= a->x + a->width) return 0;\n\tif (b->y >= a->y + a->height) return 0;\n\treturn 1;\n}\n\n\nvoid redraw(void) {\n\tdraw_fill(paddle_left_ctx, rgba(0,0,0,0));\n\tdraw_fill(paddle_right_ctx, rgba(0,0,0,0));\n\tdraw_fill(ball_ctx, rgba(0,0,0,0));\n\n\tdraw_sprite(paddle_left_ctx, &left.sprite, 0, 0);\n\tdraw_sprite(paddle_right_ctx, &right.sprite, 0, 0);\n\tdraw_sprite(ball_ctx, &ball.sprite, 0, 0);\n\n\tyutani_flip(yctx, paddle_left);\n\tyutani_flip(yctx, paddle_right);\n\tyutani_flip(yctx, ball_win);\n}\n\nvoid update_left(void) {\n\tyutani_window_move(yctx, paddle_left, left.x, left.y);\n}\nvoid update_right(void) {\n\tyutani_window_move(yctx, paddle_right, right.x, right.y);\n}\nvoid update_ball(void) {\n\tyutani_window_move(yctx, ball_win, ball.x, ball.y);\n}\n\nvoid update_stuff(void) {\n\n\tright.vel_y = (right.y + right.height / 2 < ball.y + ball.height / 2) ? 2.0 : -2.0;\n\tright.y += right.vel_y;\n\tupdate_right();\n\n\tball.x += ball.vel_x;\n\tball.y += ball.vel_y;\n\n\tif (ball.y < 0) {\n\t\tball.vel_y = -ball.vel_y;\n\t\tball.y = 0;\n\t}\n\tif (ball.y > yctx->display_height - ball.height) {\n\t\tball.vel_y = -ball.vel_y;\n\t\tball.y = yctx->display_height - ball.height;\n\t}\n\n\tif (ball.x < 0) {\n\t\tball.x       = yctx->display_width / 2 - ball.width / 2;\n\t\tball.y       = yctx->display_height / 2 - ball.height / 2;\n\t\tball.vel_x   = -10.0;\n\t\tball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;\n\t\tnote(10000);\n\t\tright_score++;\n\t\tprintf(\"%d : %d\\n\", left_score, right_score);\n\t}\n\n\tif (ball.x > yctx->display_width - ball.width ) {\n\t\tball.x       = yctx->display_width / 2 - ball.width / 2;\n\t\tball.y       = yctx->display_height / 2 - ball.height / 2;\n\t\tball.vel_x   = 10.0;\n\t\tball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;\n\t\tnote(17000);\n\t\tleft_score++;\n\t\tprintf(\"%d : %d\\n\", left_score, right_score);\n\t}\n\n\tif (colliding(&ball, &left)) {\n\t\tball.x = left.x + left.width + 2;\n\t\tball.vel_x   = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x;\n\n\t\tdouble intersect = ((ball.y + ball.height/2) - (left.y)) / ((double)left.height) - 0.5;\n\t\tball.vel_y = intersect * 8.0;\n\t\tnote(15680);\n\t}\n\n\tif (colliding(&ball, &right)) {\n\t\tball.x = right.x - ball.width - 2;\n\t\tball.vel_x   = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x;\n\n\t\tdouble intersect = ((ball.y + ball.height/2) - (right.y)) / ((double)right.height/2.0);\n\t\tball.vel_y = intersect * 3.0;\n\t\tnote(11747);\n\t}\n\n\tupdate_ball();\n}\n\nint main (int argc, char ** argv) {\n\n\tyctx = yutani_init();\n\n\tleft.width   = PADDLE_WIDTH;\n\tleft.height  = PADDLE_HEIGHT;\n\n\tright.width  = PADDLE_WIDTH;\n\tright.height = PADDLE_HEIGHT;\n\n\tball.width   = BALL_SIZE;\n\tball.height  = BALL_SIZE;\n\n\tball.x       = yctx->display_width / 2 - ball.width / 2;\n\tball.y       = yctx->display_height / 2 - ball.height / 2;\n\n\tleft.x       = 10;\n\tleft.y       = yctx->display_height / 2 - left.height / 2;\n\n\tright.x      = yctx->display_width - right.width - 10;\n\tright.y      = yctx->display_height / 2 - right.height / 2;\n\n\tpaddle_left  = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT);\n\tpaddle_right = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT);\n\tball_win     = yutani_window_create(yctx, BALL_SIZE, BALL_SIZE);\n\n\tpaddle_left_ctx  = init_graphics_yutani(paddle_left);\n\tpaddle_right_ctx = init_graphics_yutani(paddle_right);\n\tball_ctx         = init_graphics_yutani(ball_win);\n\n\tsrand(time(NULL));\n\n\tball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;\n\tball.vel_x = -10.0;\n\n\tfprintf(stderr, \"Loading sprites...\\n\");\n\tload_sprite(&left.sprite, GAME_PATH \"/paddle-red.png\");\n\tload_sprite(&right.sprite,GAME_PATH \"/paddle-blue.png\");\n\tload_sprite(&ball.sprite, GAME_PATH \"/ball.png\");\n\n\tredraw();\n\tupdate_left();\n\tupdate_right();\n\tupdate_ball();\n\n\tuint32_t last_tick = current_time();\n\n\tspkr = open(\"/dev/spkr\", O_WRONLY);\n\n\twhile (!should_exit) {\n\t\tuint32_t t = current_time();\n\t\tif (t > last_tick + 10) {\n\t\t\tlast_tick += 10;\n\t\t\tupdate_stuff();\n\t\t}\n\t\tyutani_msg_t * m = yutani_poll_async(yctx);\n\t\tif (m) {\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (ke->event.key == 'q' && ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOVE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_move * wm = (void*)m->data;\n\t\t\t\t\t\tif (wm->wid == paddle_left->wid) {\n\t\t\t\t\t\t\t/* Update paddle speed and position */\n\t\t\t\t\t\t\tleft.y = (double)wm->y;\n\t\t\t\t\t\t\tif (wm->x != (int)left.x) {\n\t\t\t\t\t\t\t\tupdate_left();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\tif (me->wid == paddle_left->wid) {\n\t\t\t\t\t\t\t\tyutani_window_drag_start(yctx, paddle_left);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t} else {\n\t\t\tsched_yield();\n\t\t}\n\t}\n\n\tyutani_close(yctx, paddle_left);\n\tyutani_close(yctx, paddle_right);\n\tyutani_close(yctx, ball_win);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/prompt_and_delete.krk",
    "content": "#!/bin/kuroko\nimport kuroko\nimport os\nimport _waitpid\nlet count = len(kuroko.argv)\n\nif count < 3:\n    print(\"expected at least two arguments\")\n    return 1\n\nlet callbackPid = int(kuroko.argv[1])\nif callbackPid < 1:\n    print(\"suspicious callback pid:\", callbackPid)\n    return 1\n\ncount -= 2\n\ndef show_dialog(msg,title=\"File Browser\",icon=\"/usr/share/icons/48/folder.png\"):\n    let pid = os.fork()\n    if pid == 0:\n        os.execvp('showdialog',['showdialog',title,icon,msg])\n        os.exit(1)\n    while True:\n        let result = _waitpid.waitpid(pid)\n        if result[0] != pid:\n            continue\n        return result[1] == 0\n\nif show_dialog(f'This will permanently delete {count} file{\"s\" if count != 1 else \"\"}.'):\n    for file in kuroko.argv[2:]:\n        try:\n            os.remove(file)\n        except Exception as e:\n            if not show_dialog(f'An error occured while trying to delete \"{file}\":\\n{e.arg}'):\n                break\n    os.kill(callbackPid, 21) # SIGURG\nelse:\n    return 1\n"
  },
  {
    "path": "apps/ps.c",
    "content": "/**\n * @brief Print a list of running processes.\n *\n * The listed processes are limited to ones owned by the current\n * user and are listed in PID order. Various options allow for\n * threads to be shown separately, extra information to be\n * included in the output, etc.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <pwd.h>\n\n#include <toaru/list.h>\n#include <toaru/hashmap.h>\n\n#define LINE_LEN 4096\n\nstatic int show_all = 0;\nstatic int show_threads = 0;\nstatic int show_username = 0;\nstatic int show_mem = 0;\nstatic int show_cpu = 0;\nstatic int show_time = 0;\nstatic int collect_commandline = 0;\n\nstatic int widths[] = {3,3,4,3,3,4,4,4};\n\nstruct process {\n\tint uid;\n\tint pid;\n\tint tid;\n\tint mem;\n\tint vsz;\n\tint shm;\n\tint cpu;\n\tunsigned long time;\n\tchar * process;\n\tchar * command_line;\n};\n\nstatic hashmap_t * process_ents = NULL;\n\nvoid print_username(int uid) {\n\tstruct passwd * p = getpwuid(uid);\n\n\tif (p) {\n\t\tprintf(\"%-8s\", p->pw_name);\n\t} else {\n\t\tprintf(\"%-8d\", uid);\n\t}\n\n\tendpwent();\n}\n\nstruct process * process_from_pid(pid_t pid) {\n\treturn hashmap_get(process_ents, (void*)(uintptr_t)pid);\n}\n\nstruct process * process_entry(struct dirent *dent) {\n\tchar tmp[300];\n\tFILE * f;\n\tchar line[LINE_LEN];\n\n\tint pid = 0, uid = 0, tgid = 0, mem = 0, shm = 0, vsz = 0, cpu = 0;\n\tunsigned long ttime = 0;\n\tchar name[100];\n\n\tsprintf(tmp, \"/proc/%s/status\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\n\tif (!f) {\n\t\treturn NULL;\n\t}\n\n\tline[0] = 0;\n\n\twhile (fgets(line, LINE_LEN, f) != NULL) {\n\t\tchar * n = strstr(line,\"\\n\");\n\t\tif (n) { *n = '\\0'; }\n\t\tchar * tab = strstr(line,\"\\t\");\n\t\tif (tab) {\n\t\t\t*tab = '\\0';\n\t\t\ttab++;\n\t\t}\n\t\tif (strstr(line, \"Pid:\") == line) {\n\t\t\tpid = atoi(tab);\n\t\t} else if (strstr(line, \"Uid:\") == line) {\n\t\t\tuid = atoi(tab);\n\t\t} else if (strstr(line, \"Tgid:\") == line) {\n\t\t\ttgid = atoi(tab);\n\t\t} else if (strstr(line, \"Name:\") == line) {\n\t\t\tstrcpy(name, tab);\n\t\t} else if (strstr(line, \"VmSize:\") == line) {\n\t\t\tvsz = atoi(tab);\n\t\t} else if (strstr(line, \"RssShmem:\") == line) {\n\t\t\tshm = atoi(tab);\n\t\t} else if (strstr(line, \"MemPermille:\") == line) {\n\t\t\tmem = atoi(tab);\n\t\t} else if (strstr(line, \"CpuPermille:\") == line) {\n\t\t\tcpu = atoi(tab);\n\t\t} else if (strstr(line, \"TotalTime:\") == line) {\n\t\t\tttime = strtoul(tab,NULL,0);\n\t\t}\n\t}\n\n\tfclose(f);\n\n\tif (!show_all) {\n\t\t/* Filter not ours */\n\t\tif (uid != getuid()) return NULL;\n\t}\n\n\tif (!show_threads) {\n\t\tif (tgid != pid) {\n\t\t\t/* Add this thread's CPU usage to the parent */\n\t\t\tstruct process * parent = process_from_pid(tgid);\n\t\t\tif (parent) {\n\t\t\t\tparent->cpu += cpu;\n\t\t\t\tparent->time += ttime;\n\t\t\t}\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tstruct process * out = malloc(sizeof(struct process));\n\tout->uid = uid;\n\tout->pid = tgid;\n\tout->tid = pid;\n\tout->mem = mem;\n\tout->shm = shm;\n\tout->vsz = vsz;\n\tout->cpu = cpu;\n\tout->time = ttime;\n\tout->process = strdup(name);\n\tout->command_line = NULL;\n\n\thashmap_set(process_ents, (void*)(uintptr_t)pid, out);\n\n\tchar garbage[1024];\n\tint len;\n\n\tif ((len = sprintf(garbage, \"%d\", out->pid)) > widths[0]) widths[0] = len;\n\tif ((len = sprintf(garbage, \"%d\", out->tid)) > widths[1]) widths[1] = len;\n\tif ((len = sprintf(garbage, \"%d\", out->vsz)) > widths[3]) widths[3] = len;\n\tif ((len = sprintf(garbage, \"%d\", out->shm)) > widths[4]) widths[4] = len;\n\tif ((len = sprintf(garbage, \"%d.%01d\", out->mem / 10, out->mem % 10)) > widths[5]) widths[5] = len;\n\tif ((len = sprintf(garbage, \"%d.%01d\", out->cpu / 10, out->cpu % 10)) > widths[6]) widths[6] = len;\n\tif ((len = sprintf(garbage, \"%lu:%02lu.%02lu\",\n\t\t(out->time / (1000000UL * 60 * 60)),\n\t\t(out->time / (1000000UL * 60)) % 60,\n\t\t(out->time / (1000000UL)) % 60)) > widths[7]) widths[7] = len;\n\n\tstruct passwd * p = getpwuid(out->uid);\n\tif (p) {\n\t\tif ((len = strlen(p->pw_name)) > widths[2]) widths[2] = len;\n\t} else {\n\t\tif ((len = sprintf(garbage, \"%d\", out->uid)) > widths[2]) widths[2] = len;\n\t}\n\tendpwent();\n\n\tif (collect_commandline) {\n\t\tsprintf(tmp, \"/proc/%s/cmdline\", dent->d_name);\n\t\tf = fopen(tmp, \"r\");\n\t\tchar foo[1024];\n\t\tint s = fread(foo, 1, 1024, f);\n\t\tif (s > 0) {\n\t\t\tout->command_line = malloc(s + 1);\n\t\t\tmemset(out->command_line, 0, s + 1);\n\t\t\tmemcpy(out->command_line, foo, s);\n\n\t\t\tfor (int i = 0; i < s; ++i) {\n\t\t\t\tif (out->command_line[i] == 30) {\n\t\t\t\t\tout->command_line[i] = ' ';\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t\tfclose(f);\n\t}\n\n\treturn out;\n}\n\nvoid print_header(void) {\n\tif (show_username) {\n\t\tprintf(\"%-*s \", widths[2], \"USER\");\n\t}\n\tprintf(\"%*s \", widths[0], \"PID\");\n\tif (show_threads) {\n\t\tprintf(\"%*s \", widths[1], \"TID\");\n\t}\n\tif (show_cpu) {\n\t\tprintf(\"%*s \", widths[6], \"%CPU\");\n\t}\n\tif (show_mem) {\n\t\tprintf(\"%*s \", widths[5], \"%MEM\");\n\t\tprintf(\"%*s \", widths[3], \"VSZ\");\n\t\tprintf(\"%*s \", widths[4], \"SHM\");\n\t}\n\tif (show_time) {\n\t\tprintf(\"%*s \", widths[7], \"TIME\");\n\t}\n\tprintf(\"CMD\\n\");\n}\n\nvoid print_entry(struct process * out) {\n\tif (show_username) {\n\t\tstruct passwd * p = getpwuid(out->uid);\n\t\tif (p) {\n\t\t\tprintf(\"%-*s \", widths[2], p->pw_name);\n\t\t} else {\n\t\t\tprintf(\"%-*d \", widths[2], out->uid);\n\t\t}\n\t\tendpwent();\n\t}\n\tprintf(\"%*d \", widths[0], out->pid);\n\tif (show_threads) {\n\t\tprintf(\"%*d \", widths[1], out->tid);\n\t}\n\tif (show_cpu) {\n\t\tchar tmp[10];\n\t\tsprintf(tmp, \"%*d.%01d\", widths[6]-2, out->cpu / 10, out->cpu % 10);\n\t\tprintf(\"%*s \", widths[6], tmp);\n\t}\n\tif (show_mem) {\n\t\tchar tmp[10];\n\t\tsprintf(tmp, \"%*d.%01d\", widths[5]-2, out->mem / 10, out->mem % 10);\n\t\tprintf(\"%*s \", widths[5], tmp);\n\t\tprintf(\"%*d \", widths[3], out->vsz);\n\t\tprintf(\"%*d \", widths[4], out->shm);\n\t}\n\tif (show_time) {\n\t\tchar tmp[30];\n\t\tsprintf(tmp, \"%lu:%02lu.%02lu\",\n\t\t(out->time / (1000000UL * 60 * 60)),\n\t\t(out->time / (1000000UL * 60)) % 60,\n\t\t(out->time / (1000000UL)) % 60);\n\n\t\tprintf(\"%*s \", widths[7], tmp);\n\t}\n\tif (out->command_line) {\n\t\tprintf(\"%s\\n\", out->command_line);\n\t} else {\n\t\tprintf(\"%s\\n\", out->process);\n\t}\n}\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"ps - list running processes\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-A] [format]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -A     \\033[3mshow other users' processes\\033[0m\\n\"\n\t\t\t\" -T     \\033[3mshow threads\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" [format] supports some BSD options:\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"  a     \\033[3mshow full command line\\033[0m\\n\"\n\t\t\t\"  u     \\033[3muse 'user-oriented' format\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nint main (int argc, char * argv[]) {\n\n\t/* Parse arguments */\n\tint c;\n\twhile ((c = getopt(argc, argv, \"AT?\")) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'A':\n\t\t\t\tshow_all = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'T':\n\t\t\t\tshow_threads = 1;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\tchar * show = argv[optind];\n\t\twhile (*show) {\n\t\t\tswitch (*show) {\n\t\t\t\tcase 'u':\n\t\t\t\t\tshow_username = 1;\n\t\t\t\t\tshow_mem = 1;\n\t\t\t\t\tshow_cpu = 1;\n\t\t\t\t\tshow_time = 1;\n\t\t\t\t\t// fallthrough\n\t\t\t\tcase 'a':\n\t\t\t\t\tcollect_commandline = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tshow++;\n\t\t}\n\t}\n\n\t/* Open the directory */\n\tDIR * dirp = opendir(\"/proc\");\n\n\t/* Read the entries in the directory */\n\tlist_t * ents_list = list_create();\n\n\tprocess_ents = hashmap_create_int(10);\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {\n\t\t\tstruct process * p = process_entry(ent);\n\t\t\tif (p) {\n\t\t\t\tlist_insert(ents_list, (void *)p);\n\t\t\t}\n\t\t}\n\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tprint_header();\n\tforeach(entry, ents_list) {\n\t\tprint_entry(entry->value);\n\t}\n\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/pstree.c",
    "content": "/**\n * @brief pstree - Display a tree of running process\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n\n#include <toaru/list.h>\n#include <toaru/tree.h>\n\ntypedef struct process {\n\tint pid;\n\tint ppid;\n\tint tgid;\n\tchar name[100];\n\tchar path[200];\n} p_t;\n\n#define LINE_LEN 4096\n\np_t * build_entry(struct dirent * dent) {\n\tchar tmp[300];\n\tFILE * f;\n\tchar line[LINE_LEN];\n\n\tsprintf(tmp, \"/proc/%s/status\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\n\tp_t * proc = malloc(sizeof(p_t));\n\n\twhile (fgets(line, LINE_LEN, f) != NULL) {\n\t\tchar * n = strstr(line,\"\\n\");\n\t\tif (n) { *n = '\\0'; }\n\t\tchar * tab = strstr(line,\"\\t\");\n\t\tif (tab) {\n\t\t\t*tab = '\\0';\n\t\t\ttab++;\n\t\t}\n\t\tif (strstr(line, \"Pid:\") == line) {\n\t\t\tproc->pid = atoi(tab);\n\t\t} else if (strstr(line, \"PPid:\") == line) {\n\t\t\tproc->ppid = atoi(tab);\n\t\t} else if (strstr(line, \"Tgid:\") == line) {\n\t\t\tproc->tgid = atoi(tab);\n\t\t} else if (strstr(line, \"Name:\") == line) {\n\t\t\tstrcpy(proc->name, tab);\n\t\t} else if (strstr(line, \"Path:\") == line) {\n\t\t\tstrcpy(proc->path, tab);\n\t\t}\n\t}\n\n\tif (strstr(proc->name,\"python\") == proc->name) {\n\t\tchar * name = proc->path + strlen(proc->path) - 1;\n\n\t\twhile (1) {\n\t\t\tif (*name == '/') {\n\t\t\t\tname++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (name == proc->name) break;\n\t\t\tname--;\n\t\t}\n\n\t\tmemcpy(proc->name, name, strlen(name)+1);\n\t}\n\n\tif (proc->tgid != proc->pid) {\n\t\tchar tmp[100] = {0};\n\t\tsprintf(tmp, \"{%s}\", proc->name);\n\t\tmemcpy(proc->name, tmp, strlen(tmp)+1);\n\t}\n\n\tfclose(f);\n\n\treturn proc;\n}\n\nuint8_t find_pid(void * proc_v, void * pid_v) {\n\tp_t * p = proc_v;\n\tpid_t i = (pid_t)(uintptr_t)pid_v;\n\n\treturn (uint8_t)(p->pid == i);\n}\n\nvoid print_process_tree_node(tree_node_t * node, size_t depth, int indented, int more, char lines[]) {\n\n\tp_t * proc = node->value;\n\n\tfor (int i = 0; i < (int)strlen(proc->name)+3; ++i) {\n\t\tlines[depth+i] = 0;\n\t}\n\n\tif (!indented && depth) {\n\t\tif (more) {\n\t\t\tprintf(\"─┬─\");\n\t\t\tlines[depth+1] = 1;\n\t\t} else {\n\t\t\tprintf(\"───\");\n\t\t}\n\t\tdepth += 3;\n\t} else if (depth) {\n\t\tfor (int i = 0; i < (int)depth; ++i) {\n\t\t\tif (lines[i]) {\n\t\t\t\tprintf(\"│\");\n\t\t\t} else {\n\t\t\t\tprintf(\" \");\n\t\t\t}\n\t\t}\n\t\tif (more) {\n\t\t\tprintf(\" ├─\");\n\t\t\tlines[depth+1] = 1;\n\t\t} else {\n\t\t\tprintf(\" └─\");\n\t\t}\n\t\tdepth += 3;\n\t}\n\n\tprintf(proc->name);\n\n\tif (!node->children->length) {\n\t\tprintf(\"\\n\");\n\t} else {\n\t\tdepth += strlen(proc->name);\n\n\t\tint t = 0;\n\t\tforeach(child, node->children) {\n\t\t\t/* Recursively print the children */\n\t\t\tprint_process_tree_node(child->value, depth, !!(t), ((t+1)!=(int)node->children->length), lines);\n\t\t\tt++;\n\t\t}\n\t}\n\n\tfor (int i = 0; i < (int)strlen(proc->name)+3; ++i) {\n\t\tlines[depth+i] = 0;\n\t}\n}\n\nint main (int argc, char * argv[]) {\n\n\t/* Open the directory */\n\tDIR * dirp = opendir(\"/proc\");\n\n\t/* Read the entries in the directory */\n\ttree_t * procs = tree_create();\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') {\n\t\t\tp_t * proc = build_entry(ent);\n\n\t\t\tif (proc->ppid == 0 && proc->pid == 1) {\n\t\t\t\ttree_set_root(procs, proc);\n\t\t\t} else {\n\t\t\t\ttree_node_t * parent = tree_find(procs,(void *)(uintptr_t)proc->ppid,find_pid);\n\t\t\t\tif (parent) {\n\t\t\t\t\ttree_node_insert_child(procs, parent, proc);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tchar lines[500] = {0};\n\tprint_process_tree_node(procs->root, 0, 0, 0, lines);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/pwd.c",
    "content": "/**\n * @brief Print the current working directory\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <unistd.h>\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\tchar tmp[1024];\n\tif (getcwd(tmp, 1023)) {\n\t\tputs(tmp);\n\t\treturn 0;\n\t} else {\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "apps/qemu-display-hack.c",
    "content": "/**\n * @brief qemu-display-hack - Manage display size under QEMU\n *\n * XXX Does this still work? Does the TTY interface interfere\n *     with the operation of the communication pipe?\n *\n * Communicates with a harness on the host running QEMU to\n * automatically update the display resolution when the\n * QEMU window size changes, similar to how VirtualBox's\n * display size changing works.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <sys/ioctl.h>\n#include <kernel/video.h>\n\nint main(int argc, char * argv[]) {\n\n\tif (system(\"qemu-fwcfg -q opt/org.toaruos.displayharness\") != 0) {\n\t\tfprintf(stderr, \"%s: display harness not enabled\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint fd = open(\"/dev/fb0\", O_RDONLY);\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"%s: failed to open framebuffer: %s\\n\", argv[0], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tstruct vid_size s;\n\n\tFILE * f = fopen(\"/dev/ttyS1\",\"r+\");\n\tif (!f) {\n\t\tfprintf(stderr, \"%s: failed to open serial: %s\\n\", argv[0], strerror(errno));\n\t\treturn 1;\n\t}\n\n\tif (!fork()) {\n\n\t\twhile (!feof(f)) {\n\t\t\tchar data[128];\n\t\t\tfgets(data, 128, f);\n\n\t\t\tchar * linefeed = strstr(data,\"\\n\");\n\t\t\tif (linefeed) { *linefeed = '\\0'; }\n\n\t\t\tchar * width;\n\t\t\tchar * height;\n\n\t\t\twidth = strstr(data, \" \");\n\t\t\tif (width) {\n\t\t\t\t*width = '\\0';\n\t\t\t\twidth++;\n\t\t\t} else {\n\t\t\t\tcontinue; /* bad line */\n\t\t\t}\n\n\t\t\theight = strstr(width, \" \");\n\t\t\tif (height) {\n\t\t\t\t*height = '\\0';\n\t\t\t\theight++;\n\t\t\t} else {\n\t\t\t\tcontinue; /* bad line */\n\t\t\t}\n\n\t\t\ts.width = atoi(width);\n\t\t\ts.height = atoi(height);\n\n\t\t\tioctl(fd, IO_VID_SET, &s);\n\t\t\tfprintf(f, \"X\");\n\t\t\tfflush(f);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/qemu-fwcfg.c",
    "content": "/**\n * @brief qemu-fwcfg - Read QEMU fwcfg values.\n *\n * Provides easy access to values and files set by QEMU's -fw_cfg\n * flag. This is used by the QEMU harness, as well as the bootloader,\n * and can be used to provide files directly to the guest.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <signal.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#define FW_CFG_PORT_OUT 0x510\n#define FW_CFG_PORT_IN  0x511\n#define FW_CFG_SELECT_QEMU 0x0000\n#define FW_CFG_SELECT_LIST 0x0019\n\nstatic int port_fd = -1;\n\n/* outw / inb helper functions */\nstatic void outports(unsigned short _port, unsigned short _data) {\n\tpwrite(port_fd, &_data, 2, _port);\n}\n\nstatic unsigned char inportb(unsigned short _port) {\n\tunsigned char out;\n\tpread(port_fd, &out, 1, _port);\n\treturn out;\n}\n\n/* Despite primarily emulating x86, these are all big-endian */\nstatic void swap_bytes(void * in, int count) {\n\tchar * bytes = in;\n\tif (count == 4) {\n\t\tuint32_t * t = in;\n\t\t*t = (bytes[0] << 24) | (bytes[1] << 12) | (bytes[2] << 8) | bytes[3];\n\t} else if (count == 2) {\n\t\tuint16_t * t = in;\n\t\t*t = (bytes[0] << 8) | bytes[1];\n\t}\n}\n\n/* Layout of the information returned from the fw_cfg port */\nstruct fw_cfg_file {\n\tuint32_t size;\n\tuint16_t select;\n\tuint16_t reserved;\n\tchar name[56];\n};\n\nstatic int usage(char * argv[]) {\n\tprintf(\n\t\t\t\"Obtain QEMU fw_cfg values\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-?ln] [config name]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -l     \\033[3mlist available config entries\\033[0m\\n\"\n\t\t\t\" -n     \\033[3mdon't print a new line after data\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nstatic void sig_pass(int sig) {\n\texit(1);\n}\n\nint main(int argc, char * argv[]) {\n\n\tuint32_t count = 0;\n\tuint8_t * bytes = (uint8_t *)&count;\n\tint found = 0;\n\tstruct fw_cfg_file file;\n\tuint8_t * tmp = (uint8_t *)&file;\n\n\tint opt = 0;\n\tint list = 0;\n\tint no_newline = 0;\n\tint query_quietly = 0;\n\n\twhile ((opt = getopt(argc, argv, \"?lnq\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'n':\n\t\t\t\tno_newline = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tquery_quietly = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tlist = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind >= argc && !list) {\n\t\treturn usage(argv);\n\t}\n\n\tport_fd = open(\"/dev/port\", O_RDWR);\n\n\tif (port_fd < 0) {\n\t\t/* Try the special aarch64 interface. */\n\t\tport_fd = open(\"/dev/fwcfg\", O_RDWR);\n\t}\n\n\tif (port_fd < 0) {\n\t\tfprintf(stderr, \"%s: could not open port IO device\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tsignal(SIGILL, sig_pass);\n\n\t/* First check for QEMU */\n\toutports(FW_CFG_PORT_OUT, FW_CFG_SELECT_QEMU);\n\tif (inportb(FW_CFG_PORT_IN) != 'Q' ||\n\t\tinportb(FW_CFG_PORT_IN) != 'E' ||\n\t\tinportb(FW_CFG_PORT_IN) != 'M' ||\n\t\tinportb(FW_CFG_PORT_IN) != 'U') {\n\t\tfprintf(stderr, \"%s: this doesn't seem to be qemu\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* Then get the list of \"files\" so we can look at names */\n\toutports(FW_CFG_PORT_OUT, FW_CFG_SELECT_LIST);\n\tfor (int i = 0; i < 4; ++i) {\n\t\tbytes[i] = inportb(FW_CFG_PORT_IN);\n\t}\n\tswap_bytes(&count, sizeof(count));\n\n\tfor (unsigned int i = 0; i < count; ++i) {\n\n\t\t/* read one file entry */\n\t\tfor (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) {\n\t\t\ttmp[j] = inportb(FW_CFG_PORT_IN);\n\t\t}\n\n\t\t/* endian swap to get file size and selector ID */\n\t\tswap_bytes(&file.size, sizeof(file.size));\n\t\tswap_bytes(&file.select, sizeof(file.select));\n\n\t\tif (list) {\n\t\t\t/* 0x0020 org/whatever (1234 bytes) */\n\t\t\tfprintf(stdout, \"0x%04x %s (%d byte%s)\\n\", file.select, file.name, (int)file.size, file.size == 1 ? \"\" : \"s\");\n\t\t} else {\n\t\t\tif (!strcmp(file.name, argv[optind])) {\n\t\t\t\t/* found the requested file */\n\t\t\t\tfound = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (query_quietly) {\n\t\treturn !found;\n\t}\n\n\tif (found) {\n\t\t/* if we found the requested file, read it from the port */\n\t\toutports(FW_CFG_PORT_OUT, file.select);\n\n\t\tfor (unsigned int i = 0; i < file.size; ++i) {\n\t\t\tfputc(inportb(FW_CFG_PORT_IN), stdout);\n\t\t}\n\n\t\tif (!no_newline) {\n\t\t\tfprintf(stdout, \"\\n\");\n\t\t} else {\n\t\t\tfflush(stdout);\n\t\t}\n\n\t} else if (!list) {\n\t\tfprintf(stderr, \"%s: config option not found\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/readelf.c",
    "content": "/**\n * @file  readelf.c\n * @brief Display information about a 64-bit Elf binary or object.\n *\n * Implementation of a `readelf` utility.\n *\n * I've tried to get the output here as close to the binutils format\n * as possible, so it should be compatible with tools that try to\n * parse that. Most of the same arguments should also be supported.\n *\n * This is a rewrite of an earlier version.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020-2021 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <getopt.h>\n#ifdef __toaru__\n#include <kernel/elf.h>\n#else\n#include \"../base/usr/include/kernel/elf.h\"\n#endif\n\n#define SHOW_FILE_HEADER      0x0001\n#define SHOW_SECTION_HEADERS  0x0002\n#define SHOW_PROGRAM_HEADERS  0x0004\n#define SHOW_SYMBOLS          0x0008\n#define SHOW_DYNAMIC          0x0010\n#define SHOW_RELOCATIONS      0x0020\n\nstatic const char * elf_classToStr(unsigned char ei_class) {\n\tstatic char buf[64];\n\tswitch (ei_class) {\n\t\tcase ELFCLASS32: return \"ELF32\";\n\t\tcase ELFCLASS64: return \"ELF64\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", ei_class);\n\t\t\treturn buf;\n\t}\n}\n\nstatic const char * elf_dataToStr(unsigned char ei_data) {\n\tstatic char buf[64];\n\tswitch (ei_data) {\n\t\tcase ELFDATA2LSB: return \"2's complement, little endian\";\n\t\tcase ELFDATA2MSB: return \"2's complement, big endian\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", ei_data);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * elf_versionToStr(unsigned char ei_version) {\n\tstatic char buf[64];\n\tswitch (ei_version) {\n\t\tcase 1: return \"1 (current)\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", ei_version);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * elf_osabiToStr(unsigned char ei_osabi) {\n\tstatic char buf[64];\n\tswitch (ei_osabi) {\n\t\tcase 0: return \"UNIX - System V\";\n\t\tcase 1: return \"HP-UX\";\n\t\tcase 255: return \"Standalone\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", ei_osabi);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * elf_typeToStr(Elf64_Half type) {\n\tstatic char buf[64];\n\tswitch (type) {\n\t\tcase ET_NONE: return \"NONE (No file type)\";\n\t\tcase ET_REL:  return \"REL (Relocatable object file)\";\n\t\tcase ET_EXEC: return \"EXEC (Executable file)\";\n\t\tcase ET_DYN:  return \"DYN (Shared object file)\";\n\t\tcase ET_CORE: return \"CORE (Core file)\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", type);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * elf_machineToStr(Elf64_Half machine) {\n\tstatic char buf[64];\n\tswitch (machine) {\n\t\tcase EM_X86_64: return \"Advanced Micro Devices X86-64\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"unknown (%d)\", machine);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * sectionHeaderTypeToStr(Elf64_Word type) {\n\tstatic char buf[64];\n\tswitch (type) {\n\t\tcase SHT_NULL: return \"NULL\";\n\t\tcase SHT_PROGBITS: return \"PROGBITS\";\n\t\tcase SHT_SYMTAB: return \"SYMTAB\";\n\t\tcase SHT_STRTAB: return \"STRTAB\";\n\t\tcase SHT_RELA: return \"RELA\";\n\t\tcase SHT_HASH: return \"HASH\";\n\t\tcase SHT_DYNAMIC: return \"DYNAMIC\";\n\t\tcase SHT_NOTE: return \"NOTE\";\n\t\tcase SHT_NOBITS: return \"NOBITS\";\n\t\tcase SHT_REL: return \"REL\";\n\t\tcase SHT_SHLIB: return \"SHLIB\";\n\t\tcase SHT_DYNSYM: return \"DYNSYM\";\n\n\t\tcase 0xE: return \"INIT_ARRAY\";\n\t\tcase 0xF: return \"FINI_ARRAY\";\n\t\tcase 0x6ffffff6: return \"GNU_HASH\";\n\t\tcase 0x6ffffffe: return \"VERNEED\";\n\t\tcase 0x6fffffff: return \"VERSYM\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"(%x)\", type);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * programHeaderTypeToStr(Elf64_Word type) {\n\tstatic char buf[64];\n\tswitch (type) {\n\t\tcase PT_NULL: return \"NULL\";\n\t\tcase PT_LOAD: return \"LOAD\";\n\t\tcase PT_DYNAMIC: return \"DYNAMIC\";\n\t\tcase PT_INTERP: return \"INTERP\";\n\t\tcase PT_NOTE: return \"NOTE\";\n\t\tcase PT_PHDR: return \"PHDR\";\n\t\tcase PT_GNU_EH_FRAME: return \"GNU_EH_FRAME\";\n\n\t\tcase 0x6474e553: return \"GNU_PROPERTY\";\n\t\tcase 0x6474e551: return \"GNU_STACK\";\n\t\tcase 0x6474e552: return \"GNU_RELRO\";\n\n\t\tdefault:\n\t\t\tsprintf(buf, \"(%x)\", type);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * programHeaderFlagsToStr(Elf64_Word flags) {\n\tstatic char buf[10];\n\n\tsnprintf(buf, 10, \"%c%c%c   \",\n\t\t(flags & PF_R) ? 'R' : ' ',\n\t\t(flags & PF_W) ? 'W' : ' ',\n\t\t(flags & PF_X) ? 'E' : ' '); /* yes, E, not X... */\n\n\treturn buf;\n}\n\nstatic char * dynamicTagToStr(Elf64_Dyn * dynEntry, char * dynstr) {\n\tstatic char buf[1024];\n\tstatic char extra[500];\n\tchar * name = NULL;\n\tsprintf(extra, \"0x%lx\", dynEntry->d_un.d_val);\n\n\tswitch (dynEntry->d_tag) {\n\t\tcase DT_NULL:\n\t\t\tname = \"(NULL)\";\n\t\t\tbreak;\n\t\tcase DT_NEEDED:\n\t\t\tname = \"(NEEDED)\";\n\t\t\tsprintf(extra, \"[shared lib = %s]\", dynstr + dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase DT_PLTRELSZ:\n\t\t\tname = \"(PLTRELSZ)\";\n\t\t\tbreak;\n\t\tcase DT_PLTGOT:\n\t\t\tname = \"(PLTGOT)\";\n\t\t\tbreak;\n\t\tcase DT_HASH:\n\t\t\tname = \"(HASH)\";\n\t\t\tbreak;\n\t\tcase DT_STRTAB:\n\t\t\tname = \"(STRTAB)\";\n\t\t\tbreak;\n\t\tcase DT_SYMTAB:\n\t\t\tname = \"(SYMTAB)\";\n\t\t\tbreak;\n\t\tcase DT_RELA:\n\t\t\tname = \"(RELA)\";\n\t\t\tbreak;\n\t\tcase DT_RELASZ:\n\t\t\tname = \"(RELASZ)\";\n\t\t\tbreak;\n\t\tcase DT_RELAENT:\n\t\t\tname = \"(RELAENT)\";\n\t\t\tbreak;\n\t\tcase DT_STRSZ:\n\t\t\tname = \"(STRSZ)\";\n\t\t\tsprintf(extra, \"%ld (bytes)\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase DT_SYMENT:\n\t\t\tname = \"(SYMENT)\";\n\t\t\tsprintf(extra, \"%ld (bytes)\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase DT_INIT:\n\t\t\tname = \"(INIT)\";\n\t\t\tbreak;\n\t\tcase DT_FINI:\n\t\t\tname = \"(FINI)\";\n\t\t\tbreak;\n\t\tcase DT_SONAME:\n\t\t\tname = \"(SONAME)\";\n\t\t\tbreak;\n\t\tcase DT_RPATH:\n\t\t\tname = \"(RPATH)\";\n\t\t\tbreak;\n\t\tcase DT_SYMBOLIC:\n\t\t\tname = \"(SYMBOLIC)\";\n\t\t\tbreak;\n\t\tcase DT_REL:\n\t\t\tname = \"(REL)\";\n\t\t\tbreak;\n\t\tcase DT_RELSZ:\n\t\t\tname = \"(RELSZ)\";\n\t\t\tsprintf(extra, \"%ld (bytes)\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase DT_RELENT:\n\t\t\tname = \"(RELENT)\";\n\t\t\tbreak;\n\t\tcase DT_PLTREL:\n\t\t\tname = \"(PLTREL)\";\n\t\t\tsprintf(extra, \"%s\",\n\t\t\t\tdynEntry->d_un.d_val == DT_REL ? \"REL\" : \"RELA\");\n\t\t\tbreak;\n\t\tcase DT_DEBUG:\n\t\t\tname = \"(DEBUG)\";\n\t\t\tbreak;\n\t\tcase DT_TEXTREL:\n\t\t\tname = \"(TEXTREL)\";\n\t\t\tbreak;\n\t\tcase DT_JMPREL:\n\t\t\tname = \"(JMPREL)\";\n\t\t\tbreak;\n\t\tcase DT_BIND_NOW:\n\t\t\tname = \"(BIND_NOW)\";\n\t\t\tbreak;\n\t\tcase DT_INIT_ARRAY:\n\t\t\tname = \"(INIT_ARRAY)\";\n\t\t\tbreak;\n\t\tcase DT_FINI_ARRAY:\n\t\t\tname = \"(FINI_ARRAY)\";\n\t\t\tbreak;\n\t\tcase DT_INIT_ARRAYSZ:\n\t\t\tname = \"(INIT_ARRAYSZ)\";\n\t\t\tsprintf(extra, \"%ld (bytes)\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase DT_FINI_ARRAYSZ:\n\t\t\tname = \"(FINI_ARRASZ)\";\n\t\t\tsprintf(extra, \"%ld (bytes)\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase 0x1E:\n\t\t\tname = \"(FLAGS)\";\n\t\t\tbreak;\n\t\tcase 0x6ffffef5:\n\t\t\tname = \"(GNU_HASH)\";\n\t\t\tbreak;\n\t\tcase 0x6ffffffb:\n\t\t\tname = \"(FLAGS_1)\";\n\t\t\tbreak;\n\t\tcase 0x6ffffffe:\n\t\t\tname = \"(VERNEED)\";\n\t\t\tbreak;\n\t\tcase 0x6fffffff:\n\t\t\tname = \"(VERNEEDNUM)\";\n\t\t\tsprintf(extra, \"%ld\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tcase 0x6ffffff0:\n\t\t\tname = \"(VERSYM)\";\n\t\t\tbreak;\n\t\tcase 0x6ffffff9:\n\t\t\tname = \"(RELACOUNT)\";\n\t\t\tsnprintf(extra, 500, \"%ld\", dynEntry->d_un.d_val);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tname = \"(unknown)\";\n\t\t\tbreak;\n\t}\n\n\tsnprintf(buf, 1024, \"%-15s %s\", name, extra);\n\treturn buf;\n}\n\nstatic char * relocationInfoToStr(Elf64_Xword info) {\n#define CASE(o) case o: return #o;\n\tswitch (info) {\n\t\tCASE(R_X86_64_NONE)\n\t\tCASE(R_X86_64_64)\n\t\tCASE(R_X86_64_PC32)\n\t\tCASE(R_X86_64_GOT32)\n\t\tCASE(R_X86_64_PLT32)\n\t\tCASE(R_X86_64_COPY)\n\t\tCASE(R_X86_64_GLOB_DAT)\n\t\tCASE(R_X86_64_JUMP_SLOT)\n\t\tCASE(R_X86_64_RELATIVE)\n\t\tCASE(R_X86_64_GOTPCREL)\n\t\tCASE(R_X86_64_32)\n\t\tCASE(R_X86_64_32S)\n\t\tCASE(R_X86_64_DTPMOD64)\n\t\tCASE(R_X86_64_DTPOFF64)\n\t\tCASE(R_X86_64_TPOFF64)\n\t\tCASE(R_X86_64_TLSGD)\n\t\tCASE(R_X86_64_TLSLD)\n\t\tCASE(R_X86_64_DTPOFF32)\n\t\tCASE(R_X86_64_GOTTPOFF)\n\t\tCASE(R_X86_64_TPOFF32)\n\t\tCASE(R_X86_64_PC64)\n\t\tCASE(R_X86_64_GOTOFF64)\n\t\tCASE(R_X86_64_GOTPC32)\n\t\tCASE(R_X86_64_GOT64)\n\t\tCASE(R_X86_64_GOTPCREL64)\n\t\tCASE(R_X86_64_GOTPC64)\n\t\tCASE(R_X86_64_GOTPLT64)\n\t\tCASE(R_X86_64_PLTOFF64)\n\t\tCASE(R_X86_64_SIZE32)\n\t\tCASE(R_X86_64_SIZE64)\n\t\tCASE(R_X86_64_GOTPC32_TLSDESC)\n\t\tCASE(R_X86_64_TLSDESC_CALL)\n\t\tCASE(R_X86_64_TLSDESC)\n\t\tCASE(R_X86_64_IRELATIVE)\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n#undef CASE\n}\n\nstatic char * symbolTypeToStr(int type) {\n\tstatic char buf[10];\n\tswitch (type) {\n\t\tcase STT_NOTYPE:  return \"NOTYPE\";\n\t\tcase STT_OBJECT:  return \"OBJECT\";\n\t\tcase STT_FUNC:    return \"FUNC\";\n\t\tcase STT_SECTION: return \"SECTION\";\n\t\tcase STT_FILE:    return \"FILE\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"%x\", type);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * symbolBindToStr(int bind) {\n\tstatic char buf[10];\n\tswitch (bind) {\n\t\tcase STB_LOCAL:  return \"LOCAL\";\n\t\tcase STB_GLOBAL: return \"GLOBAL\";\n\t\tcase STB_WEAK:   return \"WEAK\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"%x\", bind);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * symbolVisToStr(int vis) {\n\tstatic char buf[10];\n\tswitch (vis) {\n\t\tcase 0: return \"DEFAULT\";\n\t\tcase 1: return \"INTERNAL\";\n\t\tcase 2: return \"HIDDEN\";\n\t\tcase 3: return \"PROTECTED\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"%x\", vis);\n\t\t\treturn buf;\n\t}\n}\n\nstatic char * symbolNdxToStr(int ndx) {\n\tstatic char buf[10];\n\tswitch (ndx) {\n\t\tcase 0:     return \"UND\";\n\t\tcase 65521: return \"ABS\";\n\t\tdefault:\n\t\t\tsprintf(buf, \"%d\", ndx);\n\t\t\treturn buf;\n\t}\n\n}\n\nstatic int usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\"Usage: %s <option(s)> elf-file(s)\\n\"\n\t\t\" Displays information about ELF object files in a GNU binutils-compatible way.\\n\"\n\t\t\" Supported options:\\n\"\n\t\t\"  -a --all             Equivalent to -h -l -S -s -d -r\\n\"\n\t\t\"  -h --file-header     Display the ELF file header\\n\"\n\t\t\"  -l --program-headers Display the program headers\\n\"\n\t\t\"  -S --section-headers Display the section headers\\n\"\n\t\t\"  -e --headers         Equivalent to -h -l -S\\n\"\n\t\t\"  -s --syms            Display symbol table\\n\"\n\t\t\"  -d --dynamic         Display dynamic section\\n\"\n\t\t\"  -r --relocs          Display relocations\\n\"\n\t\t\"  -H --help            Show this help text\\n\"\n\t\t\" Aliases:\\n\"\n\t\t\"  --segments   Same as --file-header\\n\"\n\t\t\"  --sections   Same as --section-headers\\n\"\n\t\t\"  --symbols    Same as --syms\\n\"\n\t\t, argv[0]);\n\treturn 1;\n}\n\nstruct StringTable {\n\tsize_t length;\n\tchar strings[];\n};\n\nstatic struct StringTable * load_string_table(FILE * f, Elf64_Shdr * header) {\n\tstruct StringTable * out = malloc(sizeof(struct StringTable) + header->sh_size + 1);\n\tout->length = header->sh_size;\n\tfseek(f, header->sh_offset, SEEK_SET);\n\tfread(out->strings, header->sh_size, 1, f);\n\tout->strings[out->length] = 0;\n\treturn out;\n}\n\nstatic const char * string_from_table(struct StringTable * table, size_t offset) {\n\tif (offset >= table->length) return \"(out of range)\";\n\treturn table->strings + offset;\n}\n\nint main(int argc, char * argv[]) {\n\n\tstatic struct option long_opts[] = {\n\t\t{\"all\",             no_argument, 0, 'a'},\n\t\t{\"file-header\",     no_argument, 0, 'h'},\n\t\t{\"program-headers\", no_argument, 0, 'l'},\n\t\t{\"section-headers\", no_argument, 0, 'S'},\n\t\t{\"headers\",         no_argument, 0, 'e'},\n\t\t{\"syms\",            no_argument, 0, 's'},\n\t\t{\"dynamic\",         no_argument, 0, 'd'},\n\t\t{\"relocs\",          no_argument, 0, 'r'},\n\t\t{\"help\",            no_argument, 0, 'H'},\n\n\t\t{\"segments\",        no_argument, 0, 'l'}, /* Alias for --program-headers */\n\t\t{\"sections\",        no_argument, 0, 'S'}, /* Alias for --section-headers */\n\t\t{\"symbols\",         no_argument, 0, 's'}, /* Alias for --syms */\n\t\t{0,0,0,0}\n\t};\n\n\tint show_bits = 0;\n\tint index, c;\n\n\twhile ((c = getopt_long(argc, argv, \"ahlSesdrH\", long_opts, &index)) != -1) {\n\t\tif (!c) {\n\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\tc = long_opts[index].val;\n\t\t\t}\n\t\t}\n\t\tswitch (c) {\n\t\t\tcase 'H':\n\t\t\t\treturn usage(argv);\n\t\t\tcase 'a':\n\t\t\t\tshow_bits |= SHOW_FILE_HEADER | SHOW_SECTION_HEADERS | SHOW_PROGRAM_HEADERS | SHOW_SYMBOLS | SHOW_DYNAMIC | SHOW_RELOCATIONS;\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tshow_bits |= SHOW_DYNAMIC;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tshow_bits |= SHOW_FILE_HEADER;\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tshow_bits |= SHOW_PROGRAM_HEADERS;\n\t\t\t\tbreak;\n\t\t\tcase 'S':\n\t\t\t\tshow_bits |= SHOW_SECTION_HEADERS;\n\t\t\t\tbreak;\n\t\t\tcase 'e':\n\t\t\t\tshow_bits |= SHOW_FILE_HEADER | SHOW_PROGRAM_HEADERS | SHOW_SECTION_HEADERS;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tshow_bits |= SHOW_SYMBOLS;\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tshow_bits |= SHOW_RELOCATIONS;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"Unrecognized option: %c\\n\", c);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind >= argc || !show_bits) {\n\t\treturn usage(argv);\n\t}\n\n\tint out = 0;\n\tint print_names = 0;\n\n\tif (optind + 1 < argc) {\n\t\tprint_names = 1;\n\t}\n\n\tfor (; optind < argc; optind++) {\n\t\tFILE * f = fopen(argv[optind],\"r\");\n\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\tout = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (print_names) {\n\t\t\tprintf(\"\\nFile: %s\\n\", argv[optind]);\n\t\t}\n\n\t\t/**\n\t\t * Validate header.\n\t\t */\n\t\tElf64_Header header;\n\t\tfread(&header, sizeof(Elf64_Header), 1, f);\n\n\t\tif (memcmp(\"\\x7F\" \"ELF\",&header,4)) {\n\t\t\tfprintf(stderr, \"%s: %s: not an elf\\n\", argv[0], argv[optind]);\n\t\t\tout = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (show_bits & SHOW_FILE_HEADER) {\n\t\t\tprintf(\"ELF Header:\\n\");\n\t\t\tprintf(\"  Magic:   %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\\n\",\n\t\t\t\theader.e_ident[0],  header.e_ident[1],  header.e_ident[2],  header.e_ident[3],\n\t\t\t\theader.e_ident[4],  header.e_ident[5],  header.e_ident[6],  header.e_ident[7],\n\t\t\t\theader.e_ident[8],  header.e_ident[9],  header.e_ident[10], header.e_ident[11],\n\t\t\t\theader.e_ident[12], header.e_ident[13], header.e_ident[14], header.e_ident[15]);\n\t\t\tprintf(\"  Class:                             %s\\n\", elf_classToStr(header.e_ident[EI_CLASS]));\n\t\t\tprintf(\"  Data:                              %s\\n\", elf_dataToStr(header.e_ident[EI_DATA]));\n\t\t\tprintf(\"  Version:                           %s\\n\", elf_versionToStr(header.e_ident[EI_VERSION]));\n\t\t\tprintf(\"  OS/ABI:                            %s\\n\", elf_osabiToStr(header.e_ident[EI_OSABI]));\n\t\t\tprintf(\"  ABI Version:                       %u\\n\", header.e_ident[EI_ABIVERSION]);\n\t\t}\n\n\t\tif (header.e_ident[EI_CLASS] != ELFCLASS64) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (show_bits & SHOW_FILE_HEADER) {\n\t\t\t/* Byte-order dependent from here on out... */\n\t\t\tprintf(\"  Type:                              %s\\n\", elf_typeToStr(header.e_type));\n\t\t\tprintf(\"  Machine:                           %s\\n\", elf_machineToStr(header.e_machine));\n\t\t\tprintf(\"  Version:                           0x%x\\n\", header.e_version);\n\t\t\tprintf(\"  Entry point address:               0x%lx\\n\", header.e_entry);\n\t\t\tprintf(\"  Start of program headers:          %lu (bytes into file)\\n\", header.e_phoff);\n\t\t\tprintf(\"  Start of section headers:          %lu (bytes into file)\\n\", header.e_shoff);\n\t\t\tprintf(\"  Flags:                             0x%x\\n\", header.e_flags);\n\t\t\tprintf(\"  Size of this header:               %u (bytes)\\n\", header.e_ehsize);\n\t\t\tprintf(\"  Size of program headers:           %u (bytes)\\n\", header.e_phentsize);\n\t\t\tprintf(\"  Number of program headers:         %u\\n\", header.e_phnum);\n\t\t\tprintf(\"  Size of section headers:           %u (bytes)\\n\", header.e_shentsize);\n\t\t\tprintf(\"  Number of section headers:         %u\\n\", header.e_shnum);\n\t\t\tprintf(\"  Section header string table index: %u\\n\", header.e_shstrndx);\n\t\t}\n\n\t\t/* Get the section header string table */\n\t\tElf64_Shdr shstr_hdr;\n\t\tfseek(f, header.e_shoff + header.e_shentsize * header.e_shstrndx, SEEK_SET);\n\t\tfread(&shstr_hdr, sizeof(Elf64_Shdr), 1, f);\n\n\t\tstruct StringTable * stringTable = load_string_table(f, &shstr_hdr);\n\n\t\t/**\n\t\t * Section Headers\n\t\t */\n\t\tif (show_bits & SHOW_SECTION_HEADERS) {\n\t\t\tprintf(\"\\nSection Headers:\\n\");\n\t\t\tprintf(\"  [Nr] Name              Type             Address           Offset\\n\");\n\t\t\tprintf(\"       Size              EntSize          Flags  Link  Info  Align\\n\");\n\t\t\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET);\n\t\t\t\tElf64_Shdr sectionHeader;\n\t\t\t\tfread(&sectionHeader, sizeof(Elf64_Shdr), 1, f);\n\n\t\t\t\tprintf(\"  [%2d] %-17.17s %-16.16s %016lx  %08lx\\n\",\n\t\t\t\t\ti, string_from_table(stringTable, sectionHeader.sh_name), sectionHeaderTypeToStr(sectionHeader.sh_type),\n\t\t\t\t\tsectionHeader.sh_addr, sectionHeader.sh_offset);\n\t\t\t\tprintf(\"       %016lx  %016lx %4ld %6d %5d %5ld\\n\",\n\t\t\t\t\tsectionHeader.sh_size, sectionHeader.sh_entsize, sectionHeader.sh_flags,\n\t\t\t\t\tsectionHeader.sh_link, sectionHeader.sh_info, sectionHeader.sh_addralign);\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Program Headers\n\t\t */\n\t\tif (show_bits & SHOW_PROGRAM_HEADERS && header.e_phoff && header.e_phnum) {\n\t\t\tprintf(\"\\nProgram Headers:\\n\");\n\t\t\tprintf(\"  Type           Offset             VirtAddr           PhysAddr\\n\");\n\t\t\tprintf(\"                 FileSiz            MemSiz              Flags  Align\\n\");\n\t\t\tfor (unsigned int i = 0; i < header.e_phnum; ++i) {\n\t\t\t\tfseek(f, header.e_phoff + header.e_phentsize * i, SEEK_SET);\n\t\t\t\tElf64_Phdr programHeader;\n\t\t\t\tfread(&programHeader, sizeof(Elf64_Phdr), 1, f);\n\n\t\t\t\tprintf(\"  %-14.14s 0x%016lx 0x%016lx 0x%016lx\\n\",\n\t\t\t\t\tprogramHeaderTypeToStr(programHeader.p_type),\n\t\t\t\t\tprogramHeader.p_offset, programHeader.p_vaddr, programHeader.p_paddr);\n\t\t\t\tprintf(\"                 0x%016lx 0x%016lx  %s 0x%lx\\n\",\n\t\t\t\t\tprogramHeader.p_filesz, programHeader.p_memsz,\n\t\t\t\t\tprogramHeaderFlagsToStr(programHeader.p_flags), programHeader.p_align);\n\n\t\t\t\tif (programHeader.p_type == PT_INTERP) {\n\t\t\t\t\t/* Read interpreter string */\n\t\t\t\t\tchar * tmp = malloc(programHeader.p_filesz);\n\t\t\t\t\tfseek(f, programHeader.p_offset, SEEK_SET);\n\t\t\t\t\tfread(tmp, programHeader.p_filesz, 1, f);\n\t\t\t\t\tprintf(\"    [Requesting program interpreter: %.*s]\\n\",\n\t\t\t\t\t\t(int)programHeader.p_filesz, tmp);\n\t\t\t\t\tfree(tmp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/* TODO Section to segment mapping? */\n\n\t\t/**\n\t\t * Dump section information.\n\t\t */\n\t\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\t\tfseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET);\n\t\t\tElf64_Shdr sectionHeader;\n\t\t\tfread(&sectionHeader, sizeof(Elf64_Shdr), 1, f);\n\n\t\t\tif (sectionHeader.sh_size > 0x40000000) {\n\t\t\t\t/* Suspiciously large section header... */\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/* I think there should only be one of these... */\n\t\t\tswitch (sectionHeader.sh_type) {\n\t\t\t\tcase SHT_DYNAMIC:\n\t\t\t\t\tif (show_bits & SHOW_DYNAMIC) {\n\t\t\t\t\t\tprintf(\"\\nDynamic section at offset 0x%lx contains (up to) %ld entries:\\n\",\n\t\t\t\t\t\t\tsectionHeader.sh_offset, sectionHeader.sh_size / sectionHeader.sh_entsize);\n\t\t\t\t\t\tprintf(\"  Tag        Type                         Name/Value\\n\");\n\n\t\t\t\t\t\t/* Read the linked string table */\n\t\t\t\t\t\tElf64_Shdr dynstr;\n\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);\n\t\t\t\t\t\tfread(&dynstr, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\t\t\tchar * dynStr = malloc(dynstr.sh_size);\n\t\t\t\t\t\tfseek(f, dynstr.sh_offset, SEEK_SET);\n\t\t\t\t\t\tfread(dynStr, dynstr.sh_size, 1, f);\n\n\t\t\t\t\t\tchar * dynTable = malloc(sectionHeader.sh_size);\n\t\t\t\t\t\tfseek(f, sectionHeader.sh_offset, SEEK_SET);\n\t\t\t\t\t\tfread(dynTable, sectionHeader.sh_size, 1, f);\n\n\t\t\t\t\t\tfor (unsigned int i = 0; i < sectionHeader.sh_size / sectionHeader.sh_entsize; i++) {\n\t\t\t\t\t\t\tElf64_Dyn * dynEntry = (Elf64_Dyn *)(dynTable + sectionHeader.sh_entsize * i);\n\n\t\t\t\t\t\t\tprintf(\" 0x%016lx %s\\n\",\n\t\t\t\t\t\t\t\tdynEntry->d_tag,\n\t\t\t\t\t\t\t\tdynamicTagToStr(dynEntry, dynStr));\n\n\t\t\t\t\t\t\tif (dynEntry->d_tag == DT_NULL) break;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfree(dynStr);\n\t\t\t\t\t\tfree(dynTable);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase SHT_RELA:\n\t\t\t\t\tif (show_bits & SHOW_RELOCATIONS) {\n\t\t\t\t\t\tprintf(\"\\nRelocation section '%s' at offset 0x%lx contains %ld entries.\\n\",\n\t\t\t\t\t\t\tstring_from_table(stringTable, sectionHeader.sh_name), sectionHeader.sh_offset,\n\t\t\t\t\t\t\tsectionHeader.sh_size / sizeof(Elf64_Rela));\n\t\t\t\t\t\tprintf(\"  Offset          Info           Type           Sym. Value    Sym. Name + Addend\\n\");\n\n\t\t\t\t\t\t/* Section this relocation is in */\n\t\t\t\t\t\tElf64_Shdr shdr_this;\n\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_info, SEEK_SET);\n\t\t\t\t\t\tfread(&shdr_this, sizeof(Elf64_Shdr), 1, f);\n\n\t\t\t\t\t\t/* Symbol table link */\n\t\t\t\t\t\tElf64_Shdr shdr_symtab;\n\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);\n\t\t\t\t\t\tfread(&shdr_symtab, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\t\t\tElf64_Sym * symtab = malloc(shdr_symtab.sh_size);\n\t\t\t\t\t\tfseek(f, shdr_symtab.sh_offset, SEEK_SET);\n\t\t\t\t\t\tfread(symtab, shdr_symtab.sh_size, 1, f);\n\n\t\t\t\t\t\t/* Symbol table's string table link */\n\t\t\t\t\t\tElf64_Shdr shdr_strtab;\n\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * shdr_symtab.sh_link, SEEK_SET);\n\t\t\t\t\t\tfread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\t\t\tstruct StringTable * strtab = load_string_table(f, &shdr_strtab);\n\n\t\t\t\t\t\t/* Load relocations from file */\n\t\t\t\t\t\tElf64_Rela * relocations = malloc(sectionHeader.sh_size);\n\t\t\t\t\t\tfseek(f, sectionHeader.sh_offset, SEEK_SET);\n\t\t\t\t\t\tfread((void*)relocations, sectionHeader.sh_size, 1, f);\n\n\t\t\t\t\t\tfor (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Rela); ++i) {\n\t\t\t\t\t\t\tElf64_Shdr shdr;\n\t\t\t\t\t\t\tsize_t offset = ELF64_R_SYM(relocations[i].r_info);\n\t\t\t\t\t\t\tElf64_Xword value = 42;\n\t\t\t\t\t\t\tprintf(\"%012lx  %012lx %-15.15s \",\n\t\t\t\t\t\t\t\trelocations[i].r_offset, relocations[i].r_info,\n\t\t\t\t\t\t\t\trelocationInfoToStr(ELF64_R_TYPE(relocations[i].r_info)));\n\t\t\t\t\t\t\tconst char * symName = \"(null)\";\n\t\t\t\t\t\t\tif (!offset) {\n\t\t\t\t\t\t\t\tprintf(\"                \");\n\t\t\t\t\t\t\t} else if (offset < shdr_symtab.sh_size) {\n\t\t\t\t\t\t\t\tElf64_Sym * this = &symtab[offset];\n\n\t\t\t\t\t\t\t\t/* Get symbol name for this relocation */\n\t\t\t\t\t\t\t\tif ((this->st_info & 0xF) == STT_SECTION) {\n\t\t\t\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * this->st_shndx, SEEK_SET);\n\t\t\t\t\t\t\t\t\tfread(&shdr, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\t\t\t\t\t\tsymName = string_from_table(stringTable, shdr.sh_name);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tsymName = string_from_table(strtab, this->st_name);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tvalue = this->st_value + relocations[i].r_addend;\n\t\t\t\t\t\t\t\tprintf(\"%016lx %s +\", value, symName);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprintf(\" %lx\\n\", relocations[i].r_addend);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfree(relocations);\n\t\t\t\t\t\tfree(strtab);\n\t\t\t\t\t\tfree(symtab);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase SHT_DYNSYM:\n\t\t\t\tcase SHT_SYMTAB:\n\t\t\t\t\tif (show_bits & SHOW_SYMBOLS) {\n\t\t\t\t\t\tprintf(\"\\nSymbol table '%s' contains %ld entries.\\n\",\n\t\t\t\t\t\t\tstring_from_table(stringTable, sectionHeader.sh_name),\n\t\t\t\t\t\t\tsectionHeader.sh_size / sizeof(Elf64_Sym));\n\t\t\t\t\t\tprintf(\"   Num:    Value          Size Type    Bind   Vis      Ndx Name\\n\");\n\n\t\t\t\t\t\tElf64_Sym * symtab = malloc(sectionHeader.sh_size);\n\t\t\t\t\t\tfseek(f, sectionHeader.sh_offset, SEEK_SET);\n\t\t\t\t\t\tfread(symtab, sectionHeader.sh_size, 1, f);\n\n\t\t\t\t\t\tElf64_Shdr shdr_strtab;\n\t\t\t\t\t\tfseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET);\n\t\t\t\t\t\tfread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f);\n\t\t\t\t\t\tstruct StringTable * strtab = load_string_table(f, &shdr_strtab);\n\n\t\t\t\t\t\tfor (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Sym); ++i) {\n\t\t\t\t\t\t\tprintf(\"%6u: %016lx %6lu %-7.7s %-6.6s %-7.7s %4s %s\\n\",\n\t\t\t\t\t\t\t\ti, symtab[i].st_value, symtab[i].st_size,\n\t\t\t\t\t\t\t\tsymbolTypeToStr(symtab[i].st_info & 0xF),\n\t\t\t\t\t\t\t\tsymbolBindToStr(symtab[i].st_info >> 4),\n\t\t\t\t\t\t\t\tsymbolVisToStr(symtab[i].st_other),\n\t\t\t\t\t\t\t\tsymbolNdxToStr(symtab[i].st_shndx),\n\t\t\t\t\t\t\t\tstring_from_table(strtab, symtab[i].st_name));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfree(strtab);\n\t\t\t\t\t\tfree(symtab);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\n\t\t\t}\n\t\t}\n\n\t\tfree(stringTable);\n\t}\n\n\treturn out;\n}\n"
  },
  {
    "path": "apps/readlink.c",
    "content": "/**\n * @brief Examine symlinks and print the path they point to.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015 Mike Gerow\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n\n#define MAX_LINK_SIZE 4096\n\nstatic char usage[] =\n\"Usage: %s LINK\\n\";\n\nint main(int argc, char * argv[]) {\n\tif (argc != 2) {\n\t\tfprintf(stderr, usage, argv[0]);\n\t\texit(EXIT_FAILURE);\n\t}\n\tchar * name = argv[1];\n\n\tchar buf[MAX_LINK_SIZE];\n\tif (readlink(name, buf, sizeof(buf)) < 0) {\n\t\t//perror(\"link\");\n\t\texit(EXIT_FAILURE);\n\t}\n\tfprintf(stdout, \"%s\\n\", buf);\n\texit(EXIT_SUCCESS);\n}\n"
  },
  {
    "path": "apps/reboot.c",
    "content": "/**\n * @brief (Try to) reboot the system.\n *\n * Note that only root can syscall_reboot, and this doesn't\n * do any fancy setuid stuff.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2014 K. Lange\n */\n#include <stdio.h>\n#include <syscall.h>\n\nint main(int argc, char ** argv) {\n\tif (syscall_reboot() < 0) {\n\t\tprintf(\"%s: permission denied\\n\", argv[0]);\n\t}\n\treturn 1;\n}\n"
  },
  {
    "path": "apps/reload_desktop.sh",
    "content": "#!/bin/esh\n\nexport-cmd DESKTOP cat /var/run/.wallpaper.pid\n\nif not empty? \"$DESKTOP\" then kill -SIGUSR2 $DESKTOP\n"
  },
  {
    "path": "apps/reset.c",
    "content": "/**\n * @brief reset - make the terminal sane, and clear it\n *\n * Also clears scrollback!\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\tsystem(\"stty sane\");\n\tprintf(\"\\033c\\033[H\\033[2J\\033[3J\");\n\tfflush(stdout);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/rm.c",
    "content": "/**\n * @brief rm - Unlink files\n *\n * TODO: Support recursive, directory removal, etc.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <dirent.h>\n\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n\n#ifndef IS_MV\n#define APP_NAME \"rm\"\nstatic int recursive = 0;\n#endif\n\nstatic int rm_thing(char * tmp);\n\nstatic int rm_directory(char * source) {\n\tDIR * dirp = opendir(source);\n\tif (dirp == NULL) {\n\t\tfprintf(stderr, \"could not open %s\\n\", source);\n\t\treturn 1;\n\t}\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (!strcmp(ent->d_name,\".\") || !strcmp(ent->d_name,\"..\")) {\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\tchar tmp[strlen(source)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp, \"%s/%s\", source, ent->d_name);\n\t\tint status = rm_thing(tmp);\n\t\tif (status) return status;\n\t\trewinddir(dirp);\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\tint res = unlink(source);\n\tif (res < 0) {\n\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", source, strerror(errno));\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int rm_thing(char * tmp) {\n\tstruct stat statbuf;\n\tlstat(tmp,&statbuf);\n\tif (S_ISDIR(statbuf.st_mode)) {\n\t\tif (!recursive) {\n\t\t\tfprintf(stderr, APP_NAME \": %s: is a directory\\n\", tmp);\n\t\t\treturn 1;\n\t\t}\n\t\treturn rm_directory(tmp);\n\t} else {\n\t\tint res = unlink(tmp);\n\t\tif (res < 0) {\n\t\t\tfprintf(stderr, APP_NAME \": %s: %s\\n\", tmp, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\treturn 0;\n\t}\n}\n\n#ifndef IS_MV\nstatic int rm_top_level(char **argv, int argc, int optind) {\n\tint ret = 0;\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tret |= rm_thing(argv[i]);\n\t}\n\n\treturn ret;\n}\n\nint main(int argc, char * argv[]) {\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"fr\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'r':\n\t\t\t\trecursive = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\t/* ignore */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"rm: unrecognized option '%c'\\n\", opt);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn rm_top_level(argv, argc, optind);\n}\n#endif\n"
  },
  {
    "path": "apps/rmdir.c",
    "content": "/**\n * @brief rmdir - remove empty directories\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2026 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <libgen.h>\n#include <errno.h>\n\nstatic int usage(char * argv[]) {\n#define _I \"\\033[3m\"\n#define _E \"\\033[0m\\n\"\n\tfprintf(stderr, \"usage: %s [-p] path...\\n\"\n\t\t\"\\n\"\n\t\t\"Deletes empty directories.\\n\"\n\t\t\"\\n\"\n\t\t\"  -p   \" _I \"Remove parents if also empty\" _E\n\t\t\"  -v   \" _I \"Print directory names when they are successfully removed\" _E\n\t\t\"\\n\", argv[0]);\n#undef _I\n#undef _E\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tint parents = 0;\n\tint verbose = 0;\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"pv\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'p':\n\t\t\t\tparents = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tverbose = 1;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn usage(argv);\n\t\t}\n\t}\n\n\tif (optind == argc) return usage(argv);\n\n\tint ret = 0;\n\twhile (optind < argc) {\n\t\tif (rmdir(argv[optind]) < 0) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\tret |= 1;\n\t\t\toptind++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (verbose) fprintf(stdout, \"%s\\n\", argv[optind]);\n\n\t\tif (parents) {\n\t\t\tchar * parent = dirname(argv[optind]);\n\t\t\twhile (parent && strcmp(parent,\".\") && strcmp(parent,\"/\")) {\n\t\t\t\tif (rmdir(parent) < 0) {\n\t\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], parent, strerror(errno));\n\t\t\t\t\tret |= 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (verbose) fprintf(stdout, \"%s\\n\", parent);\n\n\t\t\t\tparent = dirname(parent);\n\t\t\t}\n\t\t}\n\t\toptind++;\n\t}\n\n\treturn ret;\n\n}\n"
  },
  {
    "path": "apps/serial-console.c",
    "content": "/**\n * @brief serial console\n *\n * Old tool for poking serial ports. Probably doesn't work right\n * anymore since serial ports are now behind PTYs.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2014 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <termios.h>\n#include <fcntl.h>\n#include <sys/wait.h>\n#include <sys/fswait.h>\n\nint fd = 0;\n\nint keep_echo = 0;\nint dos_lines = 0;\nint keep_canon = 0;\n\nstruct termios old;\n\nvoid set_unbuffered() {\n\ttcgetattr(fileno(stdin), &old);\n\tstruct termios new = old;\n\tif (!keep_canon) {\n\t\tnew.c_lflag &= (~ICANON);\n\t}\n\tif (!keep_echo) {\n\t\tnew.c_lflag &= (~ECHO);\n\t}\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\nvoid set_buffered() {\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n}\n\nint show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"Serial client.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-e] [-r] [-c] [device path]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -e     \\033[3mkeep echo enabled\\033[0m\\n\"\n\t\t\t\" -c     \\033[3mkeep canon enabled\\033[0m\\n\"\n\t\t\t\" -r     \\033[3mtransform line feeds to \\\\r\\\\n\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n\treturn 1;\n}\n\nint main(int argc, char ** argv) {\n\n\tint arg = 1;\n\tchar * device;\n\n\twhile (arg < argc) {\n\t\tif (argv[arg][0] != '-') break;\n\t\tif (!strcmp(argv[arg], \"-e\")) {\n\t\t\tkeep_echo = 1;\n\t\t} else if (!strcmp(argv[arg], \"-r\")) {\n\t\t\tdos_lines = 1;\n\t\t} else if (!strcmp(argv[arg], \"-c\")) {\n\t\t\tkeep_canon = 1;\n\t\t} else if (!strcmp(argv[arg], \"-?\")) {\n\t\t\treturn show_usage(argc, argv);\n\t\t} else {\n\t\t\tfprintf(stderr, \"%s: Unrecognized option: %s\\n\", argv[0], argv[arg]);\n\t\t}\n\t\targ++;\n\t}\n\n\tif (arg == argc) {\n\t\tdevice = \"/dev/ttyS0\";\n\t} else {\n\t\tdevice = argv[arg];\n\t}\n\n\tset_unbuffered();\n\n\tfd = open(device, 0, 0);\n\n\tint fds[2] = {STDIN_FILENO, fd};\n\n\twhile (1) {\n\t\tint index = fswait(2, fds);\n\n\t\tif (index == -1) {\n\t\t\tfprintf(stderr, \"serial-console: fswait: erroneous file descriptor\\n\");\n\t\t\tfprintf(stderr, \"serial-console: (did you try to open a file that isn't a serial console?\\n\");\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (index == 0) {\n\t\t\tchar c = fgetc(stdin);\n\t\t\tif (c == 0x1D) { /* ^] */\n\t\t\t\twhile (1) {\n\t\t\t\t\tprintf(\"serial-console> \");\n\t\t\t\t\tset_buffered();\n\t\t\t\t\tfflush(stdout);\n\n\t\t\t\t\tchar line[1024];\n\t\t\t\t\tfgets(line, 1024, stdin);\n\n\t\t\t\t\tif (feof(stdin)) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tint i = strlen(line);\n\t\t\t\t\tline[i-1] = '\\0';\n\n\t\t\t\t\tif (!strcmp(line, \"quit\")) {\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t} else if (!strcmp(line, \"continue\")) {\n\t\t\t\t\t\tset_unbuffered();\n\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (dos_lines && c == '\\n') {\n\t\t\t\t\tchar buf[1] = {'\\r'};\n\t\t\t\t\twrite(fd, buf, 1);\n\t\t\t\t}\n\t\t\t\tchar buf[1] = {c};\n\t\t\t\twrite(fd, buf, 1);\n\t\t\t}\n\t\t} else {\n\t\t\tchar buf[1024];\n\t\t\tsize_t r = read(fd, buf, 1024);\n\t\t\tfwrite(buf, 1, r, stdout);\n\t\t\tfflush(stdout);\n\t\t}\n\t}\n\n\tclose(fd);\n\tset_buffered();\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/session.c",
    "content": "/**\n * @brief session - UI session manager\n *\n * Runs the user's yutanirc or starts up a panel and desktop\n * if they don't have one. Generally run by glogin.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <signal.h>\n#include <errno.h>\n#include <sys/wait.h>\n\nint main(int argc, char * argv[]) {\n\n\tchar path[1024];\n\tchar * home = getenv(\"HOME\");\n\tif (home) {\n\t\tsprintf(path, \"%s/.yutanirc\", home);\n\t\tchar * args[] = {path, NULL};\n\t\texecvp(args[0], args);\n\t}\n\n\t/* Fallback */\n\n\tint _background_pid = fork();\n\tif (!_background_pid) {\n\t\tsprintf(path, \"%s/Desktop\", home);\n\t\tchdir(path);\n\t\tchar * args[] = {\"/bin/file-browser\", \"--wallpaper\", NULL};\n\t\texecvp(args[0], args);\n\t}\n\n\tint _panel_pid = fork();\n\tif (!_panel_pid) {\n\t\tchar * args[] = {\"/bin/panel\", \"--really\", NULL};\n\t\texecvp(args[0], args);\n\t}\n\n\twait(NULL);\n\n\tint pid;\n\tdo {\n\t\tpid = waitpid(-1, NULL, 0);\n\t} while ((pid > 0) || (pid == -1 && errno == EINTR));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/set-resolution.c",
    "content": "/**\n * @brief set-resolution - Change the display resolution.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/ioctl.h>\n#include <kernel/video.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 3) {\n\t\tfprintf(stderr, \"Usage: %s [--initialize DRIVER] WIDTH HEIGHT\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t/* Open framebuffer */\n\tint fd = open(\"/dev/fb0\", O_RDONLY);\n\n\tif (fd < 0) {\n\t\tperror(\"open\");\n\t\treturn 1;\n\t}\n\n\tint i = 1;\n\tint init = 0;\n\tchar * driver = NULL;\n\n\tif (argc > 4 && !strcmp(argv[1],\"--initialize\")) {\n\t\tinit = 1;\n\t\tdriver = argv[2];\n\t\ti += 2;\n\t}\n\n\t/* Prepare ioctl from arguments */\n\tstruct vid_size s;\n\ts.width = atoi(argv[i]);\n\ts.height = atoi(argv[i+1]);\n\n\t/* Send ioctl */\n\tif (init) {\n\t\tchar tmp[100];\n\t\tsprintf(tmp, \"%s,%u,%u\", driver, s.width, s.height);\n\t\tif (ioctl(fd, IO_VID_REINIT, tmp) < 0) {\n\t\t\tperror(\"ioctl\");\n\t\t\treturn 1;\n\t\t}\n\t} else {\n\t\tif (ioctl(fd, IO_VID_SET, &s) < 0) {\n\t\t\tperror(\"ioctl\");\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/set-wallpaper.sh",
    "content": "#!/bin/esh\n\nif empty? \"$1\" then exec sh -c \"echo 'usage: $0 WALLPAPER'\"\nif not stat -Lq \"$1\" then exec sh -c \"echo '$0: $1 does not exist'\"\n\nexport-cmd DESKTOP cat /var/run/.wallpaper.pid\nif empty? \"$DESKTOP\" then sh -c \"echo '$0: No wallpaper running?'\"\n\necho \"wallpaper=$1\" > $HOME/.wallpaper.conf\n\nkill -SIGUSR1 $DESKTOP\n"
  },
  {
    "path": "apps/show-toasts.krk",
    "content": "#!/bin/kuroko\nimport kuroko\nimport os\nimport fileio\nimport time\n\ntime.sleep(2)\n\ntry:\n    let meminfo = {}\n\n    let data\n    with fileio.open('/proc/meminfo','r') as f:\n        data = f.readlines()\n\n    for line in data:\n        if not ':' in line: continue\n        let bits = line.strip().split(':',1)\n        meminfo[bits[0].strip()] = bits[1].strip()\n\n    if 'MemTotal' in meminfo:\n        let kB = int(meminfo['MemTotal'].replace('kB',''))\n        if kB < 1000000:\n            let sock = os.open(\"/dev/pex/toast\",os.O_WRONLY)\n            let msg = '{\"icon\":\"/usr/share/icons/48/help.png\",\"body\":\"<b>Low System Memory</b><br>At least 1GiB of RAM is<br>recommended for the Live CD.<br>' + str(kB//1024) + 'MiB was detected.\"}'\n            os.write(sock,msg.encode())\n\ntry:\n    let manifest\n    with fileio.open('/var/msk/manifest','r') as f:\n        manifest = f.readlines()\n\n    let count = 0\n\n    for line in manifest:\n        if line.startswith('['):\n            count++\n\n    if count:\n        let sock = os.open(\"/dev/pex/toast\",os.O_WRONLY)\n        let msg = '{\"icon\":\"/usr/share/icons/48/package.png\",\"body\":\"<b>Packages Available</b><br>' + str(count) + ' package' + ('s' if count > 1 else '') + ' are available from<br>the package repository.\"}'\n        os.write(sock,msg.encode())\n\nreturn 0\n"
  },
  {
    "path": "apps/show-tutorial.sh",
    "content": "#!/bin/sh\n\ntouch ~/.tutorial-shown\nsh -c \"sleep 1; tutorial\" &\n\n"
  },
  {
    "path": "apps/showdialog.c",
    "content": "/**\n * @brief showdialog - show a window with a dialog prompt with buttons\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/button.h>\n#include <toaru/text.h>\n\n#include <sys/utsname.h>\n\n#define BUTTON_HEIGHT 28\n#define BUTTON_WIDTH 86\n#define BUTTON_PADDING 14\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\nstatic sprite_t logo;\n\nstatic int32_t width = 600;\nstatic int32_t height = 150;\n\nstatic char * icon_path;\nstatic char * title_str;\nstatic char * copyright_str[20] = {NULL};\n\nstatic struct TT_Font * _tt_font = NULL;\n\nstatic void draw_string(int y, const char * string, uint32_t color) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\ttt_set_size(_tt_font, 13);\n\ttt_draw_string(ctx, _tt_font, bounds.left_width + 80, bounds.top_height + 30 + y + 13, string, color);\n}\n\nstruct TTKButton _ok = {0};\nstruct TTKButton _cancel = {0};\n\nstatic void redraw(void) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdraw_fill(ctx, rgb(204,204,204));\n\tdraw_sprite(ctx, &logo, bounds.left_width + 20, bounds.top_height + 20);\n\tint offset = 0;\n\n\tfor (char ** copy_str = copyright_str; *copy_str; ++copy_str) {\n\t\tif (**copy_str == '-') {\n\t\t\toffset += 10;\n\t\t} else if (**copy_str == '%') {\n\t\t\tdraw_string(offset, *copy_str+1, rgb(0,0,255));\n\t\t\toffset += 20;\n\t\t} else {\n\t\t\tdraw_string(offset, *copy_str, rgb(0,0,0));\n\t\t\toffset += 20;\n\t\t}\n\t}\n\n\tttk_button_draw(ctx, &_ok);\n\tttk_button_draw(ctx, &_cancel);\n\n\twindow->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE;\n\trender_decorations(window, ctx, title_str);\n\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nstatic void init_default(void) {\n\ttitle_str = \"Dialog Prompt\";\n\ticon_path = \"/usr/share/icons/48/folder.png\";\n\n\tcopyright_str[0] = \"This is a demonstration of a dialog box.\";\n\tcopyright_str[1] = \"You can press \\\"Okay\\\" or \\\"Cancel\\\" or close the window.\";\n}\n\nint in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {\n\tif (me->new_y >= button->y && me->new_y < button->y  + button->height) {\n\t\tif (me->new_x >= button->x && me->new_x < button->x + button->width) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid setup_buttons(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\t_ok.title = \"Okay\";\n\t_ok.width = BUTTON_WIDTH;\n\t_ok.height = BUTTON_HEIGHT;\n\t_ok.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING;\n\t_ok.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n\n\t_cancel.title = \"Cancel\";\n\t_cancel.width = BUTTON_WIDTH;\n\t_cancel.height = BUTTON_HEIGHT;\n\t_cancel.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 2;\n\t_cancel.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\twidth  = w;\n\theight = h;\n\tsetup_buttons();\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n}\n\nvoid set_hilight(struct TTKButton * button, int hilight) {\n\tif (!button && (_ok.hilight || _cancel.hilight)) {\n\t\t_ok.hilight = 0;\n\t\t_cancel.hilight = 0;\n\t\tredraw();\n\t} else if (button && (button->hilight != hilight)) {\n\t\t_ok.hilight = 0;\n\t\t_cancel.hilight = 0;\n\t\tbutton->hilight = hilight;\n\t\tredraw();\n\t}\n}\n\n\nint main(int argc, char * argv[]) {\n\tint req_center_x, req_center_y;\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\t_tt_font = tt_font_from_shm(\"sans-serif\");\n\n\twindow = yutani_window_create_flags(yctx, width + bounds.width, height + bounds.height, YUTANI_WINDOW_FLAG_DIALOG_ANIMATION);\n\treq_center_x = yctx->display_width / 2;\n\treq_center_y = yctx->display_height / 2;\n\n\tif (argc < 2) {\n\t\tinit_default();\n\t} else if (argc < 4) {\n\t\tfprintf(stderr, \"Invalid arguments.\\n\");\n\t\treturn 1;\n\t} else {\n\t\ttitle_str = argv[1];\n\t\ticon_path = argv[2];\n\n\t\tint i = 0;\n\t\tchar * me = argv[3], * end;\n\t\tdo {\n\t\t\tcopyright_str[i] = me;\n\t\t\ti++;\n\t\t\tend = strchr(me,'\\n');\n\t\t\tif (end) {\n\t\t\t\t*end = '\\0';\n\t\t\t\tme = end+1;\n\t\t\t}\n\t\t} while (end);\n\n\t\tif (argc > 6) {\n\t\t\treq_center_x = atoi(argv[5]);\n\t\t\treq_center_y = atoi(argv[6]);\n\t\t}\n\t}\n\n\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\n\tyutani_window_advertise_icon(yctx, window, title_str, \"star\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\tsetup_buttons();\n\tload_sprite(&logo, icon_path);\n\tredraw();\n\n\tstruct TTKButton * _down_button = NULL;\n\n\tint playing = 1;\n\tint status = 0;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == '\\n') {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t\t} else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\tstatus = 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\tstatus = 2;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t\tdecor_get_bounds(window, &bounds);\n\t\t\t\t\t\t\tif (me->new_y > bounds.top_height) {\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_ok, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_ok, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_ok;\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_cancel, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_cancel, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_cancel;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\t\t\tif (_down_button) {\n\t\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\t\tif (_down_button == &_cancel) {\n\t\t\t\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tstatus  = 1;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button == &_ok) {\n\t\t\t\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t_down_button->hilight = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t_down_button = NULL;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_ok, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_ok, 1);\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_cancel, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_cancel, 1);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL,0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (_down_button) {\n\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(_down_button, 2);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL, 0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tstatus = 2;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn status;\n}\n"
  },
  {
    "path": "apps/sleep.c",
    "content": "/**\n * @brief sleep - Do nothing, efficiently.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <syscall.h>\n#include <string.h>\n\nint main(int argc, char ** argv) {\n\tint ret = 0;\n\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: missing operand\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tchar * arg = strdup(argv[1]);\n\n\tfloat time = atof(arg);\n\n\tunsigned int seconds = (unsigned int)time;\n\tunsigned int subsecs = (unsigned int)((time - (float)seconds) * 100);\n\n\tret = syscall_sleep(seconds, subsecs);\n\n\treturn ret;\n}\n\n"
  },
  {
    "path": "apps/snow.c",
    "content": "/**\n * @brief Draw pretty falling snowflakes.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n#include <time.h>\n#include <sched.h>\n#include <math.h>\n\n#include <sys/fswait.h>\n#include <sys/time.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx;\nstatic int should_exit = 0;\nstatic sprite_t snowflake;\n\n#define FLAKES 40\n#define FALL_SPEED 3\n\nstruct {\n\tint16_t x;\n\tint16_t y;\n\tuint8_t rotation;\n\tuint8_t alpha;\n\tint8_t wind;\n\tuint8_t exists;\n} flakes[FLAKES];\n\nint flakes_made = 0;\n\nstatic void add_flake() {\n\tfor (int i = 0; i < FLAKES; ++i) {\n\t\tif (!flakes[i].exists) {\n\t\t\tflakes[i].exists = 1;\n\t\t\tflakes[i].y = -snowflake.height / 2;\n\t\t\tflakes[i].x = rand() % (ctx->width);\n\t\t\tflakes[i].alpha = rand() % 50 + 50;\n\t\t\tflakes[i].rotation = rand() % 255;\n\t\t\tflakes[i].wind = (rand() % 6) - 3;\n\t\t\treturn;\n\t\t}\n\t}\n}\n\nstatic void draw(void) {\n\tdraw_fill(ctx, 0);\n\tfor (int i = 0; i < FLAKES; ++i) {\n\t\tif (flakes[i].exists) {\n\t\t\tdraw_sprite_rotate(ctx, &snowflake,\n\t\t\t\t\tflakes[i].x, flakes[i].y,\n\t\t\t\t\t(float)(flakes[i].rotation)  / 100.0,\n\t\t\t\t\t(float)(flakes[i].alpha) / 100.0);\n\t\t\tflakes[i].y += FALL_SPEED;\n\t\t\tflakes[i].x += flakes[i].wind;\n\t\t\tif (flakes[i].y >= ctx->height + snowflake.height / 2 ||\n\t\t\t\tflakes[i].x <= -snowflake.width / 2 ||\n\t\t\t\tflakes[i].x >= ctx->width + snowflake.width / 2) {\n\t\t\t\tflakes[i].exists = 0;\n\t\t\t\tadd_flake();\n\t\t\t}\n\t\t}\n\t}\n\tflip(ctx);\n\tyutani_flip(yctx, wina);\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, wina, w, h);\n\treinit_graphics_yutani(ctx, wina);\n\tdraw();\n\tyutani_window_resize_done(yctx, wina);\n}\n\nuint64_t last_flake = 0;\n\nstatic uint64_t precise_current_time(void) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttime_t sec_diff = t.tv_sec;\n\tsuseconds_t usec_diff = t.tv_usec;\n\n\treturn (uint64_t)(sec_diff * 1000 + usec_diff / 1000);\n}\n\nstatic uint64_t precise_time_since(uint64_t start_time) {\n\n\tuint32_t now = precise_current_time();\n\tuint32_t diff = now - start_time; /* Milliseconds */\n\n\treturn diff;\n}\n\nint main (int argc, char ** argv) {\n\tsrand(time(NULL));\n\tmemset(&flakes, 0, sizeof(flakes));\n\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tload_sprite(&snowflake, \"/usr/share/snowflake.bmp\");\n\n\twina = yutani_window_create(yctx, 100, 100);\n\tif (argc < 2 || strcmp(argv[1],\"--no-ad\")) {\n\t\tyutani_window_advertise(yctx, wina, \"snow\");\n\t}\n\tyutani_special_request(yctx, wina, YUTANI_SPECIAL_REQUEST_MAXIMIZE);\n\tyutani_window_update_shape(yctx, wina, 256);\n\n\tctx = init_graphics_yutani_double_buffer(wina);\n\tdraw_fill(ctx, rgba(0,0,0,0));\n\tflip(ctx);\n\n\twhile (!should_exit) {\n\t\tint fds[1] = {fileno(yctx->sock)};\n\t\tint index = fswait2(1,fds,10);\n\t\tif (index == 0) {\n\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\twhile (m) {\n\t\t\t\tswitch (m->type) {\n\t\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {\n\t\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\t\tsched_yield();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\tyutani_window_drag_start(yctx, wina);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfree(m);\n\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t}\n\t\t} else {\n\t\t\tif (flakes_made < 20 && precise_time_since(last_flake) > 1000) {\n\t\t\t\tadd_flake();\n\t\t\t\tflakes_made += 1;\n\t\t\t\tlast_flake = precise_current_time();\n\t\t\t}\n\t\t}\n\t\tdraw();\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/sort.c",
    "content": "/**\n * @brief Sort input lines.\n *\n * XXX for reasons unknown this is using its own insertion-sort\n *     instead of our much nicer quicksort?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <getopt.h>\n#include <errno.h>\n#include <ctype.h>\n#include <toaru/list.h>\n\nint compare(const char * a, const char * b) {\n\twhile (1) {\n\t\twhile (*a == *b || tolower(*a) == tolower(*b)) {\n\t\t\tif (!*a) return 0;\n\t\t\ta++;\n\t\t\tb++;\n\t\t}\n\n\t\twhile (*a && !isalnum(*a)) a++;\n\t\twhile (*b && !isalnum(*b)) b++;\n\n\t\tif (tolower(*a) == tolower(*b)) continue;\n\n\t\tif (tolower(*a) < tolower(*b)) return -1;\n\t\treturn 1;\n\t}\n}\n\nint main(int argc, char * argv[]) {\n\tint reverse = 0;\n\tint opt;\n\n\tlist_t * lines = list_create();\n\tlist_t * files = list_create();\n\n\twhile ((opt = getopt(argc, argv, \"r\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'r':\n\t\t\t\treverse = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind == argc) {\n\t\t/* No arguments */\n\t\tlist_insert(files, stdin);\n\t} else {\n\t\twhile (optind < argc) {\n\t\t\tFILE * f = fopen(argv[optind], \"r\");\n\t\t\tif (!f) {\n\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[optind], strerror(errno));\n\t\t\t} else {\n\t\t\t\tlist_insert(files, f);\n\t\t\t}\n\t\t\toptind++;\n\t\t}\n\t}\n\n\tchar line_buf[4096] = {0};\n\tforeach (node, files) {\n\t\tFILE * f = node->value;\n\t\twhile (!feof(f)) {\n\t\t\tif (!fgets(line_buf, 4096, f)) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!strchr(line_buf,'\\n')) {\n\t\t\t\tfprintf(stderr, \"%s: oversized line\\n\", argv[0]);\n\t\t\t}\n\t\t\tchar * line = strdup(line_buf);\n\t\t\tnode_t * next = NULL;\n\t\t\tforeach (lnode, lines) {\n\t\t\t\tchar * cmp = lnode->value;\n\t\t\t\tif (reverse ? (compare(cmp, line) < 0) : (compare(line, cmp) < 0)) {\n\t\t\t\t\tnext = lnode;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (next) {\n\t\t\t\tlist_insert_before(lines, next, line);\n\t\t\t} else {\n\t\t\t\tlist_insert(lines, line);\n\t\t\t}\n\t\t}\n\t}\n\n\tforeach (lnode, lines) {\n\t\tchar * line = lnode->value;\n\t\tfprintf(stdout, \"%s\", line);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/splash-log.c",
    "content": "/**\n * @brief Console log manager.\n *\n * Presents a PEX endpoint for startup processes to write log\n * messages to and will only send them to the console if the\n * debug flag is set or 2 seconds have elapsed since we started.\n *\n * This also makes message writes a bit more asynchonrous, which\n * is useful because the framebuffer console output can be quite\n * slow and we don't want to slow down start up processes...\n *\n * The downside to that is that splash-log may have to play catch-up\n * and could still be spewing messages to the console after startup\n * has finished...\n *\n * This used to do a lot more work, as it managed both graphical\n * output of messages and output to the VGA terminal, but that has\n * all moved back into the kernel's 'fbterm', so now we're just\n * a message buffer.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <signal.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <sys/utsname.h>\n#include <sys/times.h>\n#include <sys/fswait.h>\n\n#include <kernel/video.h>\n#include <toaru/pex.h>\n#include <toaru/hashmap.h>\n\n#define TIMEOUT_SECS 2\n\nstatic FILE * console;\n\nstatic void update_message(char * c) {\n\tfprintf(console, \"%s\\n\", c);\n}\n\nstatic FILE * pex_endpoint = NULL;\nstatic void open_socket(void) {\n\tpex_endpoint = pex_bind(\"splash\");\n\tif (!pex_endpoint) exit(1);\n}\n\nstatic void say_hello(void) {\n\t/* Get our release version */\n\tstruct utsname u;\n\tuname(&u);\n\t/* Strip git tag */\n\tchar * tmp = strstr(u.release, \"-\");\n\tif (tmp) *tmp = '\\0';\n\t/* Setup hello message */\n\tchar hello_msg[512];\n\tsnprintf(hello_msg, 511, \"ToaruOS %s is starting up...\", u.release);\n\t/* Add it to the log */\n\tupdate_message(hello_msg);\n}\n\n#include \"../kernel/misc/args.c\"\nstatic hashmap_t * get_cmdline(void) {\n\tchar * results = args_from_procfs();\n\tif (results) free(results);\n\treturn kernel_args_map;\n}\n\nint main(int argc, char * argv[]) {\n\tif (getuid() != 0) {\n\t\tfprintf(stderr, \"%s: only root should run this\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tif (!fork()) {\n\t\thashmap_t * cmdline = get_cmdline();\n\n\t\tint quiet = 0;\n\t\tchar * last_message = NULL;\n\t\tclock_t start = times(NULL);\n\n\t\tif (!hashmap_has(cmdline, \"debug\")) {\n\t\t\tquiet = 1;\n\t\t}\n\n\t\topen_socket();\n\t\tconsole = fopen(\"/dev/console\",\"a\");\n\n\t\tif (!quiet) say_hello();\n\n\t\twhile (1) {\n\t\t\tint pex_fd[] = {fileno(pex_endpoint)};\n\t\t\tint index = fswait2(1, pex_fd, 100);\n\n\t\t\tif (index == 0) {\n\t\t\t\tpex_packet_t * p = calloc(PACKET_SIZE, 1);\n\t\t\t\tpex_listen(pex_endpoint, p);\n\n\t\t\t\tif (p->size < 4)  { free(p); continue; } /* Ignore blank messages, erroneous line feeds, etc. */\n\t\t\t\tif (p->size > 80) { free(p); continue; } /* Ignore overly large messages */\n\n\t\t\t\tif (!strncmp((char*)p->data, \"!quit\", 5)) {\n\t\t\t\t\t/* Use the special message !quit to exit. */\n\t\t\t\t\tfclose(pex_endpoint);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\n\t\t\t\tif (!quiet) {\n\t\t\t\t\tp->data[p->size] = '\\0';\n\t\t\t\t\tupdate_message((char*)p->data + (p->data[0] == ':' ? 1 : 0));\n\t\t\t\t}\n\n\t\t\t\tif (last_message) {\n\t\t\t\t\tfree(last_message);\n\t\t\t\t\tlast_message = NULL;\n\t\t\t\t}\n\n\t\t\t\tif (quiet) {\n\t\t\t\t\tlast_message = strdup((char*)p->data + (p->data[0] == ':' ? 1 : 0));\n\t\t\t\t}\n\n\t\t\t\tfree(p);\n\t\t\t} else if (quiet && times(NULL) - start > TIMEOUT_SECS * 1000000L) {\n\t\t\t\tquiet = 0;\n\t\t\t\tif (last_message) {\n\t\t\t\t\tupdate_message(\"Startup is taking a while, enabling log. Last message was:\");\n\t\t\t\t\tupdate_message(last_message);\n\t\t\t\t\tfree(last_message);\n\t\t\t\t\tlast_message = NULL;\n\t\t\t\t} else {\n\t\t\t\t\tupdate_message(\"Startup is taking a while, enabling log.\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/stat.c",
    "content": "/**\n * @brief Display file status.\n *\n * The format for this is terrible and we're missing a bunch\n * of data we provide in our statbuf...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <time.h>\n\n#include <sys/stat.h>\n#include <sys/time.h>\n\nstatic void show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"stat - display file status\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-Lq] PATH\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -L     \\033[3mdereference symlinks\\033[0m\\n\"\n\t\t\t\" -q     \\033[3mdon't print anything, just return 0 if file exists\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nstatic int dereference = 0, quiet = 0;\n\nstatic int stat_file(char * file) {\n\n\tstruct stat _stat;\n\tint result;\n\n\tif (dereference) {\n\t\tresult = stat(file, &_stat);\n\t} else {\n\t\tresult = lstat(file, &_stat);\n\t}\n\n\tif (result == -1) {\n\t\tif (!quiet) {\n\t\t\tfprintf(stderr, \"stat: %s: %s\\n\", file, strerror(errno));\n\t\t}\n\t\treturn 1;\n\t}\n\n\tif (quiet) return 0;\n\n\tconst char * file_type = \"regular file\";\n\tif (S_ISDIR(_stat.st_mode))        file_type = \"directory\";\n\telse if (S_ISFIFO(_stat.st_mode))  file_type = \"fifo\";\n\telse if (S_ISLNK(_stat.st_mode))   file_type = \"symbolic link\";\n\telse if (S_ISBLK(_stat.st_mode))   file_type = \"block device\";\n\telse if (S_ISCHR(_stat.st_mode))   file_type = \"character device\";\n\n\tstruct stat * f = &_stat;\n\n\tprintf(\"  File: %s\\n\", file);\n\t/* TODO: st_blocks is not being set, skip it */\n\tprintf(\"  Size: %-10lu %s\\n\", f->st_size, file_type);\n\tprintf(\"Device: %-10lu Inode: %-10lu  Links: %u\\n\", f->st_dev, f->st_ino, f->st_nlink);\n\tprintf(\"Access: \");\n\t/* Copied from apps/ls.c */\n\tif (S_ISLNK(f->st_mode))       { printf(\"l\"); }\n\telse if (S_ISCHR(f->st_mode))  { printf(\"c\"); }\n\telse if (S_ISBLK(f->st_mode))  { printf(\"b\"); }\n\telse if (S_ISDIR(f->st_mode))  { printf(\"d\"); }\n\telse { printf(\"-\"); }\n\tprintf( (f->st_mode & S_IRUSR) ? \"r\" : \"-\");\n\tprintf( (f->st_mode & S_IWUSR) ? \"w\" : \"-\");\n\tprintf( (f->st_mode & S_ISUID) ? \"s\" : ((f->st_mode & S_IXUSR) ? \"x\" : \"-\"));\n\tprintf( (f->st_mode & S_IRGRP) ? \"r\" : \"-\");\n\tprintf( (f->st_mode & S_IWGRP) ? \"w\" : \"-\");\n\tprintf( (f->st_mode & S_IXGRP) ? \"x\" : \"-\");\n\tprintf( (f->st_mode & S_IROTH) ? \"r\" : \"-\");\n\tprintf( (f->st_mode & S_IWOTH) ? \"w\" : \"-\");\n\tprintf( (f->st_mode & S_IXOTH) ? \"x\" : \"-\");\n\tprintf(\" Uid: %-8u Gid: %-8u\\n\", f->st_uid, f->st_gid);\n\n\tchar time_buf[80];\n\tstrftime(time_buf, 80, \"%c\", localtime((time_t*)&f->st_atime));\n\tprintf(\"Access: %s\\n\", time_buf);\n\tstrftime(time_buf, 80, \"%c\", localtime((time_t*)&f->st_mtime));\n\tprintf(\"Modify: %s\\n\", time_buf);\n\tstrftime(time_buf, 80, \"%c\", localtime((time_t*)&f->st_ctime));\n\tprintf(\"Change: %s\\n\", time_buf);\n\n\treturn 0;\n\n}\n\nint main(int argc, char ** argv) {\n\tint opt;\n\n\twhile ((opt = getopt(argc, argv, \"?Lq\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'L':\n\t\t\t\tdereference = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'q':\n\t\t\t\tquiet = 1;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc,argv);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tif (optind >= argc) {\n\t\tshow_usage(argc, argv);\n\t\treturn 1;\n\t}\n\n\tint ret = 0;\n\n\twhile (optind < argc) {\n\t\tret |= stat_file(argv[optind]);\n\t\toptind++;\n\t}\n\n\treturn ret;\n\n}\n\n"
  },
  {
    "path": "apps/strace.c",
    "content": "/**\n * @brief Process system call tracer.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <signal.h>\n#include <sys/ptrace.h>\n#include <sys/wait.h>\n#include <sys/signal.h>\n#include <sys/signal_defs.h>\n#include <sys/sysfunc.h>\n#include <sys/utsname.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <sys/uregs.h>\n#include <syscall_nums.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n\nstatic FILE * logfile;\n\n/* System call names */\nconst char * syscall_names[] = {\n\t[SYS_EXT]          = \"exit\",\n\t[SYS_GETEUID]      = \"geteuid\",\n\t[SYS_OPEN]         = \"open\",\n\t[SYS_READ]         = \"read\",\n\t[SYS_WRITE]        = \"write\",\n\t[SYS_CLOSE]        = \"close\",\n\t[SYS_GETTIMEOFDAY] = \"gettimeofday\",\n\t[SYS_GETPID]       = \"getpid\",\n\t[SYS_SBRK]         = \"sbrk\",\n\t[SYS_UNAME]        = \"uname\",\n\t[SYS_SEEK]         = \"seek\",\n\t[SYS_STAT]         = \"stat\",\n\t[SYS_GETUID]       = \"getuid\",\n\t[SYS_SETUID]       = \"setuid\",\n\t[SYS_READDIR]      = \"readdir\",\n\t[SYS_CHDIR]        = \"chdir\",\n\t[SYS_GETCWD]       = \"getcwd\",\n\t[SYS_SETHOSTNAME]  = \"sethostname\",\n\t[SYS_GETHOSTNAME]  = \"gethostname\",\n\t[SYS_MKDIR]        = \"mkdir\",\n\t[SYS_GETTID]       = \"gettid\",\n\t[SYS_SYSFUNC]      = \"sysfunc\",\n\t[SYS_IOCTL]        = \"ioctl\",\n\t[SYS_ACCESS]       = \"access\",\n\t[SYS_STATF]        = \"statf\",\n\t[SYS_CHMOD]        = \"chmod\",\n\t[SYS_UMASK]        = \"umask\",\n\t[SYS_UNLINK]       = \"unlink\",\n\t[SYS_MOUNT]        = \"mount\",\n\t[SYS_SYMLINK]      = \"symlink\",\n\t[SYS_READLINK]     = \"readlink\",\n\t[SYS_LSTAT]        = \"lstat\",\n\t[SYS_CHOWN]        = \"chown\",\n\t[SYS_SETSID]       = \"setsid\",\n\t[SYS_SETPGID]      = \"setpgid\",\n\t[SYS_GETPGID]      = \"getpgid\",\n\t[SYS_DUP2]         = \"dup2\",\n\t[SYS_EXECVE]       = \"execve\",\n\t[SYS_FORK]         = \"fork\",\n\t[SYS_WAITPID]      = \"waitpid\",\n\t[SYS_YIELD]        = \"yield\",\n\t[SYS_SLEEPABS]     = \"sleepabs\",\n\t[SYS_SLEEP]        = \"sleep\",\n\t[SYS_PIPE]         = \"pipe\",\n\t[SYS_FSWAIT]       = \"fswait\",\n\t[SYS_FSWAIT2]      = \"fswait_timeout\",\n\t[SYS_FSWAIT3]      = \"fswait_multi\",\n\t[SYS_CLONE]        = \"clone\",\n\t[SYS_OPENPTY]      = \"openpty\",\n\t[SYS_SHM_OBTAIN]   = \"shm_obtain\",\n\t[SYS_SHM_RELEASE]  = \"shm_release\",\n\t[SYS_SIGNAL]       = \"signal\",\n\t[SYS_KILL]         = \"kill\",\n\t[SYS_REBOOT]       = \"reboot\",\n\t[SYS_GETGID]       = \"getgid\",\n\t[SYS_GETEGID]      = \"getegid\",\n\t[SYS_SETGID]       = \"setgid\",\n\t[SYS_GETGROUPS]    = \"getgroups\",\n\t[SYS_SETGROUPS]    = \"setgroups\",\n\t[SYS_TIMES]        = \"times\",\n\t[SYS_PTRACE]       = \"ptrace\",\n\t[SYS_SOCKET]       = \"socket\",\n\t[SYS_SETSOCKOPT]   = \"setsockopt\",\n\t[SYS_BIND]         = \"bind\",\n\t[SYS_ACCEPT]       = \"accept\",\n\t[SYS_LISTEN]       = \"listen\",\n\t[SYS_CONNECT]      = \"connect\",\n\t[SYS_GETSOCKOPT]   = \"getsockopt\",\n\t[SYS_RECV]         = \"recv\",\n\t[SYS_SEND]         = \"send\",\n\t[SYS_SHUTDOWN]     = \"shutdown\",\n\t[SYS_SIGACTION]    = \"sigaction\",\n\t[SYS_SIGPENDING]   = \"sigpending\",\n\t[SYS_SIGPROCMASK]  = \"sigprocmask\",\n\t[SYS_SIGSUSPEND]   = \"sigsuspend\",\n\t[SYS_SIGWAIT]      = \"sigwait\",\n\t[SYS_PREAD]        = \"pread\",\n\t[SYS_PWRITE]       = \"pwrite\",\n\t[SYS_RENAME]       = \"rename\",\n\t[SYS_FCNTL]        = \"fcntl\",\n\t[SYS_FCHMOD]       = \"fchmod\",\n\t[SYS_FCHOWN]       = \"fchown\",\n\t[SYS_TRUNCATE]     = \"truncate\",\n\t[SYS_FTRUNCATE]    = \"ftruncate\",\n\t[SYS_SETTIMEOFDAY] = \"settimeofday\",\n\t[SYS_GETSOCKNAME]  = \"getsockname\",\n\t[SYS_GETPEERNAME]  = \"getpeername\",\n};\n\nchar syscall_mask[] = {\n\t[SYS_EXT]          = 1,\n\t[SYS_GETEUID]      = 1,\n\t[SYS_OPEN]         = 1,\n\t[SYS_READ]         = 1,\n\t[SYS_WRITE]        = 1,\n\t[SYS_CLOSE]        = 1,\n\t[SYS_GETTIMEOFDAY] = 1,\n\t[SYS_GETPID]       = 1,\n\t[SYS_SBRK]         = 1,\n\t[SYS_UNAME]        = 1,\n\t[SYS_SEEK]         = 1,\n\t[SYS_STAT]         = 1,\n\t[SYS_GETUID]       = 1,\n\t[SYS_SETUID]       = 1,\n\t[SYS_READDIR]      = 1,\n\t[SYS_CHDIR]        = 1,\n\t[SYS_GETCWD]       = 1,\n\t[SYS_SETHOSTNAME]  = 1,\n\t[SYS_GETHOSTNAME]  = 1,\n\t[SYS_MKDIR]        = 1,\n\t[SYS_GETTID]       = 1,\n\t[SYS_SYSFUNC]      = 1,\n\t[SYS_IOCTL]        = 1,\n\t[SYS_ACCESS]       = 1,\n\t[SYS_STATF]        = 1,\n\t[SYS_CHMOD]        = 1,\n\t[SYS_UMASK]        = 1,\n\t[SYS_UNLINK]       = 1,\n\t[SYS_MOUNT]        = 1,\n\t[SYS_SYMLINK]      = 1,\n\t[SYS_READLINK]     = 1,\n\t[SYS_LSTAT]        = 1,\n\t[SYS_CHOWN]        = 1,\n\t[SYS_SETSID]       = 1,\n\t[SYS_SETPGID]      = 1,\n\t[SYS_GETPGID]      = 1,\n\t[SYS_DUP2]         = 1,\n\t[SYS_EXECVE]       = 1,\n\t[SYS_FORK]         = 1,\n\t[SYS_WAITPID]      = 1,\n\t[SYS_YIELD]        = 1,\n\t[SYS_SLEEPABS]     = 1,\n\t[SYS_SLEEP]        = 1,\n\t[SYS_PIPE]         = 1,\n\t[SYS_FSWAIT]       = 1,\n\t[SYS_FSWAIT2]      = 1,\n\t[SYS_FSWAIT3]      = 1,\n\t[SYS_CLONE]        = 1,\n\t[SYS_OPENPTY]      = 1,\n\t[SYS_SHM_OBTAIN]   = 1,\n\t[SYS_SHM_RELEASE]  = 1,\n\t[SYS_SIGNAL]       = 1,\n\t[SYS_KILL]         = 1,\n\t[SYS_REBOOT]       = 1,\n\t[SYS_GETGID]       = 1,\n\t[SYS_GETEGID]      = 1,\n\t[SYS_SETGID]       = 1,\n\t[SYS_GETGROUPS]    = 1,\n\t[SYS_SETGROUPS]    = 1,\n\t[SYS_TIMES]        = 1,\n\t[SYS_PTRACE]       = 1,\n\t[SYS_SOCKET]       = 1,\n\t[SYS_SETSOCKOPT]   = 1,\n\t[SYS_BIND]         = 1,\n\t[SYS_ACCEPT]       = 1,\n\t[SYS_LISTEN]       = 1,\n\t[SYS_CONNECT]      = 1,\n\t[SYS_GETSOCKOPT]   = 1,\n\t[SYS_RECV]         = 1,\n\t[SYS_SEND]         = 1,\n\t[SYS_SHUTDOWN]     = 1,\n\t[SYS_PREAD]        = 1,\n\t[SYS_PWRITE]       = 1,\n\t[SYS_SIGACTION]    = 1,\n\t[SYS_SIGPENDING]   = 1,\n\t[SYS_SIGPROCMASK]  = 1,\n\t[SYS_SIGSUSPEND]   = 1,\n\t[SYS_SIGWAIT]      = 1,\n\t[SYS_RENAME]       = 1,\n\t[SYS_FCNTL]        = 1,\n\t[SYS_FCHMOD]       = 1,\n\t[SYS_FCHOWN]       = 1,\n\t[SYS_TRUNCATE]     = 1,\n\t[SYS_FTRUNCATE]    = 1,\n\t[SYS_SETTIMEOFDAY] = 1,\n\t[SYS_GETSOCKNAME]  = 1,\n\t[SYS_GETPEERNAME]  = 1,\n};\n\n#define M(e) [e] = #e\nconst char * errno_names[] = {\n\tM(EPERM),\n\tM(ENOENT),\n\tM(ESRCH),\n\tM(EINTR),\n\tM(EIO),\n\tM(ENXIO),\n\tM(E2BIG),\n\tM(ENOEXEC),\n\tM(EBADF),\n\tM(ECHILD),\n\tM(EAGAIN),\n\tM(ENOMEM),\n\tM(EACCES),\n\tM(EFAULT),\n\tM(ENOTBLK),\n\tM(EBUSY),\n\tM(EEXIST),\n\tM(EXDEV),\n\tM(ENODEV),\n\tM(ENOTDIR),\n\tM(EISDIR),\n\tM(EINVAL),\n\tM(ENFILE),\n\tM(EMFILE),\n\tM(ENOTTY),\n\tM(ETXTBSY),\n\tM(EFBIG),\n\tM(ENOSPC),\n\tM(ESPIPE),\n\tM(EROFS),\n\tM(EMLINK),\n\tM(EPIPE),\n\tM(EDOM),\n\tM(ERANGE),\n\tM(ENOMSG),\n\tM(EIDRM),\n\tM(ECHRNG),\n\tM(EL2NSYNC),\n\tM(EL3HLT),\n\tM(EL3RST),\n\tM(ELNRNG),\n\tM(EUNATCH),\n\tM(ENOCSI),\n\tM(EL2HLT),\n\tM(EDEADLK),\n\tM(ENOLCK),\n\tM(EBADE),\n\tM(EBADR),\n\tM(EXFULL),\n\tM(ENOANO),\n\tM(EBADRQC),\n\tM(EBADSLT),\n\tM(EDEADLOCK),\n\tM(EBFONT),\n\tM(ENOSTR),\n\tM(ENODATA),\n\tM(ETIME),\n\tM(ENOSR),\n\tM(ENONET),\n\tM(ENOPKG),\n\tM(EREMOTE),\n\tM(ENOLINK),\n\tM(EADV),\n\tM(ESRMNT),\n\tM(ECOMM),\n\tM(EPROTO),\n\tM(EMULTIHOP),\n\tM(ELBIN),\n\tM(EDOTDOT),\n\tM(EBADMSG),\n\tM(EFTYPE),\n\tM(ENOTUNIQ),\n\tM(EBADFD),\n\tM(EREMCHG),\n\tM(ELIBACC),\n\tM(ELIBBAD),\n\tM(ELIBSCN),\n\tM(ELIBMAX),\n\tM(ELIBEXEC),\n\tM(ENOSYS),\n\tM(ENOTEMPTY),\n\tM(ENAMETOOLONG),\n\tM(ELOOP),\n\tM(EOPNOTSUPP),\n\tM(EPFNOSUPPORT),\n\tM(ECONNRESET),\n\tM(ENOBUFS),\n\tM(EAFNOSUPPORT),\n\tM(EPROTOTYPE),\n\tM(ENOTSOCK),\n\tM(ENOPROTOOPT),\n\tM(ESHUTDOWN),\n\tM(ECONNREFUSED),\n\tM(EADDRINUSE),\n\tM(ECONNABORTED),\n\tM(ENETUNREACH),\n\tM(ENETDOWN),\n\tM(ETIMEDOUT),\n\tM(EHOSTDOWN),\n\tM(EHOSTUNREACH),\n\tM(EINPROGRESS),\n\tM(EALREADY),\n\tM(EDESTADDRREQ),\n\tM(EMSGSIZE),\n\tM(EPROTONOSUPPORT),\n\tM(ESOCKTNOSUPPORT),\n\tM(EADDRNOTAVAIL),\n\tM(EISCONN),\n\tM(ENOTCONN),\n\tM(ENOTSUP),\n\tM(EOVERFLOW),\n\tM(ECANCELED),\n\tM(ENOTRECOVERABLE),\n\tM(EOWNERDEAD),\n\tM(ESTRPIPE),\n\tM(ERESTARTSYS),\n\tM(ERESTARTSIGSUSPEND),\n};\n\n\nconst char * signal_names[NSIG] = {\n\tM(SIGHUP),\n\tM(SIGINT),\n\tM(SIGQUIT),\n\tM(SIGILL),\n\tM(SIGTRAP),\n\tM(SIGABRT),\n\tM(SIGEMT),\n\tM(SIGFPE),\n\tM(SIGKILL),\n\tM(SIGBUS),\n\tM(SIGSEGV),\n\tM(SIGSYS),\n\tM(SIGPIPE),\n\tM(SIGALRM),\n\tM(SIGTERM),\n\tM(SIGUSR1),\n\tM(SIGUSR2),\n\tM(SIGCHLD),\n\tM(SIGPWR),\n\tM(SIGWINCH),\n\tM(SIGURG),\n\tM(SIGPOLL),\n\tM(SIGSTOP),\n\tM(SIGTSTP),\n\tM(SIGCONT),\n\tM(SIGTTIN),\n\tM(SIGTTOUT),\n\tM(SIGVTALRM),\n\tM(SIGPROF),\n\tM(SIGXCPU),\n\tM(SIGXFSZ),\n\tM(SIGWAITING),\n\tM(SIGDIAF),\n\tM(SIGHATE),\n\tM(SIGWINEVENT),\n\tM(SIGCAT),\n\tM(SIGTTOU),\n};\n\nconst char * fcntl_cmd_names[] = {\n\tM(F_GETFD),\n\tM(F_SETFD),\n\tM(F_GETFL),\n\tM(F_SETFL),\n\tM(F_DUPFD),\n\tM(F_GETLK),\n\tM(F_SETLK),\n\tM(F_SETLKW),\n};\n\nstatic void open_flags(int flags) {\n\tif (!flags) {\n\t\tfprintf(logfile, \"O_RDONLY\");\n\t\treturn;\n\t}\n\n\t/* That's all that's valid right now */\n\tflags &= 0xFFFF;\n\n#define H(flg) do { if (flags & flg) { fprintf(logfile, #flg); flags &= (~flg); if (flags) fprintf(logfile, \"|\"); } } while (0)\n\n\tH(O_WRONLY);\n\tH(O_RDWR);\n\tH(O_APPEND);\n\tH(O_CREAT);\n\tH(O_TRUNC);\n\tH(O_EXCL);\n\tH(O_NOFOLLOW);\n\tH(O_PATH);\n\tH(O_NONBLOCK);\n\tH(O_DIRECTORY);\n\n\tif (flags) {\n\t\tfprintf(logfile, \"(%#x)\", flags);\n\t}\n}\n\nstatic void string_arg(pid_t pid, uintptr_t ptr) {\n\tif (ptr == 0) {\n\t\tfprintf(logfile, \"NULL\");\n\t\treturn;\n\t}\n\n\tfprintf(logfile, \"\\\"\");\n\n\tsize_t size = 0;\n\tuint8_t buf = 0;\n\n\tdo {\n\t\tlong result = ptrace(PTRACE_PEEKDATA, pid, (void*)ptr, &buf);\n\t\tif (result != 0) break;\n\t\tif (!buf) {\n\t\t\tfprintf(logfile, \"\\\"\");\n\t\t\treturn;\n\t\t}\n\n\t\tif (buf == '\\\\') fprintf(logfile, \"\\\\\\\\\");\n\t\telse if (buf == '\"') fprintf(logfile, \"\\\\\\\"\");\n\t\telse if (buf >= ' ' && buf <= '~') fprintf(logfile, \"%c\", buf);\n\t\telse if (buf == '\\r') fprintf(logfile, \"\\\\r\");\n\t\telse if (buf == '\\n') fprintf(logfile, \"\\\\n\");\n\t\telse fprintf(logfile, \"\\\\x%02x\", buf);\n\n\t\tptr++;\n\t\tsize++;\n\t\tif (size > 30) break;\n\t} while (buf);\n\n\tfprintf(logfile, \"\\\"...\");\n}\n\n#define C(arg) case arg: fprintf(logfile, #arg); break\n#define COMMA fprintf(logfile, \", \");\n\nstatic void pointer_arg(uintptr_t ptr) {\n\tif (ptr == 0) fprintf(logfile, \"NULL\");\n\telse fprintf(logfile, \"%#zx\", ptr);\n}\n\nstatic void uint_arg(size_t val) {\n\tfprintf(logfile, \"%zu\", val);\n}\n\nstatic void int_arg(size_t val) {\n\tfprintf(logfile, \"%zd\", val);\n}\n\nstatic void mode_arg(mode_t val) {\n\tfprintf(logfile, \"%#03o\", val);\n}\n\nstatic void fd_arg(pid_t pid, int val) {\n\t/* TODO: Look up file in user data? */\n\tfprintf(logfile, \"%d\", val);\n}\n\nstatic void sock_dom_arg(int domain) {\n\tswitch (domain) {\n\t\tC(AF_UNSPEC);\n\t\tC(AF_RAW);\n\t\tC(AF_INET);\n\t\tdefault: fprintf(logfile, \"%d\", domain); break;\n\t}\n}\n\nstatic void sock_typ_arg(int type) {\n\tswitch (type) {\n\t\tC(SOCK_DGRAM);\n\t\tC(SOCK_STREAM);\n\t\tC(SOCK_RAW);\n\t\tdefault: fprintf(logfile, \"%d\", type); break;\n\t}\n}\n\nstatic void sock_pro_arg(int domain, int type, int proto) {\n\tswitch (domain) {\n\t\tcase AF_INET:\n\t\t\tswitch (proto) {\n\t\t\t\tC(IPPROTO_IP);\n\t\t\t\tC(IPPROTO_ICMP);\n\t\t\t\tC(IPPROTO_TCP);\n\t\t\t\tC(IPPROTO_UDP);\n\t\t\t\tdefault:\n\t\t\t\t\tfprintf(logfile, \"%d\", proto);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn;\n\t}\n\n\t/* Fallback case */\n\tfprintf(logfile, \"%d\", proto);\n}\n\nstatic void sock_lvl_arg(int level) {\n\tswitch (level) {\n\t\tC(SOL_SOCKET);\n\t\tdefault: fprintf(logfile, \"%d\", level); break;\n\t}\n}\n\nstatic void sock_opt_arg(int opt) {\n\tswitch (opt) {\n\t\tC(SO_KEEPALIVE);\n\t\tC(SO_REUSEADDR);\n\t\tC(SO_BINDTODEVICE);\n\t\tdefault: fprintf(logfile, \"%d\", opt); break;\n\t}\n}\n\nstatic int data_read_bytes(pid_t pid, uintptr_t addr, char * buf, size_t size) {\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tif (ptrace(PTRACE_PEEKDATA, pid, (void*)addr++, &buf[i])) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int data_read_int(pid_t pid, uintptr_t addr) {\n\tint x;\n\tdata_read_bytes(pid, addr, (char*)&x, sizeof(int));\n\treturn x;\n}\n\nstatic uintptr_t data_read_ptr(pid_t pid, uintptr_t addr) {\n\tuintptr_t x;\n\tdata_read_bytes(pid, addr, (char*)&x, sizeof(uintptr_t));\n\treturn x;\n}\n\nstatic void sockaddr_arg(pid_t pid, uintptr_t addr, size_t size) {\n\tif (addr == 0) {\n\t\tfprintf(logfile, \"null\");\n\t\treturn;\n\t}\n\n\tstruct sockaddr_storage sa = {0};\n\tdata_read_bytes(pid, addr, (char*)&sa, size < sizeof(struct sockaddr_storage) ? size : sizeof(struct sockaddr_storage));\n\n\tfprintf(logfile, \"{sa_family=\");\n\tsock_dom_arg(sa.ss_family);\n\n\tif (sa.ss_family == AF_INET && size >= sizeof(struct sockaddr_in)) {\n\t\tstruct sockaddr_in addr;\n\t\tmemcpy(&addr, &sa, sizeof(struct sockaddr_in));\n\t\tfprintf(logfile, \", sin_port=htons(%d), sin_addr=inet_addr(\\\"%s\\\")\", ntohs(addr.sin_port), inet_ntoa(addr.sin_addr));\n\t}\n\n\tfprintf(logfile, \"}\");\n}\n\nstatic void sockaddrp_arg(pid_t pid, uintptr_t addr, uintptr_t size_p) {\n\tsize_t size = 0;\n\tdata_read_bytes(pid, size_p, (char*)&size, sizeof(size_t));\n\tsockaddr_arg(pid,addr,size);\n}\n\n\nstatic void fds_arg(pid_t pid, size_t ecount, uintptr_t array) {\n\tfprintf(logfile, \"[\");\n\tfor (size_t count = 0; count < 10 && count < ecount; ++count) {\n\t\tint x = data_read_int(pid, array);\n\t\tfprintf(logfile, \"%d\", x);\n\t\tif (count + 1 < ecount) fprintf(logfile, \",\");\n\t\tarray += sizeof(int);\n\t}\n\tfprintf(logfile, \"]\");\n}\n\nstatic void string_array_arg(pid_t pid, uintptr_t array) {\n\tfprintf(logfile, \"[\");\n\tuintptr_t val = data_read_ptr(pid, array);\n\tfor (size_t count = 0; count < 10; ++count) {\n\t\tstring_arg(pid, val);\n\t\tarray += sizeof(uintptr_t);\n\t\tval = data_read_ptr(pid, array);\n\t\tif (val) { COMMA; }\n\t\telse break;\n\t}\n\tfprintf(logfile, \"]\");\n}\n\nstatic void buffer_arg(pid_t pid, uintptr_t buffer, ssize_t count) {\n\tif (count < 0) {\n\t\tfprintf(logfile, \"...\");\n\t} else if (buffer == 0) {\n\t\tfprintf(logfile, \"NULL\");\n\t} else {\n\t\tssize_t x = 0;\n\t\tuint8_t buf = 0;\n\t\tfprintf(logfile, \"\\\"\");\n\t\twhile (x < count && x < 30) {\n\t\t\tlong result = ptrace(PTRACE_PEEKDATA, pid, (void*)buffer, &buf);\n\t\t\tif (result != 0) break;\n\n\t\t\tif (buf == '\\\\') fprintf(logfile, \"\\\\\\\\\");\n\t\t\telse if (buf == '\"') fprintf(logfile, \"\\\\\\\"\");\n\t\t\telse if (buf >= ' ' && buf < '~') fprintf(logfile, \"%c\", buf);\n\t\t\telse if (buf == '\\r') fprintf(logfile, \"\\\\r\");\n\t\t\telse if (buf == '\\n') fprintf(logfile, \"\\\\n\");\n\t\t\telse fprintf(logfile, \"\\\\x%02x\", buf);\n\n\t\t\tbuffer++;\n\t\t\tx++;\n\t\t}\n\t\tfprintf(logfile, \"\\\"\");\n\t\tif (x < count) fprintf(logfile, \"...\");\n\t}\n}\n\nstatic void msghdr_arg(pid_t pid, uintptr_t msghdr) {\n\tstruct msghdr data = {0};\n\tif (data_read_bytes(pid, msghdr, (char*)&data, sizeof(struct msghdr))) {\n\t\tfprintf(logfile, \"(?)\");\n\t} else {\n\t\tfprintf(logfile, \"{msg_name=%#zx,msg_iovlen=%zu,msg_iov[0]=\", (uintptr_t)data.msg_name, data.msg_iovlen);\n\t\tif (data.msg_iovlen > 0) {\n\t\t\tstruct iovec iodata = {0};\n\t\t\tif (data_read_bytes(pid, (uintptr_t)data.msg_iov, (char*)&iodata, sizeof(struct iovec))) {\n\t\t\t\tfprintf(logfile,\"?\");\n\t\t\t} else {\n\t\t\t\tfprintf(logfile,\"{iov_base=%#zx,iov_len=%zu}\", (uintptr_t)iodata.iov_base, iodata.iov_len);\n\t\t\t}\n\t\t}\n\t\tfprintf(logfile,\"}\");\n\t}\n}\n\nstatic void fcntl_cmd_arg(long cmd) {\n\tconst char * name = (cmd >= 0 && (size_t)cmd < (sizeof(fcntl_cmd_names) / sizeof(*fcntl_cmd_names))) ? fcntl_cmd_names[cmd] : NULL;\n\tif (name) {\n\t\tfprintf(logfile, \"%s\", name);\n\t} else {\n\t\tfprintf(logfile, \"%ld\", cmd);\n\t}\n}\n\nstatic void print_error(int err) {\n\tconst char * name = (err >= 0 && (size_t)err < (sizeof(errno_names) / sizeof(*errno_names))) ? errno_names[err] : NULL;\n\tif (name) {\n\t\tfprintf(logfile, \" %s (%s)\", name, strerror(err));\n\t} else {\n\t\tfprintf(logfile, \" %d (%s)\", err, strerror(err));\n\t}\n}\n\nstatic void maybe_errno(struct URegs * r) {\n\tfprintf(logfile, \") = %ld\", uregs_syscall_result(r));\n\tif ((intptr_t)uregs_syscall_result(r) < 0) print_error(-uregs_syscall_result(r));\n\tfprintf(logfile, \"\\n\");\n}\n\nstatic void struct_utsname_arg(pid_t pid, uintptr_t ptr) {\n\tif (!ptr) {\n\t\tfprintf(logfile, \"NULL\");\n\t\treturn;\n\t}\n\n\tfprintf(logfile, \"{\");\n\tfprintf(logfile, \"sysname=\");\n\tstring_arg(pid, ptr + offsetof(struct utsname, sysname));\n\tCOMMA;\n\tfprintf(logfile, \"nodename=\");\n\tstring_arg(pid, ptr + offsetof(struct utsname, nodename));\n\tCOMMA;\n\tfprintf(logfile, \"...}\");\n}\n\nstatic void struct_timeval_arg(pid_t pid, uintptr_t ptr) {\n\tif (!ptr) {\n\t\tfprintf(logfile, \"NULL\");\n\t\treturn;\n\t}\n\n\tfprintf(logfile, \"{\");\n\tfprintf(logfile, \"tv_sec=\");\n\tint_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_sec)));\n\tCOMMA;\n\tfprintf(logfile, \"tv_usec=\");\n\tint_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_usec)));\n\tfprintf(logfile, \"}\");\n}\n\nstatic void signal_arg(int signum) {\n\tif (signum >= 0 && signum < NSIG && signal_names[signum]) {\n\t\tfprintf(logfile, \"%s\", signal_names[signum]);\n\t} else {\n\t\tfprintf(logfile, \"%d\", signum);\n\t}\n}\n\nstatic void sigset_ptr_arg(pid_t pid, uintptr_t ptr) {\n\tif (!ptr) {\n\t\tfprintf(logfile, \"NULL\");\n\t\treturn;\n\t}\n\n\tuint64_t sigset = data_read_ptr(pid, ptr);\n\n\t/* handle special cases */\n\tif (sigset == 0xFFFFffffFFFFffff) {\n\t\tfprintf(logfile, \"sigfillset()\");\n\t\treturn;\n\t} else if (sigset == 0) {\n\t\tfprintf(logfile, \"sigemptyset()\");\n\t\treturn;\n\t}\n\n\tuint64_t x = (1ULL << NUMSIGNALS) - 2; /* 0 is also unused */\n\tsigset &= x;\n\n\tfprintf(logfile, \"{\");\n\tfor (int i = 1; i < NUMSIGNALS; ++i) {\n\t\tuint64_t s = 1ULL << i;\n\t\tif (sigset & s) {\n\t\t\tsignal_arg(i);\n\t\t\tsigset &= ~s;\n\t\t\tif (sigset) {\n\t\t\t\tfprintf(logfile, \"|\");\n\t\t\t}\n\t\t}\n\t}\n\tfprintf(logfile, \"}\");\n}\n\nstatic void signal_ptr_arg(pid_t pid, uintptr_t ptr) {\n\tint i = data_read_int(pid, ptr);\n\tfprintf(logfile, \"{\");\n\tsignal_arg(i);\n\tfprintf(logfile, \"}\");\n}\n\nstatic void handle_syscall(pid_t pid, struct URegs * r) {\n\tif (uregs_syscall_num(r) >= sizeof(syscall_mask)) return;\n\tif (!syscall_mask[uregs_syscall_num(r)]) return;\n\n\tfprintf(logfile, \"%s(\", syscall_names[uregs_syscall_num(r)]);\n\tswitch (uregs_syscall_num(r)) {\n\t\tcase SYS_OPEN:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\topen_flags(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_CHMOD:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tmode_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_CHOWN:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_TRUNCATE:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_READ:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\t/* Plus two more when done */\n\t\t\tbreak;\n\t\tcase SYS_WRITE:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tbuffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_PREAD:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\t/* Plus three more when done */\n\t\t\tbreak;\n\t\tcase SYS_PWRITE:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tbuffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg4(r));\n\t\t\tbreak;\n\t\tcase SYS_CLOSE:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_SBRK:\n\t\t\tuint_arg(uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_SEEK:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tswitch (uregs_syscall_arg3(r)) {\n\t\t\t\tC(SEEK_SET);\n\t\t\t\tC(SEEK_CUR);\n\t\t\t\tC(SEEK_END);\n\t\t\t\tdefault: int_arg(uregs_syscall_arg3(r)); break;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SYS_STATF:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_LSTAT:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_READDIR:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_KILL:\n\t\t\tint_arg(uregs_syscall_arg1(r)); COMMA; /* pid_arg? */\n\t\t\tsignal_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_CHDIR:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_GETCWD:\n\t\t\t/* output is first arg */\n\t\t\tpointer_arg(uregs_syscall_arg1(r)); COMMA; /* TODO syscall outputs */\n\t\t\tuint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_CLONE:\n\t\t\tpointer_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_SETHOSTNAME:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_GETHOSTNAME:\n\t\t\t/* plus one more when done */\n\t\t\tbreak;\n\t\tcase SYS_MKDIR:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_RENAME:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tstring_arg(pid, uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_ACCESS:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_PTRACE:\n\t\t\tswitch (uregs_syscall_arg1(r)) {\n\t\t\t\tC(PTRACE_ATTACH);\n\t\t\t\tC(PTRACE_CONT);\n\t\t\t\tC(PTRACE_DETACH);\n\t\t\t\tC(PTRACE_TRACEME);\n\t\t\t\tC(PTRACE_GETREGS);\n\t\t\t\tC(PTRACE_PEEKDATA);\n\t\t\t\tdefault: int_arg(uregs_syscall_arg1(r)); break;\n\t\t\t} COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg4(r));\n\t\t\tbreak;\n\t\tcase SYS_EXECVE:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tstring_array_arg(pid, uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_SHM_OBTAIN:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_SHM_RELEASE:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_SIGNAL:\n\t\t\tsignal_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_SYSFUNC:\n\t\t\tswitch (uregs_syscall_arg1(r)) {\n\t\t\t\tC(TOARU_SYS_FUNC_SYNC);\n\t\t\t\tC(TOARU_SYS_FUNC_LOGHERE);\n\t\t\t\tC(TOARU_SYS_FUNC_KDEBUG);\n\t\t\t\tC(TOARU_SYS_FUNC_INSMOD);\n\t\t\t\tC(TOARU_SYS_FUNC_SETHEAP);\n\t\t\t\tC(TOARU_SYS_FUNC_MMAP);\n\t\t\t\tC(TOARU_SYS_FUNC_THREADNAME);\n\t\t\t\tC(TOARU_SYS_FUNC_SETGSBASE);\n\t\t\t\tC(TOARU_SYS_FUNC_NPROC);\n\t\t\t\tC(TOARU_SYS_FUNC_CLEARICACHE);\n\t\t\t\tC(TOARU_SYS_FUNC_MUNMAP);\n\t\t\t\tdefault: int_arg(uregs_syscall_arg1(r)); break;\n\t\t\t} COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_FSWAIT:\n\t\t\tint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tfds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_FSWAIT2:\n\t\t\tint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tfds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_FSWAIT3:\n\t\t\tint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tfds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg4(r));\n\t\t\tbreak;\n\t\tcase SYS_IOCTL:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_WAITPID:\n\t\t\tint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r)); /* TODO waitpid options */\n\t\t\tbreak;\n\t\tcase SYS_EXT:\n\t\t\tint_arg(uregs_syscall_arg1(r));\n\t\t\tfprintf(logfile, \") = ?\\n\");\n\t\t\treturn;\n\t\tcase SYS_UNAME:\n\t\t\t/* One output arg */\n\t\t\tbreak;\n\t\tcase SYS_SLEEPABS:\n\t\t\tuint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_SLEEP:\n\t\t\tuint_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_PIPE:\n\t\t\t/* Arg is a pointer */\n\t\t\tbreak;\n\t\tcase SYS_DUP2:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tfd_arg(pid, uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_FCNTL:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tfcntl_cmd_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_FCHMOD:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tmode_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_FCHOWN:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_FTRUNCATE:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_MOUNT:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tstring_arg(pid, uregs_syscall_arg2(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg4(r));\n\t\t\tbreak;\n\t\tcase SYS_UMASK:\n\t\t\tint_arg(uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_UNLINK:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_GETTIMEOFDAY:\n\t\t\t/* two output args */\n\t\t\tbreak;\n\t\tcase SYS_SETTIMEOFDAY:\n\t\t\tstruct_timeval_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_SIGACTION:\n\t\t\tsignal_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg3(r)); /* sigaction output */\n\t\t\tbreak;\n\t\tcase SYS_SIGPENDING:\n\t\t\t/* output only */\n\t\t\tbreak;\n\t\tcase SYS_SIGPROCMASK:\n\t\t\tswitch (uregs_syscall_arg1(r)) {\n\t\t\t\tC(SIG_BLOCK);\n\t\t\t\tC(SIG_UNBLOCK);\n\t\t\t\tC(SIG_SETMASK);\n\t\t\t\tdefault: int_arg(uregs_syscall_arg1(r)); break;\n\t\t\t} COMMA;\n\t\t\tsigset_ptr_arg(pid, uregs_syscall_arg2(r)); COMMA;\n\t\t\t/* one more after with old set */\n\t\t\tbreak;\n\t\tcase SYS_SIGSUSPEND:\n\t\t\tsigset_ptr_arg(pid, uregs_syscall_arg1(r));\n\t\t\tbreak;\n\t\tcase SYS_SIGWAIT:\n\t\t\tsigset_ptr_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tbreak;\n\t\tcase SYS_SOCKET:\n\t\t\tsock_dom_arg(uregs_syscall_arg1(r)); COMMA;\n\t\t\tsock_typ_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tsock_pro_arg(uregs_syscall_arg1(r), uregs_syscall_arg2(r), uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_RECV:\n\t\tcase SYS_SEND:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tmsghdr_arg(pid, uregs_syscall_arg2(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg3(r));\n\t\t\tbreak;\n\t\tcase SYS_LISTEN:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r));\n\t\t\tbreak;\n\t\tcase SYS_CONNECT:\n\t\tcase SYS_ACCEPT:\n\t\tcase SYS_BIND:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tsockaddr_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); /* Consumes both */\n\t\t\tbreak;\n\t\tcase SYS_GETSOCKNAME:\n\t\tcase SYS_GETPEERNAME:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\t/* two output args */\n\t\t\tbreak;\n\t\tcase SYS_SHUTDOWN:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg2(r)); /* TODO SHUT_... */\n\t\t\tbreak;\n\t\tcase SYS_SETSOCKOPT:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tsock_lvl_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tsock_opt_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg4(r)); COMMA; /* TODO sockaddr in some contexts */\n\t\t\tuint_arg(uregs_syscall_arg5(r));\n\t\t\tbreak;\n\t\tcase SYS_GETSOCKOPT:\n\t\t\tfd_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tsock_lvl_arg(uregs_syscall_arg2(r)); COMMA;\n\t\t\tsock_opt_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg4(r)); COMMA; /* TODO sockaddr in some contexts */\n\t\t\tpointer_arg(uregs_syscall_arg5(r)); /* Note - differs from set */\n\t\t\tbreak;\n\t\t/* These have no arguments: */\n\t\tcase SYS_YIELD:\n\t\tcase SYS_FORK:\n\t\tcase SYS_GETEUID:\n\t\tcase SYS_GETPID:\n\t\tcase SYS_GETUID:\n\t\tcase SYS_REBOOT:\n\t\tcase SYS_GETTID:\n\t\tcase SYS_SETSID:\n\t\tcase SYS_GETGID:\n\t\tcase SYS_GETEGID:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(logfile, \"...\");\n\t\t\tbreak;\n\t}\n\tfflush(stdout);\n}\n\nstatic void finish_syscall(pid_t pid, int syscall, struct URegs * r) {\n\tif (syscall >= (int)sizeof(syscall_mask)) return;\n\tif (syscall >= 0 && !syscall_mask[syscall]) return;\n\n\tswitch (syscall) {\n\t\tcase -1:\n\t\t\tbreak; /* This is ptrace(PTRACE_TRACEME)... probably... */\n\t\t/* read() returns data in second value */\n\t\tcase SYS_READ:\n\t\t\tbuffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_result(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg3(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_PREAD:\n\t\t\tbuffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_result(r)); COMMA;\n\t\t\tuint_arg(uregs_syscall_arg3(r)); COMMA;\n\t\t\tint_arg(uregs_syscall_arg4(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_GETHOSTNAME:\n\t\t\tstring_arg(pid, uregs_syscall_arg1(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_UNAME:\n\t\t\tstruct_utsname_arg(pid, uregs_syscall_arg1(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_PIPE:\n\t\t\tfds_arg(pid, 2, uregs_syscall_arg1(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_GETTIMEOFDAY:\n\t\t\tstruct_timeval_arg(pid, uregs_syscall_arg1(r)); COMMA;\n\t\t\tpointer_arg(uregs_syscall_arg2(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\t/* sbrk() returns an address */\n\t\tcase SYS_SBRK:\n\t\t\tfprintf(logfile, \") = %#zx\\n\", uregs_syscall_result(r));\n\t\t\tbreak;\n\t\tcase SYS_EXECVE:\n\t\t\tif (r == NULL) fprintf(logfile, \") = 0\\n\");\n\t\t\telse maybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_SIGPROCMASK:\n\t\t\tsigset_ptr_arg(pid, uregs_syscall_arg3(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_SIGWAIT:\n\t\t\tsignal_ptr_arg(pid, uregs_syscall_arg2(r));\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t\tcase SYS_GETSOCKNAME:\n\t\tcase SYS_GETPEERNAME:\n\t\t\tsockaddrp_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); /* Consumes both */\n\t\t\tbreak;\n\t\t/* Most things return -errno, or positive valid result */\n\t\tdefault:\n\t\t\tmaybe_errno(r);\n\t\t\tbreak;\n\t}\n}\n\nstatic int usage(char * argv[]) {\n#define T_I \"\\033[3m\"\n#define T_O \"\\033[0m\"\n\tfprintf(stderr, \"usage: %s [-o logfile] [-e trace=...] [-p PID] [command...]\\n\"\n\t\t\t\"  -o logfile   \" T_I \"Write tracing output to a file.\" T_O \"\\n\"\n\t\t\t\"  -h           \" T_I \"Show this help text.\" T_O \"\\n\"\n\t\t\t\"  -e trace=... \" T_I \"Set tracing options.\" T_O \"\\n\"\n\t\t\t\"  -p PID       \" T_I \"Trace an existing process.\" T_O \"\\n\",\n\t\t\targv[0]);\n\treturn 1;\n}\n\nint main(int argc, char * argv[]) {\n\tlogfile = stdout;\n\n\tpid_t p = 0;\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"ho:e:p:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'p':\n\t\t\t\tp = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'o':\n\t\t\t\tlogfile = fopen(optarg, \"w\");\n\t\t\t\tif (!logfile) {\n\t\t\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], optarg, strerror(errno));\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'e':\n\t\t\t\tif (strstr(optarg,\"trace=\") == optarg) {\n\t\t\t\t\t/* First, disable everything. */\n\t\t\t\t\tmemset(syscall_mask, 0, sizeof(syscall_mask));\n\n\t\t\t\t\t/* Now look at each comma-separated option */\n\t\t\t\t\tchar * option = optarg + 6;\n\t\t\t\t\tchar * comma = strstr(option, \",\");\n\t\t\t\t\tdo {\n\t\t\t\t\t\tif (comma) *comma = '\\0';\n\n\t\t\t\t\t\tif (*option == '%') {\n\t\t\t\t\t\t\t/* Check for special options */\n\t\t\t\t\t\t\tif (!strcmp(option+1,\"net\") || !strcmp(option+1,\"network\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_SOCKET, SYS_SETSOCKOPT, SYS_BIND, SYS_ACCEPT, SYS_LISTEN,\n\t\t\t\t\t\t\t\t\tSYS_CONNECT, SYS_GETSOCKOPT, SYS_RECV, SYS_SEND, SYS_SHUTDOWN,\n\t\t\t\t\t\t\t\t\tSYS_GETPEERNAME, SYS_GETSOCKNAME,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"file\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_OPEN, SYS_STATF, SYS_LSTAT, SYS_ACCESS, SYS_EXECVE,\n\t\t\t\t\t\t\t\t\tSYS_GETCWD, SYS_CHDIR, SYS_MKDIR, SYS_SYMLINK, SYS_UNLINK,\n\t\t\t\t\t\t\t\t\tSYS_CHMOD, SYS_CHOWN, SYS_MOUNT, SYS_READLINK, SYS_RENAME,\n\t\t\t\t\t\t\t\t\tSYS_TRUNCATE,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"desc\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_OPEN, SYS_READ, SYS_WRITE, SYS_CLOSE, SYS_STAT, SYS_FSWAIT,\n\t\t\t\t\t\t\t\t\tSYS_FSWAIT2, SYS_FSWAIT3, SYS_SEEK, SYS_IOCTL, SYS_PIPE,\n\t\t\t\t\t\t\t\t\tSYS_DUP2, SYS_READDIR, SYS_OPENPTY, SYS_PREAD, SYS_PWRITE, SYS_FCNTL,\n\t\t\t\t\t\t\t\t\tSYS_FCHMOD, SYS_FCHOWN, SYS_FTRUNCATE,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"memory\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_SBRK, SYS_SHM_OBTAIN, SYS_SHM_RELEASE,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"ipc\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_SHM_OBTAIN, SYS_SHM_RELEASE,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"signal\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_SIGNAL, SYS_KILL, SYS_SIGACTION, SYS_SIGPENDING, SYS_SIGPROCMASK,\n\t\t\t\t\t\t\t\t\tSYS_SIGSUSPEND, SYS_SIGWAIT,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"process\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_EXT, SYS_EXECVE, SYS_FORK, SYS_CLONE, SYS_WAITPID, SYS_KILL,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (!strcmp(option+1,\"creds\")) {\n\t\t\t\t\t\t\t\tint syscalls[] = {\n\t\t\t\t\t\t\t\t\tSYS_GETUID, SYS_GETGID, SYS_GETGROUPS, SYS_GETEGID, SYS_GETEUID,\n\t\t\t\t\t\t\t\t\tSYS_SETUID, SYS_SETGID, SYS_SETGROUPS,\n\t\t\t\t\t\t\t\t\t0\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tfor (int *i = syscalls; *i; i++) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[*i] = 1;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: Unrecognized syscall group: %s\\n\", argv[0], option + 1);\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t/* Check the list */\n\t\t\t\t\t\t\tint set_something = 0;\n\t\t\t\t\t\t\tfor (size_t i = 0; i < sizeof(syscall_names) / sizeof(*syscall_names); ++i) {\n\t\t\t\t\t\t\t\tif (syscall_names[i] && !strcmp(option,syscall_names[i])) {\n\t\t\t\t\t\t\t\t\tsyscall_mask[i] = 1;\n\t\t\t\t\t\t\t\t\tset_something = 1;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!set_something) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: Unrecognized syscall name: %s\\n\", argv[0], option);\n\t\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (comma) { option = comma + 1; }\n\t\t\t\t\t} while (comma);\n\t\t\t\t} else {\n\t\t\t\t\tchar * eq = strstr(optarg, \"=\");\n\t\t\t\t\tif (eq) *eq = '\\0';\n\t\t\t\t\tfprintf(stderr, \"%s: Unrecognized -e option: %s\\n\", argv[0], optarg);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\treturn (usage(argv), 0);\n\t\t\tcase '?':\n\t\t\t\treturn usage(argv);\n\t\t}\n\t}\n\n\tif (!p && optind == argc) {\n\t\treturn usage(argv);\n\t}\n\n\tif (!p) {\n\t\tp = fork();\n\t\tif (!p) {\n\t\t\tif (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) {\n\t\t\t\tfprintf(stderr, \"%s: ptrace: %s\\n\", argv[0], strerror(errno));\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\texecvp(argv[optind], &argv[optind]);\n\t\t\treturn 1;\n\t\t}\n\t\tsignal(SIGINT, SIG_IGN);\n\t} else {\n\t\tif (ptrace(PTRACE_ATTACH, p, NULL, NULL) < 0) {\n\t\t\tfprintf(stderr, \"%s: ptrace: %s\\n\", argv[0], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tint previous_syscall = -1;\n\twhile (1) {\n\t\tint status = 0;\n\t\tpid_t res = waitpid(p, &status, WSTOPPED);\n\n\t\tif (res < 0) {\n\t\t\tfprintf(stderr, \"%s: waitpid: %s\\n\", argv[0], strerror(errno));\n\t\t} else {\n\t\t\tif (WIFSTOPPED(status)) {\n\t\t\t\tif (WSTOPSIG(status) == SIGTRAP) {\n\t\t\t\t\tstruct URegs regs;\n\t\t\t\t\tptrace(PTRACE_GETREGS, p, NULL, &regs);\n\n\t\t\t\t\t/* Event type */\n\t\t\t\t\tint event = (status >> 16) & 0xFF;\n\t\t\t\t\tswitch (event) {\n\t\t\t\t\t\tcase PTRACE_EVENT_SYSCALL_ENTER:\n\t\t\t\t\t\t\tif (previous_syscall == SYS_EXECVE) finish_syscall(p,SYS_EXECVE,NULL);\n\t\t\t\t\t\t\tprevious_syscall = uregs_syscall_num(&regs);\n\t\t\t\t\t\t\thandle_syscall(p, &regs);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase PTRACE_EVENT_SYSCALL_EXIT:\n\t\t\t\t\t\t\tfinish_syscall(p, previous_syscall, &regs);\n\t\t\t\t\t\t\tprevious_syscall = -1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tfprintf(logfile, \"Unknown event.\\n\");\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tptrace(PTRACE_CONT, p, NULL, NULL);\n\t\t\t\t} else {\n\t\t\t\t\tfprintf(logfile, \"--- %s ---\\n\", signal_names[WSTOPSIG(status)]);\n\t\t\t\t\tptrace(PTRACE_CONT, p, NULL, (void*)(uintptr_t)WSTOPSIG(status));\n\t\t\t\t}\n\t\t\t} else if (WIFSIGNALED(status)) {\n\t\t\t\tfprintf(logfile, \"+++ killed by %s +++\\n\", signal_names[WTERMSIG(status)]);\n\t\t\t\treturn 0;\n\t\t\t} else if (WIFEXITED(status)) {\n\t\t\t\tfprintf(logfile, \"+++ exited with %d +++\\n\", WEXITSTATUS(status));\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/strings.c",
    "content": "/**\n * @brief strings - print printable character sequences found in a file\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <getopt.h>\n#include <ctype.h>\n#include <unistd.h>\n#include <errno.h>\n#include <stdlib.h>\n\nstatic char format = 0;\nstatic int min_chars = 4;\n\nstatic void read_file(FILE * f) {\n\tchar buf[1024] = {0};\n\tint  ind = 0;\n\tsize_t offset = 0;\n\n\twhile (!feof(f)) {\n\t\tint c = fgetc(f);\n\t\tif (c < 0) break;\n\t\tif (c == '\\n' || c == '\\0') {\n\t\t\tif (ind >= min_chars) {\n\t\t\t\tswitch (format) {\n\t\t\t\t\tcase 'x':\n\t\t\t\t\t\tfprintf(stdout, \"%lx \", offset - ind);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'd':\n\t\t\t\t\t\tfprintf(stdout, \"%lu \", offset - ind);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbuf[ind] = '\\0';\n\t\t\t\tfprintf(stdout, \"%s\\n\", buf);\n\t\t\t}\n\t\t\tind = 0;\n\t\t\toffset++;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isprint(c)) {\n\t\t\tind = 0;\n\t\t} else {\n\t\t\tif (ind < 1024) {\n\t\t\t\tbuf[ind] = c;\n\t\t\t\tind++;\n\t\t\t}\n\t\t}\n\t\toffset++;\n\t}\n}\n\nint main(int argc, char * argv[]) {\n\tint opt;\n\tint ret_val = 0;\n\n\twhile ((opt = getopt(argc, argv, \"an:t:\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'a':\n\t\t\t\t/* TODO: With ELF support, read only the string table\n\t\t\t\t * by default, unless this option is specified. */\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tmin_chars = atoi(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tformat = optarg[0];\n\t\t\t\tif (format != 'd' && format != 'x') {\n\t\t\t\t\tfprintf(stderr, \"%s: format '%c' is not supported\\n\", argv[0], format);\n\t\t\t\t\tformat = 0;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind == argc) {\n\t\tread_file(stdin);\n\t}\n\tfor (int i = optind; i < argc; ++i) {\n\t\tFILE * f = fopen(argv[i], \"r\");\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tret_val = 1;\n\t\t\tcontinue;\n\t\t}\n\t\tread_file(f);\n\t\tfclose(f);\n\t}\n\n\treturn ret_val;\n}\n"
  },
  {
    "path": "apps/stty.c",
    "content": "/**\n * @brief Set and display tty config bits.\n *\n * Surprisingly complete.\n *\n * We used to use Minix's stty, so I may have adopted some conventions from it\n * that aren't in other stty's, but I don't even remember any more.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <ctype.h>\n#include <termios.h>\n#include <sys/ioctl.h>\n\nstatic int hide_defaults = 1;\nstatic int printed = 0;\n\nstatic void print_cc(struct termios * t, const char * lbl, int val, int def) {\n\tint c = t->c_cc[val];\n\tif (hide_defaults && c == def) return;\n\tif (!c) {\n\t\tfprintf(stdout, \"%s = <undef>; \", lbl);\n\t} else if (c < 32) {\n\t\tfprintf(stdout, \"%s = ^%c; \", lbl, '@' + c);\n\t} else if (c == 0x7F) {\n\t\tfprintf(stdout, \"%s = ^?; \", lbl);\n\t} else {\n\t\tfprintf(stdout, \"%s = %c; \", lbl, c);\n\t}\n\tprinted = 1;\n}\n\nstatic void print_(int flags, const char * lbl, int val, int def) {\n\tint c = !!(flags & val);\n\tif (!hide_defaults || c != def) {\n\t\tfprintf(stdout, \"%s%s \", c ? \"\" : \"-\", lbl);\n\t\tprinted = 1;\n\t}\n}\n\n#define print_cflag(lbl,val,def) print_(t.c_cflag, lbl, val, def)\n#define print_iflag(lbl,val,def) print_(t.c_iflag, lbl, val, def)\n#define print_oflag(lbl,val,def) print_(t.c_oflag, lbl, val, def)\n#define print_lflag(lbl,val,def) print_(t.c_lflag, lbl, val, def)\n\nstatic int set_char_(struct termios *t, const char * lbl, int val, const char * cmp, const char * arg, int * i) {\n\tif (!strcmp(cmp, lbl)) {\n\t\tif (!arg) {\n\t\t\tfprintf(stderr, \"%s: expected argument\\n\", lbl);\n\t\t\texit(1);\n\t\t}\n\t\t/* Parse arg */\n\t\tif (strlen(arg) == 1) {\n\t\t\t/* Assume raw character */\n\t\t\tt->c_cc[val] = *arg;\n\t\t} else if (*arg == '^') { /* ^c, etc. */\n\t\t\tint v = toupper(arg[1]);\n\t\t\tif (v == '?') { /* special case */\n\t\t\t\tt->c_cc[val] = 0x7F;\n\t\t\t} else {\n\t\t\t\tt->c_cc[val] = v - '@';\n\t\t\t}\n\t\t} else {\n\t\t\t/* Assume decimal for now */\n\t\t\tint v = atoi(arg);\n\t\t\tt->c_cc[val] = v;\n\t\t}\n\t\t(*i)++;\n\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\n#define set_char(lbl,val) if (set_char_(&t, lbl, val, argv[i], argv[i+1], &i)) { i += 2; continue; }\n\nstatic int setunset_flag(tcflag_t * flag, const char * lbl, int val, const char * cmp) {\n\tif (*cmp == '-') {\n\t\tif (!strcmp(cmp+1, lbl)) {\n\t\t\t(*flag) = (*flag) & (~val);\n\t\t\treturn 1;\n\t\t}\n\t} else {\n\t\tif (!strcmp(cmp, lbl)) {\n\t\t\t(*flag) = (*flag) | (val);\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\n#define set_cflag(lbl,val) if (setunset_flag(&(t.c_cflag), lbl, val, argv[i])) { i++; continue; }\n#define set_iflag(lbl,val) if (setunset_flag(&(t.c_iflag), lbl, val, argv[i])) { i++; continue; }\n#define set_oflag(lbl,val) if (setunset_flag(&(t.c_oflag), lbl, val, argv[i])) { i++; continue; }\n#define set_lflag(lbl,val) if (setunset_flag(&(t.c_lflag), lbl, val, argv[i])) { i++; continue; }\n\nstatic int set_toggle_(tcflag_t * flag, const char * lbl, int base, int val, const char * cmp) {\n\tif (!strcmp(cmp, lbl)) {\n\t\t(*flag) = (*flag) & ~(base);\n\t\t(*flag) = (*flag) | (val);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n#define set_ctoggle(lbl,base,val) if (set_toggle_(&(t.c_cflag), lbl, base, val, argv[i])) { i++; continue; }\n#define set_otoggle(lbl,base,val) if (set_toggle_(&(t.c_oflag), lbl, base, val, argv[i])) { i++; continue; }\n\nstatic struct baud_table {\n\tconst char * as_str;\n\tspeed_t as_baud;\n} baud_rates[] = {\n\t{      \"0\", B0      },\n\t{     \"50\", B50     },\n\t{     \"75\", B75     },\n\t{    \"110\", B110    },\n\t{    \"134\", B134    },\n\t{  \"134.5\", B134    },\n\t{    \"150\", B150    },\n\t{    \"200\", B200    },\n\t{    \"300\", B300    },\n\t{    \"600\", B600    },\n\t{   \"1200\", B1200   },\n\t{   \"1800\", B1800   },\n\t{   \"2400\", B2400   },\n\t{   \"4800\", B4800   },\n\t{   \"9600\", B9600   },\n\t{  \"19200\", B19200  },\n\t{  \"38400\", B38400  },\n\t{  \"57600\", B57600  },\n\t{ \"115200\", B115200 },\n\t{ \"230400\", B230400 },\n\t{ \"460800\", B460800 },\n\t{ \"921600\", B921600 },\n};\n\nstatic int set_baud_rate(struct termios * t, const char * arg) {\n\tfor (size_t j = 0; j < sizeof(baud_rates) / sizeof(*baud_rates); ++j) {\n\t\tif (!strcmp(arg, baud_rates[j].as_str)) {\n\t\t\tcfsetospeed(t, baud_rates[j].as_baud);\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int print_baud_rate(struct termios * t) {\n\tspeed_t ospeed = cfgetospeed(t);\n\tfor (size_t j = 0; j < sizeof(baud_rates) / sizeof(*baud_rates); ++j) {\n\t\tif (ospeed == baud_rates[j].as_baud) {\n\t\t\tfprintf(stdout, \"speed %s baud; \", baud_rates[j].as_str);\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int show_settings(int all) {\n\tstruct termios t;\n\ttcgetattr(STDERR_FILENO, &t);\n\tprint_baud_rate(&t);\n\n\t/* Baud rate */\n\t/* Size */\n\tstruct winsize w;\n\tioctl(STDERR_FILENO, TIOCGWINSZ, &w);\n\tfprintf(stdout, \"rows %d; columns %d; ypixels %d; xpixels %d;\\n\", w.ws_row, w.ws_col, w.ws_ypixel, w.ws_xpixel);\n\tprinted = 0;\n\n\t/* Keys */\n\tprint_cc(&t, \"intr\",  VINTR,  3);\n\tprint_cc(&t, \"quit\",  VQUIT,  28);\n\tprint_cc(&t, \"erase\", VERASE, 0x7F);\n\tprint_cc(&t, \"kill\",  VKILL,  21);\n\tprint_cc(&t, \"eof\",   VEOF,   4);\n\tprint_cc(&t, \"eol\",   VEOL,   0);\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\tprint_cc(&t, \"start\", VSTART, 17);\n\tprint_cc(&t, \"stop\",  VSTOP,  19);\n\tprint_cc(&t, \"susp\",  VSUSP,  26);\n\tprint_cc(&t, \"lnext\", VLNEXT, 22);\n\tprint_cc(&t, \"werase\",VWERASE, 23);\n\n\t/* MIN, TIME */\n\tif (!hide_defaults || t.c_cc[VMIN]  != 1) { fprintf(stdout, \"min = %d; \",  t.c_cc[VMIN]);  printed = 1; }\n\tif (!hide_defaults || t.c_cc[VTIME] != 0) { fprintf(stdout, \"time = %d; \", t.c_cc[VTIME]); printed = 1; }\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\tswitch ((t.c_cflag & CSIZE)) {\n\t\tcase CS5: fprintf(stdout, \"cs5 \"); printed = 1; break;\n\t\tcase CS6: fprintf(stdout, \"cs6 \"); printed = 1; break;\n\t\tcase CS7: fprintf(stdout, \"cs7 \"); printed = 1; break;\n\t\tcase CS8: if (!hide_defaults) { fprintf(stdout, \"cs8 \"); printed = 1; } break;\n\t}\n\n\tprint_cflag(\"cstopb\", CSTOPB, 0);\n\tprint_cflag(\"cread\",  CREAD,  1);\n\tprint_cflag(\"parenb\", PARENB, 0);\n\tprint_cflag(\"parodd\", PARODD, 0);\n\tprint_cflag(\"hupcl\",  HUPCL,  0);\n\tprint_cflag(\"clocal\", CLOCAL, 0);\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\tprint_iflag(\"brkint\", BRKINT, 1);\n\tprint_iflag(\"icrnl\",  ICRNL,  1);\n\tprint_iflag(\"ignbrk\", IGNBRK, 0);\n\tprint_iflag(\"igncr\",  IGNCR,  0);\n\tprint_iflag(\"ignpar\", IGNPAR, 0);\n\tprint_iflag(\"inlcr\",  INLCR,  0);\n\tprint_iflag(\"inpck\",  INPCK,  0);\n\tprint_iflag(\"istrip\", ISTRIP, 0);\n\tprint_iflag(\"ixany\",  IXANY,  0);\n\tprint_iflag(\"ixoff\",  IXOFF,  0);\n\tprint_iflag(\"ixon\",   IXON,   0);\n\tprint_iflag(\"iuclc\",  IUCLC,  0);\n\tprint_iflag(\"parmrk\", PARMRK, 0);\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\tprint_oflag(\"opost\",  OPOST,  1);\n\tprint_oflag(\"olcuc\",  OLCUC,  0);\n\tprint_oflag(\"onlcr\",  ONLCR,  1);\n\tprint_oflag(\"ocrnl\",  OCRNL,  0);\n\tprint_oflag(\"onocr\",  ONOCR,  0);\n\tprint_oflag(\"onlret\", ONLRET, 0);\n\tprint_oflag(\"ofill\",  OFILL,  0);\n\tprint_oflag(\"ofdel\",  OFDEL,  0);\n\n\tswitch ((t.c_oflag & CRDLY)) {\n\t\tcase CR0: if (!hide_defaults) { fprintf(stdout, \"cr0 \"); printed = 1; } break;\n\t\tcase CR1: fprintf(stdout, \"cr1 \"); printed = 1; break;\n\t\tcase CR2: fprintf(stdout, \"cr2 \"); printed = 1; break;\n\t\tcase CR3: fprintf(stdout, \"cr3 \"); printed = 1; break;\n\t}\n\n\tswitch ((t.c_oflag & NLDLY)) {\n\t\tcase NL0: if (!hide_defaults) { fprintf(stdout, \"nl0 \"); printed = 1; } break;\n\t\tcase NL1: fprintf(stdout, \"nl1 \"); printed = 1; break;\n\t}\n\n\tswitch ((t.c_oflag & TABDLY)) {\n\t\tcase TAB0: if (!hide_defaults) { fprintf(stdout, \"tab0 \"); printed = 1; } break;\n\t\tcase TAB1: fprintf(stdout, \"tab1 \"); printed = 1; break;\n\t\tcase TAB2: fprintf(stdout, \"tab2 \"); printed = 1; break;\n\t\tcase TAB3: fprintf(stdout, \"tab3 \"); printed = 1; break;\n\t}\n\n\tswitch ((t.c_oflag & BSDLY)) {\n\t\tcase BS0: if (!hide_defaults) { fprintf(stdout, \"bs0 \"); printed = 1; } break;\n\t\tcase BS1: fprintf(stdout, \"bs1 \"); printed = 1; break;\n\t}\n\n\tswitch ((t.c_oflag & FFDLY)) {\n\t\tcase FF0: if (!hide_defaults) { fprintf(stdout, \"ff0 \"); printed = 1; } break;\n\t\tcase FF1: fprintf(stdout, \"ff1 \"); printed = 1; break;\n\t}\n\n\tswitch ((t.c_oflag & VTDLY)) {\n\t\tcase VT0: if (!hide_defaults) { fprintf(stdout, \"vt0\"); printed = 1; } break;\n\t\tcase VT1: fprintf(stdout, \"vt1\"); printed = 1; break;\n\t}\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\tprint_lflag(\"isig\",   ISIG,   1);\n\tprint_lflag(\"icanon\", ICANON, 1);\n\tprint_lflag(\"xcase\",  XCASE,  0);\n\tprint_lflag(\"echo\",   ECHO,   1);\n\tprint_lflag(\"echoe\",  ECHOE,  1);\n\tprint_lflag(\"echok\",  ECHOK,  1);\n\tprint_lflag(\"echonl\", ECHONL, 0);\n\tprint_lflag(\"echoctl\",ECHOCTL,1);\n\tprint_lflag(\"noflsh\", NOFLSH, 0);\n\tprint_lflag(\"tostop\", TOSTOP, 0);\n\tprint_lflag(\"iexten\", IEXTEN, 1);\n\tif (printed) { fprintf(stdout, \"\\n\"); printed = 0; }\n\n\treturn 0;\n}\n\nstatic void show_size(void) {\n\tstruct winsize w;\n\tioctl(STDERR_FILENO, TIOCGWINSZ, &w);\n\tfprintf(stdout, \"%d %d\\n\", w.ws_row, w.ws_col);\n}\n\nint main(int argc, char * argv[]) {\n\n\tint i = 1;\n\n\tif (i < argc && !strcmp(argv[i], \"-a\")) {\n\t\thide_defaults = 0;\n\t\ti++;\n\t}\n\n\tif (i == argc) {\n\t\treturn show_settings(0);\n\t}\n\n\tstruct termios t;\n\ttcgetattr(STDERR_FILENO, &t);\n\n\twhile (i < argc) {\n\n\t\tif (!strcmp(argv[i], \"sane\")) {\n\t\t\tt.c_iflag = ICRNL | BRKINT;\n\t\t\tt.c_oflag = ONLCR | OPOST;\n\t\t\tt.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN | ECHOCTL;\n\t\t\tt.c_cflag |= CREAD;\n\t\t\tt.c_cc[VEOF]   =  4; /* ^D */\n\t\t\tt.c_cc[VEOL]   =  0; /* Not set */\n\t\t\tt.c_cc[VERASE] = 0x7F; /* ^? */\n\t\t\tt.c_cc[VINTR]  =  3; /* ^C */\n\t\t\tt.c_cc[VKILL]  = 21; /* ^U */\n\t\t\tt.c_cc[VMIN]   =  1;\n\t\t\tt.c_cc[VQUIT]  = 28; /* ^\\ */\n\t\t\tt.c_cc[VSTART] = 17; /* ^Q */\n\t\t\tt.c_cc[VSTOP]  = 19; /* ^S */\n\t\t\tt.c_cc[VSUSP] = 26; /* ^Z */\n\t\t\tt.c_cc[VTIME]  =  0;\n\t\t\tt.c_cc[VLNEXT] = 22; /* ^V */\n\t\t\tt.c_cc[VWERASE] = 23; /* ^W */\n\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(argv[i], \"raw\")) {\n\t\t\tt.c_iflag = 0;       /* no input processing */\n\t\t\tt.c_oflag &= ~OPOST; /* no postprocessing of output */\n\t\t\tt.c_lflag &= ~(ISIG | ICANON | XCASE);\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(argv[i], \"cooked\")) {\n\t\t\tt.c_iflag |= ICRNL | BRKINT;\n\t\t\tt.c_oflag |= OPOST;\n\t\t\tt.c_lflag |= ISIG | ICANON;\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!strcmp(argv[i], \"size\")) {\n\t\t\tshow_size();\n\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (*argv[i] >= '0' && *argv[i] <= '9') {\n\t\t\tif (set_baud_rate(&t, argv[i])) {\n\t\t\t\ti++;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tset_char(\"eof\",   VEOF);\n\t\tset_char(\"eol\",   VEOL);\n\t\tset_char(\"erase\", VERASE);\n\t\tset_char(\"intr\",  VINTR);\n\t\tset_char(\"kill\",  VKILL);\n\t\tset_char(\"quit\",  VQUIT);\n\t\tset_char(\"start\", VSTART);\n\t\tset_char(\"stop\",  VSTOP);\n\t\tset_char(\"susp\",  VSUSP);\n\t\tset_char(\"lnext\", VLNEXT);\n\t\tset_char(\"vwerase\",VWERASE);\n\n\t\tset_cflag(\"parenb\", PARENB);\n\t\tset_cflag(\"parodd\", PARODD);\n\t\tset_cflag(\"hupcl\",  HUPCL);\n\t\tset_cflag(\"hup\",    HUPCL); /* alias */\n\t\tset_cflag(\"cstopb\", CSTOPB);\n\t\tset_cflag(\"cread\",  CREAD);\n\t\tset_cflag(\"clocal\", CLOCAL);\n\n\t\tset_ctoggle(\"cs5\", CSIZE, CS5);\n\t\tset_ctoggle(\"cs6\", CSIZE, CS6);\n\t\tset_ctoggle(\"cs7\", CSIZE, CS7);\n\t\tset_ctoggle(\"cs8\", CSIZE, CS8);\n\n\t\tset_iflag(\"ignbrk\", IGNBRK);\n\t\tset_iflag(\"brkint\", BRKINT);\n\t\tset_iflag(\"ignpar\", IGNPAR);\n\t\tset_iflag(\"parmrk\", PARMRK);\n\t\tset_iflag(\"inpck\",  INPCK);\n\t\tset_iflag(\"istrip\", ISTRIP);\n\t\tset_iflag(\"inlcr\",  INLCR);\n\t\tset_iflag(\"igncr\",  IGNCR);\n\t\tset_iflag(\"icrnl\",  ICRNL);\n\t\tset_iflag(\"ixon\",   IXON);\n\t\tset_iflag(\"ixany\",  IXANY);\n\t\tset_iflag(\"ixoff\",  IXOFF);\n\t\tset_iflag(\"iuclc\",  IUCLC);\n\n\t\tset_oflag(\"olcuc\",  OLCUC);\n\t\tset_oflag(\"opost\",  OPOST);\n\t\tset_oflag(\"onlcr\",  ONLCR);\n\t\tset_oflag(\"ocrnl\",  OCRNL);\n\t\tset_oflag(\"onocr\",  ONOCR);\n\t\tset_oflag(\"onlret\", ONLRET);\n\t\tset_oflag(\"ofill\",  OFILL);\n\t\tset_oflag(\"ofdel\",  OFDEL);\n\n\t\tset_otoggle(\"cr0\", CRDLY, CR0);\n\t\tset_otoggle(\"cr1\", CRDLY, CR1);\n\t\tset_otoggle(\"cr2\", CRDLY, CR2);\n\t\tset_otoggle(\"cr3\", CRDLY, CR3);\n\n\t\tset_otoggle(\"nl0\", NLDLY, NL0);\n\t\tset_otoggle(\"nl1\", NLDLY, NL1);\n\n\t\tset_otoggle(\"tab0\", TABDLY, TAB0);\n\t\tset_otoggle(\"tab1\", TABDLY, TAB1);\n\t\tset_otoggle(\"tab2\", TABDLY, TAB2);\n\t\tset_otoggle(\"tab3\", TABDLY, TAB3);\n\n\t\tset_otoggle(\"bs0\", BSDLY, BS0);\n\t\tset_otoggle(\"bs1\", BSDLY, BS1);\n\n\t\tset_otoggle(\"ff0\", FFDLY, FF0);\n\t\tset_otoggle(\"ff1\", FFDLY, FF1);\n\n\t\tset_otoggle(\"vt0\", VTDLY, VT0);\n\t\tset_otoggle(\"vt1\", VTDLY, VT1);\n\n\t\tset_lflag(\"isig\",   ISIG);\n\t\tset_lflag(\"icanon\", ICANON);\n\t\tset_lflag(\"iexten\", IEXTEN);\n\t\tset_lflag(\"echo\",   ECHO);\n\t\tset_lflag(\"echoe\",  ECHOE);\n\t\tset_lflag(\"echok\",  ECHOK);\n\t\tset_lflag(\"echonl\", ECHONL);\n\t\tset_lflag(\"echoctl\",ECHOCTL);\n\t\tset_lflag(\"noflsh\", NOFLSH);\n\t\tset_lflag(\"tostop\", TOSTOP);\n\n\t\tfprintf(stderr, \"%s: invalid argument '%s'\\n\", argv[0], argv[i]);\n\t\treturn 1;\n\t}\n\n\ttcsetattr(STDERR_FILENO, TCSADRAIN, &t);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/sudo.c",
    "content": "/**\n * @brief sudo - Run processes as the root user, after authenticating.\n *\n * Our sudo supports cached authentication, so you don't need to keep\n * entering your password.\n *\n * Probably terribly insecure, but our main password auth function is\n * a plain text comparison, so *shrug*.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014 K. Lange\n */\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <time.h>\n#include <termios.h>\n#include <errno.h>\n#include <pwd.h>\n#include <dirent.h>\n#include <sys/utsname.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <toaru/auth.h>\n\n#define MINUTES * 60\n\n#define SUDO_TIME 5 MINUTES\n\nextern int setgroups(int size, const gid_t list[]);\n\nstatic int sudo_loop(int (*prompt_callback)(char * username, char * password, int failures, char * argv[]), char * argv[]) {\n\n\tint fails = 0;\n\n\tif (geteuid() != 0) {\n\t\tfprintf(stderr, \"%s: effective uid is not 0\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tstruct stat buf;\n\tif (stat(\"/var/sudoers\", &buf)) {\n\t\tmkdir(\"/var/sudoers\", 0700);\n\t}\n\n\twhile (1) {\n\t\tint need_password = 1;\n\t\tint need_sudoers  = 1;\n\n\t\tuid_t me = getuid();\n\t\tif (me == 0) {\n\t\t\tneed_password = 0;\n\t\t\tneed_sudoers  = 0;\n\t\t}\n\n\t\tstruct passwd * p = getpwuid(me);\n\t\tif (!p) {\n\t\t\tfprintf(stderr, \"%s: unable to obtain username for real uid=%d\\n\", argv[0], getuid());\n\t\t\treturn 1;\n\t\t}\n\t\tchar * username = strdup(p->pw_name);\n\n\t\tchar token_file[64];\n\t\tsprintf(token_file, \"/var/sudoers/%d\", me); /* TODO: Restrict to this session? */\n\n\t\tif (need_password) {\n\t\t\tstruct stat buf;\n\t\t\tif (!stat(token_file, &buf)) {\n\t\t\t\t/* check the time */\n\t\t\t\tif (buf.st_mtime > (SUDO_TIME) && time(NULL) - buf.st_mtime < (SUDO_TIME)) {\n\t\t\t\t\tneed_password = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (need_password) {\n\t\t\tchar * password = calloc(sizeof(char) * 1024, 1);\n\n\t\t\tif (prompt_callback(username, password, fails, argv)) {\n\t\t\t\tfree(username);\n\t\t\t\tfree(password);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\tint uid = toaru_auth_check_pass(username, password);\n\n\t\t\tfree(password);\n\n\t\t\tif (uid < 0) {\n\t\t\t\tfree(username);\n\t\t\t\tfails++;\n\t\t\t\tif (fails == 3) {\n\t\t\t\t\tfprintf(stderr, \"%s: %d incorrect password attempts\\n\", argv[0], fails);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tfprintf(stderr, \"Sorry, try again.\\n\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t/* Determine if this user is in the sudoers file */\n\t\tif (need_sudoers) {\n\t\t\tFILE * sudoers = fopen(\"/etc/sudoers\",\"r\");\n\t\t\tif (!sudoers) {\n\t\t\t\tfree(username);\n\t\t\t\tfprintf(stderr, \"%s: /etc/sudoers is not available\\n\", argv[0]);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t/* Read each line */\n\t\t\tint in_sudoers = 0;\n\t\t\twhile (!feof(sudoers)) {\n\t\t\t\tchar line[1024];\n\t\t\t\tfgets(line, 1024, sudoers);\n\t\t\t\tchar * nl = strchr(line, '\\n');\n\t\t\t\tif (nl) {\n\t\t\t\t\t*nl = '\\0';\n\t\t\t\t}\n\t\t\t\tif (!strncmp(line,username,1024)) {\n\t\t\t\t\tin_sudoers = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfclose(sudoers);\n\n\t\t\tif (!in_sudoers) {\n\t\t\t\tfprintf(stderr, \"%s is not in sudoers file.\\n\", username);\n\t\t\t\tfree(username);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\n\t\tfree(username);\n\n\t\t/* Write a timestamp file */\n\t\tFILE * f = fopen(token_file, \"w\");\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: (warning) failed to create token file\\n\", argv[0]);\n\t\t}\n\t\tfclose(f);\n\n\t\t/* Set username to root */\n\t\tputenv(\"USER=root\");\n\n\t\t/* Actually become root, so real user id = 0 */\n\t\tsetgid(0);\n\t\tsetuid(0);\n\t\tsetgroups(0,NULL);\n\n\t\tif (!strcmp(argv[1], \"-s\")) {\n\t\t\targv[1] = getenv(\"SHELL\");\n\t\t}\n\n\t\tchar ** args = &argv[1];\n\t\texecvp(args[0], args);\n\n\t\t/* XXX: There are other things that can cause an exec to fail. */\n\t\tfprintf(stderr, \"%s: %s: command not found\\n\", argv[0], args[0]);\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int basic_callback(char * username, char * password, int fails, char * argv[]) {\n\tfprintf(stderr, \"[%s] password for %s: \", argv[0], username);\n\tfflush(stderr);\n\n\t/* Disable echo */\n\tstruct termios old, new;\n\ttcgetattr(fileno(stdin), &old);\n\tnew = old;\n\tnew.c_lflag &= (~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n\n\tfgets(password, 1024, stdin);\n\tif (feof(stdin)) return 1;\n\n\tpassword[strlen(password)-1] = '\\0';\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n\tfprintf(stderr, \"\\n\");\n\n\treturn 0;\n}\n\nvoid usage(int argc, char * argv[]) {\n\tfprintf(stderr, \"usage: %s [command]\\n\", argv[0]);\n}\n\nint main(int argc, char ** argv) {\n\n\tif (argc < 2) {\n\t\tusage(argc, argv);\n\t\treturn 1;\n\t}\n\n\treturn sudo_loop(basic_callback, argv);\n}\n\n"
  },
  {
    "path": "apps/sync.c",
    "content": "/**\n * @brief Wait for filesystem buffered writes to finish.\n *\n * And by \"wait\" and \"to finish\" I mean \"tell the block device\n * to write everything because it doesn't do that asynchronously\".\n *\n * Currently only syncs the block device that owns the current\n * working directory because I haven't set this up to iterate\n * over mounts yet.\n *\n * It will correctly wait until the sync finishes, though.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n\nint main(int argc, char * argv[]) {\n\tchar * file = \".\";\n\tif (argc > 1) {\n\t\tfile = argv[1];\n\t}\n\tint fd = open(file,O_RDONLY|O_DIRECTORY);\n\tif (fd < 0) {\n\t\tfd = open(file,O_RDONLY);\n\t}\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"sync: open: %s: %s (%d)\\n\", file, strerror(errno), fd);\n\t\treturn 1;\n\t}\n\tint res = ioctl(fd, IOCTLSYNC, NULL);\n\tif (res < 0) {\n\t\tfprintf(stderr, \"sync: ioctl: %s (%d)\\n\", strerror(errno), fd);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/sysfunc.c",
    "content": "/**\n * @brief Exceute \"extended system function\" syscalls.\n *\n * Most of these are deprecated, and the ones that are useful\n * to call manually are all behind other utilities.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <errno.h>\n#include <string.h>\n\n#include <sys/sysfunc.h>\n\nint main(int argc, char ** argv) {\n\tif (argc < 2) return 1;\n\tint ret = sysfunc(atoi(argv[1]), &argv[2]);\n\tif (ret < 0) {\n\t\tfprintf(stderr, \"%s: %s\\n\", argv[0], strerror(errno));\n\t}\n\treturn ret;\n}\n"
  },
  {
    "path": "apps/sysinfo.c",
    "content": "/**\n * @brief Display system information.\n *\n * Similar to tools like 'screenfetch', this displays information\n * about ToaruOS, the current machine state, and the user's\n * configuration options, alongside a terminal-safe rendition\n * of the OS's logo.\n *\n * This is a bit overcomplicated as we used to show an elaborate\n * logo from a BMP/PNG, but our current logo can be nicely\n * represented with block characters so that's kind moot; maybe\n * this should be simplified...\n *\n * Uses a few other utilities:\n *   hostname\n *   uname -sr\n *   uptime -p\n *   msk count\n *   sh -v\n *   yutani-query resolution\n *   font-tool -n\n *   cpu-name.krk\n *   free -ut\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/utsname.h>\n\n#include <toaru/graphics.h>\n#include <toaru/termemu.h>\n\n#include \"toaru_logo.h\"\n\n#define NUM_DATA_LINES 30\n\nchar data_lines[NUM_DATA_LINES][100];\nconst char * prog_lines[NUM_DATA_LINES] = {NULL};\n\n#define C_A \"\\033[34;1m\"\n#define C_O \"\\033[0m\"\n\nvoid print_thing(int j) {\n\tprintf(\"\\033[0m  %s\", data_lines[j]);\n\tfflush(stdout);\n\tif (prog_lines[j]) {\n\t\tsystem(prog_lines[j]);\n\t} else {\n\t\tprintf(\"\\n\");\n\t}\n}\n\nstatic void reset(void) {\n\tprintf(\"\\033[0m\");\n}\n\nstatic int term_is_toaru = 0;\nstatic void foreground_color(uint32_t color) {\n\tprintf(term_is_toaru ? \"\\033[38;6;%d;%d;%d;%dm\" : \"\\033[38;2;%d;%d;%dm\",\n\t\t(int)_RED(color), (int)_GRE(color), (int)_BLU(color), (int)_ALP(color));\n}\n\nstatic void background_color(uint32_t color) {\n\tprintf(term_is_toaru ? \"\\033[48;6;%d;%d;%d;%dm\" : \"\\033[48;2;%d;%d;%dm\",\n\t\t(int)_RED(color), (int)_GRE(color), (int)_BLU(color), (int)_ALP(color));\n}\n\nint main(int argc, char * argv[]) {\n\n\t/* Prepare data */\n\tchar * user = getenv(\"USER\");\n\tchar * wm_theme = getenv(\"WM_THEME\");\n\tchar * term = getenv(\"TERM\");\n\tterm_is_toaru = term && strstr(term,\"toaru\");\n\n\tint i = 0;\n\n\tprog_lines[i] = \"hostname\";\n\tsprintf(data_lines[i++], C_A \"%s\" C_O \"@\" C_A, user);\n\n\tprog_lines[i] = \". /etc/os-release; echo ${PRETTY_NAME}\";\n\tsprintf(data_lines[i++], C_A \"OS: \" C_O);\n\n\tprog_lines[i] = \"uname -sr\";\n\tsprintf(data_lines[i++], C_A \"Kernel: \" C_O);\n\n\tprog_lines[i] = \"uptime -p\";\n\tsprintf(data_lines[i++], C_A \"Uptime: \" C_O);\n\n\tprog_lines[i] = \"msk count\";\n\tsprintf(data_lines[i++], C_A \"Packages: \" C_O);\n\n\tprog_lines[i] = \"esh -v\";\n\tsprintf(data_lines[i++], C_A \"Shell: \" C_O);\n\n\tprog_lines[i] = \"yutani-query resolution\";\n\tsprintf(data_lines[i++], C_A \"Resolution: \" C_O);\n\n\t/* no command */\n\tsprintf(data_lines[i++], C_A \"WM: \" C_O \"Yutani\");\n\n\t/* from environment */\n\tsprintf(data_lines[i++], C_A \"WM Theme: \" C_O \"%s\", wm_theme);\n\n\tprog_lines[i] = \"font-tool -n\";\n\tsprintf(data_lines[i++], C_A \"Font: \" C_O);\n\n\tprog_lines[i] = \"cpu-name.krk\";\n\tsprintf(data_lines[i++], C_A \"CPU: \" C_O);\n\n\tprog_lines[i] = \"free -ut\";\n\tsprintf(data_lines[i++], C_A \"RAM: \" C_O);\n\n\tint j = 0;\n\tfor (unsigned int y = 0; y < gimp_image.height; y += 2) {\n\t\tfor (unsigned int x = 0; x < gimp_image.width; x += 1) {\n\t\t\tunsigned char r_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4];\n\t\t\tunsigned char g_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 1];\n\t\t\tunsigned char b_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 2];\n\t\t\tunsigned char a_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 3];\n\t\t\tunsigned char r_b = 0;\n\t\t\tunsigned char g_b = 0;\n\t\t\tunsigned char b_b = 0;\n\t\t\tunsigned char a_b = 0;\n\t\t\tif (y != gimp_image.height - 1) {\n\t\t\t\tr_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4];\n\t\t\t\tg_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 1];\n\t\t\t\tb_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 2];\n\t\t\t\ta_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 3];\n\t\t\t}\n\n\t\t\tuint32_t out = alpha_blend_rgba(\n\t\t\t\trgba(0,0,0,TERM_DEFAULT_OPAC),\n\t\t\t\tpremultiply(rgba(r_t, g_t, b_t, a_t)));\n\n\t\t\tuint32_t back = alpha_blend_rgba(\n\t\t\t\trgba(0,0,0,TERM_DEFAULT_OPAC),\n\t\t\t\tpremultiply(rgba(r_b, g_b, b_b, a_b)));\n\n\t\t\t/* Are both cells transparent? */\n\t\t\tif (a_t == 0 && a_b == 0) {\n\t\t\t\treset();\n\t\t\t\tprintf(\" \");\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (a_b == 0) {\n\t\t\t\treset();\n\t\t\t\tforeground_color(out);\n\t\t\t\tprintf(\"▀\");\n\t\t\t} else if (a_t == 0) {\n\t\t\t\treset();\n\t\t\t\tforeground_color(back);\n\t\t\t\tprintf(\"▄\");\n\t\t\t} else {\n\t\t\t\tforeground_color(back);\n\t\t\t\tbackground_color(out);\n\t\t\t\tprintf(\"▄\");\n\t\t\t}\n\t\t}\n\t\tif (j < i) {\n\t\t\tprint_thing(j);\n\t\t\tj++;\n\t\t} else {\n\t\t\tprintf(\"\\033[0m\\n\");\n\t\t}\n\t}\n\n\twhile (j < i) {\n\t\tfor (int x = 0; x < (int)gimp_image.width; x++) {\n\t\t\tprintf(\" \");\n\t\t}\n\t\tprint_thing(j);\n\t\tj++;\n\t}\n}\n"
  },
  {
    "path": "apps/t_mbstowcs.c",
    "content": "/**\n * @brief Test tool for libc UTF8 decoding\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\nint main(int argc, char * argv[]) {\n\tsize_t req = mbstowcs(NULL, argv[1], 0);\n\twchar_t * dest = malloc(sizeof(wchar_t) * req);\n\tmbstowcs(dest, argv[1], req+1);\n\n\tfor (size_t i = 0; i < req; ++i) {\n\t\tchar tmp[8];\n\t\twchar_t in[] = {dest[i], L'\\0'};\n\t\twcstombs(tmp, in, 8);\n\t\tfprintf(stdout, \"U+%4x %s\\n\", dest[i], tmp);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/tar.c",
    "content": "/**\n * @brief tar - extract archives\n *\n * This is a very minimal and incomplete implementation of tar.\n * It supports on ustar-formatted archives, and its arguments\n * must be the - forms. As of writing, creating archives is not\n * supported. Decompression is supported by piping through gunzip.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2020 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n\n#include <toaru/inflate.h>\n\nstruct ustar {\n\tchar filename[100];\n\tchar mode[8];\n\tchar ownerid[8];\n\tchar groupid[8];\n\n\tchar size[12];\n\tchar mtime[12];\n\n\tchar checksum[8];\n\tchar type[1];\n\tchar link[100];\n\n\tchar ustar[6];\n\tchar version[2];\n\n\tchar owner[32];\n\tchar group[32];\n\n\tchar dev_major[8];\n\tchar dev_minor[8];\n\n\tchar prefix[155];\n\tchar padding[12];\n};\n\nstatic struct ustar * extract_file(FILE * f) {\n\tstatic struct ustar _ustar;\n\tif (fread(&_ustar, 1, sizeof(struct ustar), f) != sizeof(struct ustar)) {\n\t\tfprintf(stderr, \"failed to read file\\n\");\n\t\treturn NULL;\n\t}\n\n\tif (_ustar.filename[0] == 0) {\n\t\treturn NULL;\n\t}\n\n\treturn &_ustar;\n}\n\nstatic unsigned int interpret_mode(struct ustar * file) {\n\treturn \n\t\t((file->mode[0] - '0') << 18) |\n\t\t((file->mode[1] - '0') << 15) |\n\t\t((file->mode[2] - '0') << 12) |\n\t\t((file->mode[3] - '0') <<  9) |\n\t\t((file->mode[4] - '0') <<  6) |\n\t\t((file->mode[5] - '0') <<  3) |\n\t\t((file->mode[6] - '0') <<  0);\n}\n\nstatic unsigned int interpret_size(struct ustar * file) {\n\tif (file->size[0] != '0') {\n\t\tfprintf(stderr, \"\\033[3;32mWarning:\\033[0;3m File is too big.\\033[0m\\n\");\n\t}\n\treturn\n\t\t((file->size[ 0] - '0') << 30) |\n\t\t((file->size[ 1] - '0') << 27) |\n\t\t((file->size[ 2] - '0') << 24) |\n\t\t((file->size[ 3] - '0') << 21) |\n\t\t((file->size[ 4] - '0') << 18) |\n\t\t((file->size[ 5] - '0') << 15) |\n\t\t((file->size[ 6] - '0') << 12) |\n\t\t((file->size[ 7] - '0') <<  9) |\n\t\t((file->size[ 8] - '0') <<  6) |\n\t\t((file->size[ 9] - '0') <<  3) |\n\t\t((file->size[10] - '0') <<  0);\n}\n\nstatic const char * type_to_string(char type) {\n\tstatic char unknown[100];\n\tswitch (type) {\n\t\tcase '\\0':\n\t\tcase '0':\n\t\t\treturn \"Normal file\";\n\t\tcase '1':\n\t\t\treturn \"Hard link (unsupported)\";\n\t\tcase '2':\n\t\t\treturn \"Symolic link\";\n\t\tcase '3':\n\t\t\treturn \"Character special (unsupported)\";\n\t\tcase '4':\n\t\t\treturn \"Block special (unsupported)\";\n\t\tcase '5':\n\t\t\treturn \"Directory\";\n\t\tcase '6':\n\t\t\treturn \"FIFO (unsupported)\";\n\t\tcase 'g':\n\t\t\treturn \"Extended header\";\n\t\tcase 'x':\n\t\t\treturn \"Extended preheader\";\n\t\tdefault:\n\t\t\tsprintf(unknown, \"Uknown: %c\", type);\n\t\t\treturn unknown;\n\t}\n}\n\n#if 0\nstatic void dump_file(struct ustar * file) {\n\tfprintf(stdout, \"\\033[1m%.155s%.100s\\033[0m\\n\", file->prefix, file->filename);\n\tfprintf(stdout, \"%c - %s\\n\", file->type[0], type_to_string(file->type[0]));\n\tfprintf(stdout, \"File size: %u\\n\", interpret_size(file));\n}\n#endif\n\n#define CHUNK_SIZE 4096\nstatic void write_file(struct ustar * file, FILE * f, FILE * mf, char * name) {\n\tsize_t length = interpret_size(file);\n\tchar buf[CHUNK_SIZE];\n\twhile (length > CHUNK_SIZE) {\n\t\tfread( buf, 1, CHUNK_SIZE, f);\n\t\tfwrite(buf, 1, CHUNK_SIZE, mf);\n\t\tlength -= CHUNK_SIZE;\n\t}\n\tif (length > 0) {\n\t\tfread( buf, 1, length, f);\n\t\tfwrite(buf, 1, length, mf);\n\t}\n\tfflush(mf);\n\tif (mf != stdout) {\n\t\tfclose(mf);\n\t\tchmod(name, interpret_mode(file));\n\t}\n}\n\nstatic void _seek_forward(FILE * f, size_t amount) {\n\tfor (size_t i = 0; i < amount; ++i) {\n\t\tfgetc(f);\n\t}\n}\n\nstatic void usage(char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"tar - extract ustar archives\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-ctxvaf] [name]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -f     \\033[3mfile archive to open\\033[0m\\n\"\n\t\t\t\" -x     \\033[3mextract\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nstatic int matches_files(int argc, char * argv[], int optind, char * filename) {\n\twhile (optind < argc) {\n\t\tif (!strcmp(argv[optind], filename)) return 1;\n\t\toptind++;\n\t}\n\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\n\tint opt;\n\tchar * fname = NULL;\n\tint verbose = 0;\n\tint action = 0;\n\tint compressed = 0;\n\tint to_stdout = 0;\n\tint only_matches = 0;\n#define TAR_ACTION_EXTRACT 1\n#define TAR_ACTION_CREATE  2\n#define TAR_ACTION_LIST    3\n\n\tif (argc > 1 && argv[1][0] != '-') {\n\t\tswitch(argv[1][0]) {\n\t\t\tcase 'x':\n\t\t\t\taction = TAR_ACTION_EXTRACT;\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\taction = TAR_ACTION_CREATE;\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\taction = TAR_ACTION_LIST;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tusage(argv);\n\t\t\t\treturn 1;\n\t\t}\n\n\t\t/* Go through the rest of the argument */\n\t\tint expect_file = 0;\n\t\tfor (int i = 1; argv[1][i]; ++i) {\n\t\t\tswitch (argv[1][i]) {\n\t\t\t\tcase 'f':\n\t\t\t\t\texpect_file = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'v':\n\t\t\t\t\tverbose = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'z':\n\t\t\t\t\tcompressed = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'O':\n\t\t\t\t\tto_stdout = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tfprintf(stderr, \"%s: %c: unrecognized option\\n\", argv[0], argv[1][i]);\n\t\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\n\t\toptind = 2;\n\n\t\tif (expect_file) {\n\t\t\tif (argc < 3) {\n\t\t\t\tfprintf(stderr, \"%s: filename expected after arguments\\n\", argv[0]);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\toptind = 3;\n\t\t\tfname = argv[2];\n\t\t}\n\n\t\tgoto _skip_getopt;\n\t}\n\n\twhile ((opt = getopt(argc, argv, \"?ctxzvaf:O\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'c':\n\t\t\t\tif (action) {\n\t\t\t\t\tfprintf(stderr, \"%s: %c: already specified action\\n\", argv[0], opt);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\taction = TAR_ACTION_CREATE;\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tfname = optarg;\n\t\t\t\tbreak;\n\t\t\tcase 'x':\n\t\t\t\tif (action) {\n\t\t\t\t\tfprintf(stderr, \"%s: %c: already specified action\\n\", argv[0], opt);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\taction = TAR_ACTION_EXTRACT;\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tif (action) {\n\t\t\t\t\tfprintf(stderr, \"%s: %c: already specified action\\n\", argv[0], opt);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\taction = TAR_ACTION_LIST;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tverbose = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'z':\n\t\t\t\tcompressed = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'O':\n\t\t\t\tto_stdout = 1;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tusage(argv);\n\t\t\t\treturn 1;\n\t\t\tdefault:\n\t\t\t\tfprintf(stderr, \"%s: unsupported option '%c'\\n\", argv[0], opt);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n_skip_getopt:\n\t(void)0;\n\n\tif (!fname) {\n\t\tfname = \"-\";\n\t}\n\n\tif (optind < argc) {\n\t\tonly_matches = 1;\n\t}\n\n\tif (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) {\n\n\t\tFILE * f;\n\t\tif (!strcmp(fname,\"-\")) {\n\t\t\tf = stdin;\n\t\t} else {\n\t\t\tf = fopen(fname,\"r\");\n\t\t}\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], fname, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\n\t\tif (compressed) {\n\t\t\tint fds[2];\n\t\t\tpipe(fds);\n\n\t\t\tint child = fork();\n\t\t\tif (child == 0) {\n\t\t\t\t/* Close the read end */\n\t\t\t\tclose(fds[0]);\n\t\t\t\t/* Put f's fd into stdin */\n\t\t\t\tdup2(fileno(f), STDIN_FILENO);\n\t\t\t\t/* Make stdout the pipe */\n\t\t\t\tdup2(fds[1], STDOUT_FILENO);\n\t\t\t\t/* Execeute gzunzip */\n\t\t\t\tchar * args[] = {\"gunzip\",\"-c\",NULL};\n\t\t\t\texit(execvp(\"gunzip\",args));\n\t\t\t} else if (child < 0) {\n\t\t\t\tfprintf(stderr, \"%s: failed to fork gunzip for compressed archive\\n\", argv[0]);\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t/* Reattach f to pipe */\n\t\t\tclose(fds[1]);\n\t\t\tf = fdopen(fds[0], \"r\");\n\t\t}\n\n\t\tchar tmpname[1024] = {0};\n\t\tint  last_was_long = 0;\n\n\t\twhile (!feof(f)) {\n\t\t\tstruct ustar * file = extract_file(f);\n\n\t\t\tif (!file) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (action == TAR_ACTION_LIST) {\n\t\t\t\tif (verbose) {\n\t\t\t\t\tfprintf(stdout, \"%10d %c %.155s%.100s\\n\", interpret_size(file), file->type[0], file->prefix, file->filename);\n\t\t\t\t} else {\n\t\t\t\t\tfprintf(stdout, \"%.155s%.100s\\n\", file->prefix, file->filename);\n\t\t\t\t}\n\t\t\t\t_seek_forward(f, interpret_size(file));\n\t\t\t} else if (action == TAR_ACTION_EXTRACT) {\n\t\t\t\tif (verbose) {\n\t\t\t\t\tfprintf(stdout, \"%.155s%.100s\\n\", file->prefix, file->filename);\n\t\t\t\t}\n\t\t\t\tchar name[1025] = {0};\n\t\t\t\tif (last_was_long) {\n\t\t\t\t\tstrncat(name, tmpname, 1024);\n\t\t\t\t\tlast_was_long = 0;\n\t\t\t\t} else {\n\t\t\t\t\tstrncat(name, file->prefix, 167);\n\t\t\t\t\tstrncat(name, file->filename, 512);\n\t\t\t\t}\n\n\t\t\t\tif (file->type[0] == '0' || file->type[0] == 0) {\n\t\t\t\t\tFILE * mf = to_stdout ? stdout : fopen(name,\"w\");\n\t\t\t\t\tif (!mf) {\n\t\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s\\n\", argv[0], fname, name, strerror(errno));\n\t\t\t\t\t\t_seek_forward(f, interpret_size(file));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (!only_matches || matches_files(argc,argv,optind,name)) {\n\t\t\t\t\t\t\twrite_file(file,f,mf,name);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (file->type[0] == '5') {\n\t\t\t\t\tif (!to_stdout) {\n\t\t\t\t\t\tif (name[strlen(name)-1] == '/') {\n\t\t\t\t\t\t\tname[strlen(name)-1] = '\\0';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (strlen(name)) {\n\t\t\t\t\t\t\tif (!only_matches || matches_files(argc,argv,optind,name)) {\n\t\t\t\t\t\t\t\tif (mkdir(name, 0777) < 0) {\n\t\t\t\t\t\t\t\t\tif (errno != EEXIST) {\n\t\t\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s\\n\", argv[0], fname, name, strerror(errno));\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else if (file->type[0] == '1') {\n\t\t\t\t\tif (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) {\n\t\t\t\t\t\tchar tmp[356] = {0};\n\t\t\t\t\t\tstrncat(tmp, file->link, 355);\n\t\t\t\t\t\tFILE * mf = fopen(name,\"w\");\n\t\t\t\t\t\tif (!mf) {\n\t\t\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s\\n\", argv[0], fname, name, strerror(errno));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tFILE * source = fopen(tmp, \"r\");\n\t\t\t\t\t\t\tif (!source) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s\\n\", argv[0], fname, tmp, strerror(errno));\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\twhile (!feof(source)) {\n\t\t\t\t\t\t\t\t\tchar buf[4096];\n\t\t\t\t\t\t\t\t\tssize_t r = fread(buf, 1, 4096, source);\n\t\t\t\t\t\t\t\t\tfwrite(buf, 1, r, mf);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfclose(source);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfclose(mf);\n\t\t\t\t\t\t\tchmod(name, interpret_mode(file));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_seek_forward(f, interpret_size(file));\n\t\t\t\t} else if (file->type[0] == '2') {\n\t\t\t\t\tif (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) {\n\t\t\t\t\t\tchar tmp[356] = {0};\n\t\t\t\t\t\tstrncat(tmp, file->link, 355);\n\t\t\t\t\t\tif (symlink(tmp, name) < 0) {\n\t\t\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s: %s\\n\", argv[0], fname, name, tmp, strerror(errno));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t_seek_forward(f, interpret_size(file));\n\t\t\t\t} else if (file->type[0] == 'L') {\n\t\t\t\t\t/* This is a GNU Long Name block; store its contents as a file name */\n\t\t\t\t\tsize_t s = interpret_size(file);\n\t\t\t\t\tfread(tmpname, 1, s, f);\n\t\t\t\t\ttmpname[s] = '\\0';\n\t\t\t\t\tlast_was_long = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfprintf(stderr, \"%s: %s: %s: %s\\n\", argv[0], fname, name, type_to_string(file->type[0]));\n\t\t\t\t\t_seek_forward(f, interpret_size(file));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsize_t file_size = interpret_size(file);\n\t\t\tif (file_size % 512) {\n\t\t\t\t_seek_forward(f, 512 - (file_size % 512));\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfprintf(stderr, \"%s: unsupported action\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/tee.c",
    "content": "/**\n * @brief tee - copy stdin to stdout and to specified files\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <signal.h>\n#include <string.h>\n#include <errno.h>\n\nint main(int argc, char * argv[]) {\n\tint append = 0;\n\tint ret_val = 0;\n\tint opt;\n\n\twhile ((opt = getopt(argc, argv, \"ai\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'a':\n\t\t\t\tappend = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'i':\n\t\t\t\tsignal(SIGINT, SIG_IGN);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tint file_count = argc - optind;\n\tFILE ** files = malloc(sizeof(FILE *) * file_count);\n\n\tfor (int i = 0, j = optind; j < argc && i < file_count; j++) {\n\t\tfiles[i] = fopen(argv[j], append ? \"a\" : \"w\");\n\t\tif (!files[i]) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[j], strerror(errno));\n\t\t\tret_val = 1;\n\t\t\tfile_count--;\n\t\t\tcontinue;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\twhile (!feof(stdin)) {\n\t\tint c = fgetc(stdin);\n\t\tif (c >= 0) {\n\t\t\tfputc(c, stdout);\n\n\t\t\tfor (int i = 0; i < file_count; ++i) {\n\t\t\t\tfputc(c, files[i]);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (int i = 0; i < file_count; ++i) {\n\t\tfclose(files[i]);\n\t}\n\n\treturn ret_val;\n}\n"
  },
  {
    "path": "apps/terminal-font.h",
    "content": "/**\n * @file apps/terminal-font.h\n * @brief Fallback font used to render text where the TrueType renderer would be inappropriate.\n *\n * Used variously by Terminal, splash-log, and the kernel.\n *\n * This is baked bitmap version of Deja Vu Sans Mono.\n *\n * @copyright\n * Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.\n *\n * Bitstream Vera Fonts Copyright\n * ------------------------------\n *\n * Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this\n * license (\"Fonts\") and associated documentation files (the \"Font Software\"), to reproduce and distribute the\n * Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell\n * copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject\n * to the following conditions:\n *\n * The above copyright and trademark notices and this permission notice shall be included in all copies of one\n * or more of the Font Software typefaces.\n *\n * The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or\n * characters in the Fonts may be modified and additional glyphs or  or characters may be added to the Fonts,\n * only if the fonts are renamed to names not containing either the words \"Bitstream\" or the word \"Vera\".\n *\n * This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified\n * and is distributed under the \"Bitstream Vera\" names.\n *\n * The Font Software may be sold as part of a larger software package but no copy of one or more of the Font\n * Software typefaces may be sold by itself.\n *\n * THE FONT SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT\n * LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT,\n * PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM,\n * DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,\n * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT\n * SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.\n *\n * Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be\n * used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior\n * written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information,\n * contact: fonts at gnome dot org.\n */\n#define LARGE_FONT_CELL_WIDTH 8\n#define LARGE_FONT_CELL_HEIGHT 17\n#define LARGE_FONT_MASK 7\nuint8_t large_font[][17] = {\n\t/* 0 32 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 1 9786 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*  #    #  */\n\t\t/* # #  # # */\n\t\t/* # #  # # */\n\t\t/* #  ## #  */\n\t\t/*  #    #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x42,\n\t\t0xa5,\n\t\t0xa5,\n\t\t0x9a,\n\t\t0x42,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 2 9787 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*  # ## #  */\n\t\t/* ## ## ## */\n\t\t/* ######## */\n\t\t/* ## ## #  */\n\t\t/*  ######  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x5a,\n\t\t0xdb,\n\t\t0xff,\n\t\t0xda,\n\t\t0x7e,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 3 9829 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##  ##  */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*  ######  */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x66,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0x7e,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 4 9830 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   ####   */\n\t\t/*  #####   */\n\t\t/*  ######  */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x3c,\n\t\t0x7c,\n\t\t0x7e,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 5 9827 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   ####   */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*  ######  */\n\t\t/* ######## */\n\t\t/*  ## ###  */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x3c,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x7e,\n\t\t0xff,\n\t\t0x6e,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 6 9824 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   ###    */\n\t\t/*   ####   */\n\t\t/*  ######  */\n\t\t/*  ######  */\n\t\t/*  ### ##  */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x38,\n\t\t0x3c,\n\t\t0x7e,\n\t\t0x7e,\n\t\t0x76,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 7 8226 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   ####   */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x3c,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 8 9688 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ###  ### */\n\t\t/* ##    ## */\n\t\t/* ##    ## */\n\t\t/* ###  ### */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xe7,\n\t\t0xc3,\n\t\t0xc3,\n\t\t0xe7,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 9 9675 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  #    #  */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/*  #    #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x42,\n\t\t0x81,\n\t\t0x81,\n\t\t0x81,\n\t\t0x81,\n\t\t0x42,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 10 9689 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ##    ## */\n\t\t/* # #### # */\n\t\t/* # #### # */\n\t\t/* # #### # */\n\t\t/* # #### # */\n\t\t/* # #### # */\n\t\t/* # #### # */\n\t\t/* ##    ## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xc3,\n\t\t0xbd,\n\t\t0xbd,\n\t\t0xbd,\n\t\t0xbd,\n\t\t0xbd,\n\t\t0xbd,\n\t\t0xc3,\n\t\t0xff,\n\t\t0xff,\n\t\t0x00,\n\t},\n\t/* 11 9794 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*      ### */\n\t\t/*      # # */\n\t\t/*  ####    */\n\t\t/* #    #   */\n\t\t/* #    #   */\n\t\t/* #    #   */\n\t\t/*  ####    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x07,\n\t\t0x05,\n\t\t0x78,\n\t\t0x84,\n\t\t0x84,\n\t\t0x84,\n\t\t0x78,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 12 9792 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*   ####   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x3c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x38,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 13 9834 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    # ##  */\n\t\t/*    #  #  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  ###     */\n\t\t/*  ###     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x16,\n\t\t0x12,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x70,\n\t\t0x70,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 14 9835 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   # ###  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*  ##   #  */\n\t\t/*  ##  ##  */\n\t\t/*      ##  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x2e,\n\t\t0x22,\n\t\t0x22,\n\t\t0x22,\n\t\t0x22,\n\t\t0x22,\n\t\t0x62,\n\t\t0x66,\n\t\t0x06,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 15 9788 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #   #   */\n\t\t/*   # #    */\n\t\t/*    #     */\n\t\t/* # # # #  */\n\t\t/*    #     */\n\t\t/*   # #    */\n\t\t/*  #   #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x44,\n\t\t0x28,\n\t\t0x10,\n\t\t0xaa,\n\t\t0x10,\n\t\t0x28,\n\t\t0x44,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 16 9658 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #        */\n\t\t/* ####     */\n\t\t/* #######  */\n\t\t/* #####    */\n\t\t/* ##       */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x80,\n\t\t0xf0,\n\t\t0xfe,\n\t\t0xf8,\n\t\t0xc0,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 17 9668 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*        # */\n\t\t/*    ##### */\n\t\t/* ######## */\n\t\t/*    ##### */\n\t\t/*       ## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x01,\n\t\t0x1f,\n\t\t0xff,\n\t\t0x1f,\n\t\t0x03,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 18 8597 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*  # # #   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  # # #   */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x1c,\n\t\t0x28,\n\t\t0x10,\n\t\t0x10,\n\t\t0x2a,\n\t\t0x1c,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 19 8252 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*          */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x44,\n\t\t0x44,\n\t\t0x44,\n\t\t0x44,\n\t\t0x44,\n\t\t0x44,\n\t\t0x00,\n\t\t0x44,\n\t\t0x44,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 20 182 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###### */\n\t\t/*  ##### # */\n\t\t/*  ##### # */\n\t\t/*  ##### # */\n\t\t/*    ### # */\n\t\t/*      # # */\n\t\t/*      # # */\n\t\t/*      # # */\n\t\t/*      # # */\n\t\t/*      # # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3f,\n\t\t0x7d,\n\t\t0x7d,\n\t\t0x7d,\n\t\t0x1d,\n\t\t0x05,\n\t\t0x05,\n\t\t0x05,\n\t\t0x05,\n\t\t0x05,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 21 167 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  #       */\n\t\t/*   #      */\n\t\t/*  # ##    */\n\t\t/*  #  ##   */\n\t\t/*  ##  #   */\n\t\t/*   ## #   */\n\t\t/*     #    */\n\t\t/*      #   */\n\t\t/*  ####    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x40,\n\t\t0x20,\n\t\t0x58,\n\t\t0x4c,\n\t\t0x64,\n\t\t0x34,\n\t\t0x08,\n\t\t0x04,\n\t\t0x78,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 22 9644 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 23 8616 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*  # # #   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  # # #   */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x1c,\n\t\t0x28,\n\t\t0x10,\n\t\t0x10,\n\t\t0x2a,\n\t\t0x1c,\n\t\t0x08,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 24 8593 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*  # # #   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x1c,\n\t\t0x2a,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 25 8595 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  # # #   */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x2a,\n\t\t0x1c,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 26 8594 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*      #   */\n\t\t/* #######  */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x04,\n\t\t0xfe,\n\t\t0x04,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 27 8592 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #      */\n\t\t/*  #       */\n\t\t/* #######  */\n\t\t/*  #       */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x20,\n\t\t0x40,\n\t\t0xfe,\n\t\t0x40,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 28 8735 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 29 8596 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #   #   */\n\t\t/* #######  */\n\t\t/*  #   #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x44,\n\t\t0xfe,\n\t\t0x44,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 30 9650 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*   ####   */\n\t\t/*   ####   */\n\t\t/*  ######  */\n\t\t/*  ######  */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x18,\n\t\t0x18,\n\t\t0x3c,\n\t\t0x3c,\n\t\t0x7e,\n\t\t0x7e,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 31 9660 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #######  */\n\t\t/*  ######  */\n\t\t/*  #####   */\n\t\t/*   ####   */\n\t\t/*   ###    */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xfe,\n\t\t0x7e,\n\t\t0x7c,\n\t\t0x3c,\n\t\t0x38,\n\t\t0x18,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 32 32 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 33 33 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 34 34 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 35 35 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #  #  */\n\t\t/*    #  #  */\n\t\t/*    # ##  */\n\t\t/*  ####### */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/* #######  */\n\t\t/*   # #    */\n\t\t/*  #  #    */\n\t\t/*  #  #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x12,\n\t\t0x12,\n\t\t0x16,\n\t\t0x7f,\n\t\t0x24,\n\t\t0x24,\n\t\t0xfe,\n\t\t0x28,\n\t\t0x48,\n\t\t0x48,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 36 36 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*   #####  */\n\t\t/*  #  #  # */\n\t\t/*  #  #    */\n\t\t/*   ###    */\n\t\t/*     ###  */\n\t\t/*     #  # */\n\t\t/*  #  #  # */\n\t\t/*   #####  */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x3e,\n\t\t0x49,\n\t\t0x48,\n\t\t0x38,\n\t\t0x0e,\n\t\t0x09,\n\t\t0x49,\n\t\t0x3e,\n\t\t0x08,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 37 37 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##      */\n\t\t/* #  #     */\n\t\t/* #  #     */\n\t\t/*  ##   #  */\n\t\t/*    ###   */\n\t\t/*  ##  ##  */\n\t\t/*     #  # */\n\t\t/*     #  # */\n\t\t/*      ##  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x60,\n\t\t0x90,\n\t\t0x90,\n\t\t0x62,\n\t\t0x1c,\n\t\t0x66,\n\t\t0x09,\n\t\t0x09,\n\t\t0x06,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 38 38 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   ##     */\n\t\t/*  #  #  # */\n\t\t/*  #  ## # */\n\t\t/*  #   # # */\n\t\t/*  ##   #  */\n\t\t/*   #### # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x20,\n\t\t0x20,\n\t\t0x30,\n\t\t0x49,\n\t\t0x4d,\n\t\t0x45,\n\t\t0x62,\n\t\t0x3d,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 39 39 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 40 40 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*      #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x04,\n\t\t0x08,\n\t\t0x08,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x08,\n\t\t0x08,\n\t\t0x04,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 41 41 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #      */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x20,\n\t\t0x10,\n\t\t0x10,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x10,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 42 42 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* 12345678 */\n\t\t/*    #     */\n\t\t/* #  #  #  */\n\t\t/*  # # #   */\n\t\t/*   ###    */\n\t\t/*  # # #   */\n\t\t/* #  #  #  */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x92,\n\t\t0x54,\n\t\t0x38,\n\t\t0x54,\n\t\t0x92,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 43 43 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* #######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xfe,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 44 44 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 45 45 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 46 46 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 47 47 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*       #  */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*  #       */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x02,\n\t\t0x04,\n\t\t0x04,\n\t\t0x08,\n\t\t0x08,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x20,\n\t\t0x20,\n\t\t0x40,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 48 48 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #  #  # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x49,\n\t\t0x41,\n\t\t0x41,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 49 49 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 50 50 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  #    ## */\n\t\t/*        # */\n\t\t/*        # */\n\t\t/*       #  */\n\t\t/*     ##   */\n\t\t/*    ##    */\n\t\t/*   #      */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x43,\n\t\t0x01,\n\t\t0x01,\n\t\t0x02,\n\t\t0x0c,\n\t\t0x18,\n\t\t0x20,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 51 51 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  #     # */\n\t\t/*        # */\n\t\t/*       ## */\n\t\t/*    ###   */\n\t\t/*       ## */\n\t\t/*        # */\n\t\t/*  #    ## */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x41,\n\t\t0x01,\n\t\t0x03,\n\t\t0x1c,\n\t\t0x03,\n\t\t0x01,\n\t\t0x43,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 52 52 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*      ##  */\n\t\t/*     # #  */\n\t\t/*    ## #  */\n\t\t/*    #  #  */\n\t\t/*   #   #  */\n\t\t/*  #    #  */\n\t\t/*  ####### */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x06,\n\t\t0x0a,\n\t\t0x1a,\n\t\t0x12,\n\t\t0x22,\n\t\t0x42,\n\t\t0x7f,\n\t\t0x02,\n\t\t0x02,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 53 53 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #####   */\n\t\t/*       ## */\n\t\t/*        # */\n\t\t/*        # */\n\t\t/*  #    ## */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7c,\n\t\t0x03,\n\t\t0x01,\n\t\t0x01,\n\t\t0x43,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 54 54 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ####  */\n\t\t/*   #    # */\n\t\t/*  #       */\n\t\t/*  # ####  */\n\t\t/*  ##   ## */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   ## */\n\t\t/*    ####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1e,\n\t\t0x21,\n\t\t0x40,\n\t\t0x5e,\n\t\t0x63,\n\t\t0x41,\n\t\t0x41,\n\t\t0x23,\n\t\t0x1e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 55 55 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x02,\n\t\t0x02,\n\t\t0x04,\n\t\t0x04,\n\t\t0x08,\n\t\t0x18,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 56 56 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ##   ## */\n\t\t/*  #     # */\n\t\t/*  ##   ## */\n\t\t/*   #####  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x63,\n\t\t0x41,\n\t\t0x63,\n\t\t0x3e,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 57 57 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ##   ## */\n\t\t/*   #### # */\n\t\t/*        # */\n\t\t/*  #    #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x62,\n\t\t0x41,\n\t\t0x41,\n\t\t0x63,\n\t\t0x3d,\n\t\t0x01,\n\t\t0x42,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 58 58 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 59 59 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 60 60 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*       #  */\n\t\t/*    ###   */\n\t\t/*  ##      */\n\t\t/* #        */\n\t\t/*  ##      */\n\t\t/*    ###   */\n\t\t/*       #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x02,\n\t\t0x1c,\n\t\t0x60,\n\t\t0x80,\n\t\t0x60,\n\t\t0x1c,\n\t\t0x02,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 61 61 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xfe,\n\t\t0x00,\n\t\t0x00,\n\t\t0xfe,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 62 62 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #        */\n\t\t/*  ###     */\n\t\t/*     ##   */\n\t\t/*       #  */\n\t\t/*     ##   */\n\t\t/*  ###     */\n\t\t/* #        */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x80,\n\t\t0x70,\n\t\t0x0c,\n\t\t0x02,\n\t\t0x0c,\n\t\t0x70,\n\t\t0x80,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 63 63 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*  #   #   */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x44,\n\t\t0x04,\n\t\t0x08,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 64 64 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ####  */\n\t\t/*   ##  ## */\n\t\t/*   #    # */\n\t\t/*  #   ### */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #   ### */\n\t\t/*   #      */\n\t\t/*   ##     */\n\t\t/*    ####  */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1e,\n\t\t0x33,\n\t\t0x21,\n\t\t0x47,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x47,\n\t\t0x20,\n\t\t0x30,\n\t\t0x1e,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 65 65 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #####  */\n\t\t/*  ##   ## */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x14,\n\t\t0x14,\n\t\t0x14,\n\t\t0x22,\n\t\t0x22,\n\t\t0x3e,\n\t\t0x63,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 66 66 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ######  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x7e,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 67 67 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ####  */\n\t\t/*   #    # */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   #    # */\n\t\t/*    ####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1e,\n\t\t0x21,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x21,\n\t\t0x1e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 68 68 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*  #    #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #    #  */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x42,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x42,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 69 69 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 70 70 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 71 71 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ####  */\n\t\t/*   #    # */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #    ## */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #    # */\n\t\t/*    ####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1e,\n\t\t0x21,\n\t\t0x40,\n\t\t0x40,\n\t\t0x43,\n\t\t0x41,\n\t\t0x41,\n\t\t0x21,\n\t\t0x1e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 72 72 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ####### */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x7f,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 73 73 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 74 74 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*  #   #   */\n\t\t/*   ###    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x04,\n\t\t0x04,\n\t\t0x04,\n\t\t0x04,\n\t\t0x04,\n\t\t0x04,\n\t\t0x44,\n\t\t0x38,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 75 75 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #   #   */\n\t\t/*  #  #    */\n\t\t/*  # #     */\n\t\t/*  ###     */\n\t\t/*  #  #    */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x44,\n\t\t0x48,\n\t\t0x50,\n\t\t0x70,\n\t\t0x48,\n\t\t0x44,\n\t\t0x44,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 76 76 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 77 77 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##   ## */\n\t\t/*  ##   ## */\n\t\t/*  # # # # */\n\t\t/*  # # # # */\n\t\t/*  # # # # */\n\t\t/*  #  #  # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x63,\n\t\t0x63,\n\t\t0x55,\n\t\t0x55,\n\t\t0x55,\n\t\t0x49,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 78 78 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##    # */\n\t\t/*  ##    # */\n\t\t/*  # #   # */\n\t\t/*  # #   # */\n\t\t/*  #  #  # */\n\t\t/*  #   # # */\n\t\t/*  #   # # */\n\t\t/*  #    ## */\n\t\t/*  #    ## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x61,\n\t\t0x61,\n\t\t0x51,\n\t\t0x51,\n\t\t0x49,\n\t\t0x45,\n\t\t0x45,\n\t\t0x43,\n\t\t0x43,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 79 79 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 80 80 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  #    ## */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #    ## */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x43,\n\t\t0x41,\n\t\t0x41,\n\t\t0x43,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 81 81 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   ## */\n\t\t/*    ####  */\n\t\t/*      ##  */\n\t\t/*       #  */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x23,\n\t\t0x1e,\n\t\t0x06,\n\t\t0x02,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 82 82 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  #    ## */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ######  */\n\t\t/*  #    #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x43,\n\t\t0x41,\n\t\t0x41,\n\t\t0x7e,\n\t\t0x42,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 83 83 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ##    # */\n\t\t/*  #       */\n\t\t/*  ##      */\n\t\t/*   #####  */\n\t\t/*       ## */\n\t\t/*        # */\n\t\t/*  #    ## */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x61,\n\t\t0x40,\n\t\t0x60,\n\t\t0x3e,\n\t\t0x03,\n\t\t0x01,\n\t\t0x43,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 84 84 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xfe,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 85 85 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 86 86 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #     # */\n\t\t/*  ##   ## */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x41,\n\t\t0x63,\n\t\t0x22,\n\t\t0x22,\n\t\t0x22,\n\t\t0x14,\n\t\t0x14,\n\t\t0x14,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 87 87 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/*  # ## #  */\n\t\t/*  # ## #  */\n\t\t/*  # ## #  */\n\t\t/*  ##  ##  */\n\t\t/*  ##  ##  */\n\t\t/*  ##  ##  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x81,\n\t\t0x81,\n\t\t0x81,\n\t\t0x5a,\n\t\t0x5a,\n\t\t0x5a,\n\t\t0x66,\n\t\t0x66,\n\t\t0x66,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 88 88 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##   ## */\n\t\t/*   #   #  */\n\t\t/*    # #   */\n\t\t/*    ###   */\n\t\t/*     #    */\n\t\t/*    # #   */\n\t\t/*   ## ##  */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x63,\n\t\t0x22,\n\t\t0x14,\n\t\t0x1c,\n\t\t0x08,\n\t\t0x14,\n\t\t0x36,\n\t\t0x22,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 89 89 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #     #  */\n\t\t/*  #   #   */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x82,\n\t\t0x44,\n\t\t0x28,\n\t\t0x28,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 90 90 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*       ## */\n\t\t/*      ##  */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*   ##     */\n\t\t/*  ##      */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x03,\n\t\t0x06,\n\t\t0x04,\n\t\t0x08,\n\t\t0x10,\n\t\t0x30,\n\t\t0x60,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 91 91 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 92 92 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*       #  */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x20,\n\t\t0x20,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x08,\n\t\t0x08,\n\t\t0x04,\n\t\t0x04,\n\t\t0x02,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 93 93 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*   ###    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x38,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 94 94 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*   # #    */\n\t\t/*  #   #   */\n\t\t/* #     #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x28,\n\t\t0x44,\n\t\t0x82,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 95 95 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t},\n\t/* 96 96 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 97 97 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 98 98 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 99 99 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 100 100 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x02,\n\t\t0x02,\n\t\t0x02,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 101 101 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  ##   #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x62,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 102 102 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*     ##   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x0c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 103 103 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ### #  */\n\t\t/*       #  */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3a,\n\t\t0x02,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t},\n\t/* 104 104 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  # ###   */\n\t\t/*  ##   #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x5c,\n\t\t0x62,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 105 105 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 106 106 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*  ###     */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x70,\n\t\t0x00,\n\t},\n\t/* 107 107 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #   #   */\n\t\t/*  #  #    */\n\t\t/*  # #     */\n\t\t/*  ###     */\n\t\t/*  #  #    */\n\t\t/*  #   #   */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x44,\n\t\t0x48,\n\t\t0x50,\n\t\t0x70,\n\t\t0x48,\n\t\t0x44,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 108 108 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     ###  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x0e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 109 109 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ### ##  */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x76,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 110 110 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  # ###   */\n\t\t/*  ##   #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x5c,\n\t\t0x62,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 111 111 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 112 112 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*  #####   */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x7c,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t},\n\t/* 113 113 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ### #  */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3a,\n\t\t0x02,\n\t\t0x02,\n\t\t0x02,\n\t\t0x00,\n\t},\n\t/* 114 114 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*   ##  #  */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x32,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 115 115 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  #    #  */\n\t\t/*  #       */\n\t\t/*   ####   */\n\t\t/*       #  */\n\t\t/*  #    #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x42,\n\t\t0x40,\n\t\t0x3c,\n\t\t0x02,\n\t\t0x42,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 116 116 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  ######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     ###  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7e,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x0e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 117 117 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 118 118 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x66,\n\t\t0x24,\n\t\t0x24,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 119 119 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #      # */\n\t\t/* #      # */\n\t\t/*  # ## #  */\n\t\t/*  # ## #  */\n\t\t/*  # ## #  */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x81,\n\t\t0x81,\n\t\t0x5a,\n\t\t0x5a,\n\t\t0x5a,\n\t\t0x24,\n\t\t0x24,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 120 120 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*   #  #   */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x24,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x24,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 121 121 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*   #   #  */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*    # #   */\n\t\t/*    ##    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x22,\n\t\t0x24,\n\t\t0x24,\n\t\t0x14,\n\t\t0x18,\n\t\t0x08,\n\t\t0x08,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t},\n\t/* 122 122 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*       #  */\n\t\t/*      #   */\n\t\t/*    ##    */\n\t\t/*   #      */\n\t\t/*  #       */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x02,\n\t\t0x04,\n\t\t0x18,\n\t\t0x20,\n\t\t0x40,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 123 123 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*     ##   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  ##      */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     ##   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x0c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x60,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x0c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 124 124 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 125 125 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##      */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     ##   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  ##      */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x60,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x0c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x60,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 126 126 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###  # */\n\t\t/*  #   ##  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x39,\n\t\t0x46,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 127 8962 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 128 199 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ####  */\n\t\t/*   #    # */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   #    # */\n\t\t/*    ####  */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1e,\n\t\t0x21,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x21,\n\t\t0x1e,\n\t\t0x04,\n\t\t0x04,\n\t\t0x1c,\n\t\t0x00,\n\t},\n\t/* 129 252 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x24,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 130 233 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  ##   #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x62,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 131 226 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 132 228 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   # #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x28,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 133 224 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 134 229 */\n\t{\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x24,\n\t\t0x18,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 135 231 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x04,\n\t\t0x04,\n\t\t0x1c,\n\t\t0x00,\n\t},\n\t/* 136 234 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  ##   #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x62,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 137 235 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #  #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  ##   #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x48,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x62,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 138 232 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  ##   #  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x62,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 139 239 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   # #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x28,\n\t\t0x00,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 140 238 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ##     */\n\t\t/*  #  #    */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x30,\n\t\t0x48,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 141 236 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 142 196 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    # #   */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #####  */\n\t\t/*  ##   ## */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x14,\n\t\t0x00,\n\t\t0x08,\n\t\t0x14,\n\t\t0x14,\n\t\t0x14,\n\t\t0x22,\n\t\t0x22,\n\t\t0x3e,\n\t\t0x63,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 143 197 */\n\t{\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*    # #   */\n\t\t/*   #   #  */\n\t\t/*   #####  */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x1c,\n\t\t0x14,\n\t\t0x14,\n\t\t0x08,\n\t\t0x08,\n\t\t0x14,\n\t\t0x14,\n\t\t0x14,\n\t\t0x22,\n\t\t0x3e,\n\t\t0x22,\n\t\t0x41,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 144 201 */\n\t{\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 145 230 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ## ##   */\n\t\t/*    #  #  */\n\t\t/*    #  #  */\n\t\t/*  ######  */\n\t\t/*  # #     */\n\t\t/*  # #     */\n\t\t/*  ## ###  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x6c,\n\t\t0x12,\n\t\t0x12,\n\t\t0x7e,\n\t\t0x50,\n\t\t0x50,\n\t\t0x6e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 146 198 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*  #  ###  */\n\t\t/*  #  #    */\n\t\t/*  ####    */\n\t\t/* #   #    */\n\t\t/* #   ###  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x4e,\n\t\t0x48,\n\t\t0x78,\n\t\t0x88,\n\t\t0x8e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 147 244 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 148 246 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x24,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 149 242 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 150 251 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 151 249 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x08,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 152 255 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   # #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*   #   #  */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*    # #   */\n\t\t/*    ##    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*   ##     */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x28,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x22,\n\t\t0x24,\n\t\t0x24,\n\t\t0x14,\n\t\t0x18,\n\t\t0x08,\n\t\t0x08,\n\t\t0x10,\n\t\t0x30,\n\t\t0x00,\n\t},\n\t/* 153 214 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    # #   */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x14,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 154 220 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*    # #   */\n\t\t/*          */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x14,\n\t\t0x00,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 155 162 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*  # # #   */\n\t\t/*  # #     */\n\t\t/*  # #     */\n\t\t/*  # #     */\n\t\t/*  # # #   */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x38,\n\t\t0x54,\n\t\t0x50,\n\t\t0x50,\n\t\t0x50,\n\t\t0x54,\n\t\t0x38,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 156 163 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*  ####    */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/* ######   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x78,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0xfc,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 157 165 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #     #  */\n\t\t/*  #   #   */\n\t\t/*   # #    */\n\t\t/* ### ###  */\n\t\t/*    #     */\n\t\t/* #######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x82,\n\t\t0x44,\n\t\t0x28,\n\t\t0xee,\n\t\t0x10,\n\t\t0xfe,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 158 8359 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ####     */\n\t\t/* # ##     */\n\t\t/* # ###### */\n\t\t/* # ## #   */\n\t\t/* #### #   */\n\t\t/* #  #  #  */\n\t\t/* #  #   # */\n\t\t/* #  #   # */\n\t\t/* #   ###  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xf0,\n\t\t0xb0,\n\t\t0xbf,\n\t\t0xb4,\n\t\t0xf4,\n\t\t0x92,\n\t\t0x91,\n\t\t0x91,\n\t\t0x8e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 159 402 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     ###  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  ######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   #      */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x0e,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7e,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x20,\n\t\t0x00,\n\t},\n\t/* 160 225 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*       #  */\n\t\t/*   #####  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x02,\n\t\t0x3e,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 161 237 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*  ###     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x70,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 162 243 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 163 250 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*   ### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x08,\n\t\t0x10,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x3a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 164 241 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ## #   */\n\t\t/*   # ##   */\n\t\t/*          */\n\t\t/*  # ###   */\n\t\t/*  ##   #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x34,\n\t\t0x2c,\n\t\t0x00,\n\t\t0x5c,\n\t\t0x62,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 165 209 */\n\t{\n\t\t/*          */\n\t\t/*   ### #  */\n\t\t/*   # ###  */\n\t\t/*          */\n\t\t/*  ##    # */\n\t\t/*  ##    # */\n\t\t/*  # #   # */\n\t\t/*  # #   # */\n\t\t/*  #  #  # */\n\t\t/*  #   # # */\n\t\t/*  #   # # */\n\t\t/*  #    ## */\n\t\t/*  #    ## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x3a,\n\t\t0x2e,\n\t\t0x00,\n\t\t0x61,\n\t\t0x61,\n\t\t0x51,\n\t\t0x51,\n\t\t0x49,\n\t\t0x45,\n\t\t0x45,\n\t\t0x43,\n\t\t0x43,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 166 170 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*       #  */\n\t\t/*    ####  */\n\t\t/*   #   #  */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x02,\n\t\t0x1e,\n\t\t0x22,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 167 186 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x22,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 168 191 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*   ##     */\n\t\t/*  ##      */\n\t\t/*  #       */\n\t\t/*  #   #   */\n\t\t/*   ###    */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x30,\n\t\t0x60,\n\t\t0x40,\n\t\t0x44,\n\t\t0x38,\n\t\t0x00,\n\t},\n\t/* 169 8976 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 170 172 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*        # */\n\t\t/*        # */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x01,\n\t\t0x01,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 171 189 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*  ### ##  */\n\t\t/*   ###    */\n\t\t/* ##       */\n\t\t/*    ####  */\n\t\t/*       #  */\n\t\t/*      #   */\n\t\t/*     #    */\n\t\t/*    ####  */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x60,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x76,\n\t\t0x38,\n\t\t0xc0,\n\t\t0x1e,\n\t\t0x02,\n\t\t0x04,\n\t\t0x08,\n\t\t0x1e,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 172 188 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*   #      */\n\t\t/*  ### ##  */\n\t\t/*   ###    */\n\t\t/* ##       */\n\t\t/*      #   */\n\t\t/*     ##   */\n\t\t/*    # #   */\n\t\t/*    ####  */\n\t\t/*      #   */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x60,\n\t\t0x20,\n\t\t0x20,\n\t\t0x20,\n\t\t0x76,\n\t\t0x38,\n\t\t0xc0,\n\t\t0x04,\n\t\t0x0c,\n\t\t0x14,\n\t\t0x1e,\n\t\t0x04,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 173 161 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 174 171 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #  #  */\n\t\t/*   ## ##  */\n\t\t/*  ## ##   */\n\t\t/*  ## ##   */\n\t\t/*   ## ##  */\n\t\t/*    #  #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x12,\n\t\t0x36,\n\t\t0x6c,\n\t\t0x6c,\n\t\t0x36,\n\t\t0x12,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 175 187 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #  #    */\n\t\t/*  ## ##   */\n\t\t/*   ## ##  */\n\t\t/*   ## ##  */\n\t\t/*  ## ##   */\n\t\t/*  #  #    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x48,\n\t\t0x6c,\n\t\t0x36,\n\t\t0x36,\n\t\t0x6c,\n\t\t0x48,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 176 9617 */\n\t{\n\t\t/*          */\n\t\t/* #   #    */\n\t\t/*          */\n\t\t/*   #   #  */\n\t\t/*          */\n\t\t/* #   #    */\n\t\t/*          */\n\t\t/*   #   #  */\n\t\t/*          */\n\t\t/* #   #    */\n\t\t/*          */\n\t\t/*   #   #  */\n\t\t/*          */\n\t\t/* #   #    */\n\t\t/*          */\n\t\t/*   #   #  */\n\t\t/*          */\n\t\t0x00,\n\t\t0x88,\n\t\t0x00,\n\t\t0x22,\n\t\t0x00,\n\t\t0x88,\n\t\t0x00,\n\t\t0x22,\n\t\t0x00,\n\t\t0x88,\n\t\t0x00,\n\t\t0x22,\n\t\t0x00,\n\t\t0x88,\n\t\t0x00,\n\t\t0x22,\n\t\t0x00,\n\t},\n\t/* 177 9618 */\n\t{\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t/*  ## #  # */\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t/*  ## #  # */\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t/* #  # ##  */\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t/* #  # ##  */\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t/* #  # ##  */\n\t\t/*  ## #  # */\n\t\t/* #  # ##  */\n\t\t0x69,\n\t\t0x96,\n\t\t0x69,\n\t\t0x69,\n\t\t0x96,\n\t\t0x69,\n\t\t0x69,\n\t\t0x96,\n\t\t0x96,\n\t\t0x69,\n\t\t0x96,\n\t\t0x96,\n\t\t0x69,\n\t\t0x96,\n\t\t0x96,\n\t\t0x69,\n\t\t0x96,\n\t},\n\t/* 178 9619 */\n\t{\n\t\t/* ######## */\n\t\t/*  ### ### */\n\t\t/* ######## */\n\t\t/* ## ### # */\n\t\t/* ######## */\n\t\t/*  ### ### */\n\t\t/* ######## */\n\t\t/* ## ### # */\n\t\t/* ######## */\n\t\t/*  ### ### */\n\t\t/* ######## */\n\t\t/* ## ### # */\n\t\t/* ######## */\n\t\t/*  ### ### */\n\t\t/* ######## */\n\t\t/* ## ### # */\n\t\t/* ######## */\n\t\t0xff,\n\t\t0x77,\n\t\t0xff,\n\t\t0xdd,\n\t\t0xff,\n\t\t0x77,\n\t\t0xff,\n\t\t0xdd,\n\t\t0xff,\n\t\t0x77,\n\t\t0xff,\n\t\t0xdd,\n\t\t0xff,\n\t\t0x77,\n\t\t0xff,\n\t\t0xdd,\n\t\t0xff,\n\t},\n\t/* 179 9474 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 180 9508 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 181 9569 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 182 9570 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ### #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xe8,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 183 9558 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #####    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xf8,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 184 9557 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xf0,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 185 9571 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ### #    */\n\t\t/*     #    */\n\t\t/* ### #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xe8,\n\t\t0x08,\n\t\t0xe8,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 186 9553 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 187 9559 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* #####    */\n\t\t/*     #    */\n\t\t/* ### #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xf8,\n\t\t0x08,\n\t\t0xe8,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 188 9565 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ### #    */\n\t\t/*     #    */\n\t\t/* #####    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xe8,\n\t\t0x08,\n\t\t0xf8,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 189 9564 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* #####    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xf8,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 190 9563 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 191 9488 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ####     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xf0,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 192 9492 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 193 9524 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 194 9516 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 195 9500 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 196 9472 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 197 9532 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ######## */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xff,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 198 9566 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 199 9567 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x2f,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 200 9562 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #### */\n\t\t/*   #      */\n\t\t/*   ###### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x2f,\n\t\t0x20,\n\t\t0x3f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 201 9556 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###### */\n\t\t/*   #      */\n\t\t/*   # #### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3f,\n\t\t0x20,\n\t\t0x2f,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 202 9577 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ### #### */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xef,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 203 9574 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/* ### #### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0xef,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 204 9568 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #### */\n\t\t/*   #      */\n\t\t/*   # #### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x2f,\n\t\t0x20,\n\t\t0x2f,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 205 9552 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 206 9580 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ### #### */\n\t\t/*          */\n\t\t/* ### #### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xef,\n\t\t0x00,\n\t\t0xef,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 207 9575 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xff,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 208 9576 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 209 9572 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0xff,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 210 9573 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 211 9561 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   ###### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x3f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 212 9560 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 213 9554 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 214 9555 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###### */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3f,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 215 9579 */\n\t{\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/* ######## */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0xff,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t\t0x28,\n\t},\n\t/* 216 9578 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ######## */\n\t\t/*    #     */\n\t\t/* ######## */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xff,\n\t\t0x10,\n\t\t0xff,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 217 9496 */\n\t{\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* ####     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0xf0,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 218 9484 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##### */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1f,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t},\n\t/* 219 9608 */\n\t{\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t},\n\t/* 220 9604 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t},\n\t/* 221 9612 */\n\t{\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t/* ####     */\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t\t0xf0,\n\t},\n\t/* 222 9616 */\n\t{\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t/*     #### */\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t\t0x0f,\n\t},\n\t/* 223 9600 */\n\t{\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 224 945 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #### #  */\n\t\t/*  #  # #  */\n\t\t/* ##   #   */\n\t\t/* ##   #   */\n\t\t/*  #   #   */\n\t\t/*  #  ##   */\n\t\t/*  #### #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7a,\n\t\t0x4a,\n\t\t0xc4,\n\t\t0xc4,\n\t\t0x44,\n\t\t0x4c,\n\t\t0x7a,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 225 223 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*  #   #   */\n\t\t/*  #   #   */\n\t\t/*  #  #    */\n\t\t/*  # #     */\n\t\t/*  # #     */\n\t\t/*  # ###   */\n\t\t/*  #   ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  # ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x44,\n\t\t0x44,\n\t\t0x48,\n\t\t0x50,\n\t\t0x50,\n\t\t0x5c,\n\t\t0x46,\n\t\t0x42,\n\t\t0x42,\n\t\t0x5c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 226 915 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 227 960 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*  ##  #   */\n\t\t/*  ##  #   */\n\t\t/*  ##  #   */\n\t\t/*  ##  #   */\n\t\t/*  ##  #   */\n\t\t/*  ##  ### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x64,\n\t\t0x64,\n\t\t0x64,\n\t\t0x64,\n\t\t0x64,\n\t\t0x67,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 228 931 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*  ##      */\n\t\t/*   ##     */\n\t\t/*    #     */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*   ##     */\n\t\t/*  ##      */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x60,\n\t\t0x30,\n\t\t0x10,\n\t\t0x08,\n\t\t0x10,\n\t\t0x30,\n\t\t0x60,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 229 963 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ##  #   */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x64,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 230 181 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #   ##  */\n\t\t/*  ####### */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x46,\n\t\t0x7f,\n\t\t0x40,\n\t\t0x40,\n\t\t0x40,\n\t\t0x00,\n\t},\n\t/* 231 964 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  #####   */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*     ##   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7c,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x0c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 232 934 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###    */\n\t\t/*    #     */\n\t\t/*  #####   */\n\t\t/* #  #  #  */\n\t\t/* #  #  #  */\n\t\t/* #  #  #  */\n\t\t/*  #####   */\n\t\t/*    #     */\n\t\t/*   ###    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x38,\n\t\t0x10,\n\t\t0x7c,\n\t\t0x92,\n\t\t0x92,\n\t\t0x92,\n\t\t0x7c,\n\t\t0x10,\n\t\t0x38,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 233 920 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  # ### # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*   #   #  */\n\t\t/*    ###   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x5d,\n\t\t0x41,\n\t\t0x41,\n\t\t0x22,\n\t\t0x1c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 234 937 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #   #  */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  #     # */\n\t\t/*  ##   ## */\n\t\t/*   #   #  */\n\t\t/*  ### ### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x22,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x41,\n\t\t0x63,\n\t\t0x22,\n\t\t0x77,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 235 948 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  ##      */\n\t\t/*  ##      */\n\t\t/*   ####   */\n\t\t/*  ##  ##  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  ##  ##  */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x60,\n\t\t0x60,\n\t\t0x3c,\n\t\t0x66,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x66,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 236 8734 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ## ##   */\n\t\t/* # ##  #  */\n\t\t/* #  #  #  */\n\t\t/* # ##  #  */\n\t\t/*  ## ##   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x6c,\n\t\t0xb2,\n\t\t0x92,\n\t\t0xb2,\n\t\t0x6c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 237 966 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   #####  */\n\t\t/*  ## # ## */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  #  #  # */\n\t\t/*  ## # ## */\n\t\t/*   #####  */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*     #    */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3e,\n\t\t0x6b,\n\t\t0x49,\n\t\t0x49,\n\t\t0x49,\n\t\t0x6b,\n\t\t0x3e,\n\t\t0x08,\n\t\t0x08,\n\t\t0x08,\n\t\t0x00,\n\t},\n\t/* 238 949 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ####   */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   ###    */\n\t\t/*  #       */\n\t\t/*  #       */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x3c,\n\t\t0x40,\n\t\t0x40,\n\t\t0x38,\n\t\t0x40,\n\t\t0x40,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 239 8745 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*   ####   */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*  #    #  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x3c,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x42,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 240 8801 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 241 177 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/* #######  */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/* #######  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x10,\n\t\t0x10,\n\t\t0xfe,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0xfe,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 242 8805 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ##      */\n\t\t/*   ####   */\n\t\t/*       ## */\n\t\t/*    ###   */\n\t\t/*  ##      */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x60,\n\t\t0x3c,\n\t\t0x03,\n\t\t0x1c,\n\t\t0x60,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 243 8804 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*       ## */\n\t\t/*    ####  */\n\t\t/*  ##      */\n\t\t/*    ###   */\n\t\t/*       ## */\n\t\t/*          */\n\t\t/*  ####### */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x03,\n\t\t0x1e,\n\t\t0x60,\n\t\t0x1c,\n\t\t0x03,\n\t\t0x00,\n\t\t0x7f,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 244 8992 */\n\t{\n\t\t/*          */\n\t\t/*     ###  */\n\t\t/*     # #  */\n\t\t/*     #    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t0x00,\n\t\t0x0e,\n\t\t0x0a,\n\t\t0x08,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t},\n\t/* 245 8993 */\n\t{\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*  # #     */\n\t\t/*  ##      */\n\t\t/*          */\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x18,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x10,\n\t\t0x50,\n\t\t0x60,\n\t\t0x00,\n\t},\n\t/* 246 247 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0xff,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 247 8776 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*   ###  # */\n\t\t/*  #   ### */\n\t\t/*   ###  # */\n\t\t/*  #   ##  */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x39,\n\t\t0x47,\n\t\t0x39,\n\t\t0x46,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 248 176 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x24,\n\t\t0x24,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 249 8729 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*   ####   */\n\t\t/*   ####   */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x3c,\n\t\t0x3c,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 250 183 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ##    */\n\t\t/*    ##    */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x18,\n\t\t0x18,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 251 8730 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*       #  */\n\t\t/*       #  */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/*      #   */\n\t\t/* ##  #    */\n\t\t/*   # #    */\n\t\t/*   # #    */\n\t\t/*   ##     */\n\t\t/*    #     */\n\t\t/*    #     */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x02,\n\t\t0x02,\n\t\t0x04,\n\t\t0x04,\n\t\t0x04,\n\t\t0xc8,\n\t\t0x28,\n\t\t0x28,\n\t\t0x30,\n\t\t0x10,\n\t\t0x10,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 252 8319 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*  ######  */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*   #  #   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x7e,\n\t\t0x24,\n\t\t0x24,\n\t\t0x24,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 253 178 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*    ###   */\n\t\t/*   #  #   */\n\t\t/*     #    */\n\t\t/*    #     */\n\t\t/*   ####   */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x1c,\n\t\t0x24,\n\t\t0x08,\n\t\t0x10,\n\t\t0x3c,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 254 9632 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/* ######## */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0xff,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n\t/* 255 32 */\n\t{\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t/*          */\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t\t0x00,\n\t},\n};\n"
  },
  {
    "path": "apps/terminal-palette.h",
    "content": "/**\n * @file apps/terminal-palette.h\n * @brief Terminal color palette\n *\n * Provides the color table for both the basic 16 colors (here,\n * chosen from the Tango design documents), as well as the other\n * colors making up the basic 256 color palette derived from xterm.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n\n#define PALETTE_COLORS 256\n\nuint32_t term_colors[PALETTE_COLORS] = {\n\t/* black  */ 0x000000,\n\t/* red    */ 0xcc0000,\n\t/* green  */ 0x3e9a06,\n\t/* brown  */ 0xc4a000,\n\t/* navy   */ 0x3465a4,\n\t/* purple */ 0x75507b,\n\t/* d cyan */ 0x06989a,\n\t/* gray   */ 0xeeeeec,\n\t/* d gray */ 0x555753,\n\t/* red    */ 0xef2929,\n\t/* green  */ 0x8ae234,\n\t/* yellow */ 0xfce94f,\n\t/* blue   */ 0x729fcf,\n\t/* magenta*/ 0xad7fa8,\n\t/* cyan   */ 0x34e2e2,\n\t/* white  */ 0xFFFFFF,\n\t\t\t\t 0x000000,\n\t\t\t\t 0x00005f,\n\t\t\t\t 0x000087,\n\t\t\t\t 0x0000af,\n\t\t\t\t 0x0000d7,\n\t\t\t\t 0x0000ff,\n\t\t\t\t 0x005f00,\n\t\t\t\t 0x005f5f,\n\t\t\t\t 0x005f87,\n\t\t\t\t 0x005faf,\n\t\t\t\t 0x005fd7,\n\t\t\t\t 0x005fff,\n\t\t\t\t 0x008700,\n\t\t\t\t 0x00875f,\n\t\t\t\t 0x008787,\n\t\t\t\t 0x0087af,\n\t\t\t\t 0x0087d7,\n\t\t\t\t 0x0087ff,\n\t\t\t\t 0x00af00,\n\t\t\t\t 0x00af5f,\n\t\t\t\t 0x00af87,\n\t\t\t\t 0x00afaf,\n\t\t\t\t 0x00afd7,\n\t\t\t\t 0x00afff,\n\t\t\t\t 0x00d700,\n\t\t\t\t 0x00d75f,\n\t\t\t\t 0x00d787,\n\t\t\t\t 0x00d7af,\n\t\t\t\t 0x00d7d7,\n\t\t\t\t 0x00d7ff,\n\t\t\t\t 0x00ff00,\n\t\t\t\t 0x00ff5f,\n\t\t\t\t 0x00ff87,\n\t\t\t\t 0x00ffaf,\n\t\t\t\t 0x00ffd7,\n\t\t\t\t 0x00ffff,\n\t\t\t\t 0x5f0000,\n\t\t\t\t 0x5f005f,\n\t\t\t\t 0x5f0087,\n\t\t\t\t 0x5f00af,\n\t\t\t\t 0x5f00d7,\n\t\t\t\t 0x5f00ff,\n\t\t\t\t 0x5f5f00,\n\t\t\t\t 0x5f5f5f,\n\t\t\t\t 0x5f5f87,\n\t\t\t\t 0x5f5faf,\n\t\t\t\t 0x5f5fd7,\n\t\t\t\t 0x5f5fff,\n\t\t\t\t 0x5f8700,\n\t\t\t\t 0x5f875f,\n\t\t\t\t 0x5f8787,\n\t\t\t\t 0x5f87af,\n\t\t\t\t 0x5f87d7,\n\t\t\t\t 0x5f87ff,\n\t\t\t\t 0x5faf00,\n\t\t\t\t 0x5faf5f,\n\t\t\t\t 0x5faf87,\n\t\t\t\t 0x5fafaf,\n\t\t\t\t 0x5fafd7,\n\t\t\t\t 0x5fafff,\n\t\t\t\t 0x5fd700,\n\t\t\t\t 0x5fd75f,\n\t\t\t\t 0x5fd787,\n\t\t\t\t 0x5fd7af,\n\t\t\t\t 0x5fd7d7,\n\t\t\t\t 0x5fd7ff,\n\t\t\t\t 0x5fff00,\n\t\t\t\t 0x5fff5f,\n\t\t\t\t 0x5fff87,\n\t\t\t\t 0x5fffaf,\n\t\t\t\t 0x5fffd7,\n\t\t\t\t 0x5fffff,\n\t\t\t\t 0x870000,\n\t\t\t\t 0x87005f,\n\t\t\t\t 0x870087,\n\t\t\t\t 0x8700af,\n\t\t\t\t 0x8700d7,\n\t\t\t\t 0x8700ff,\n\t\t\t\t 0x875f00,\n\t\t\t\t 0x875f5f,\n\t\t\t\t 0x875f87,\n\t\t\t\t 0x875faf,\n\t\t\t\t 0x875fd7,\n\t\t\t\t 0x875fff,\n\t\t\t\t 0x878700,\n\t\t\t\t 0x87875f,\n\t\t\t\t 0x878787,\n\t\t\t\t 0x8787af,\n\t\t\t\t 0x8787d7,\n\t\t\t\t 0x8787ff,\n\t\t\t\t 0x87af00,\n\t\t\t\t 0x87af5f,\n\t\t\t\t 0x87af87,\n\t\t\t\t 0x87afaf,\n\t\t\t\t 0x87afd7,\n\t\t\t\t 0x87afff,\n\t\t\t\t 0x87d700,\n\t\t\t\t 0x87d75f,\n\t\t\t\t 0x87d787,\n\t\t\t\t 0x87d7af,\n\t\t\t\t 0x87d7d7,\n\t\t\t\t 0x87d7ff,\n\t\t\t\t 0x87ff00,\n\t\t\t\t 0x87ff5f,\n\t\t\t\t 0x87ff87,\n\t\t\t\t 0x87ffaf,\n\t\t\t\t 0x87ffd7,\n\t\t\t\t 0x87ffff,\n\t\t\t\t 0xaf0000,\n\t\t\t\t 0xaf005f,\n\t\t\t\t 0xaf0087,\n\t\t\t\t 0xaf00af,\n\t\t\t\t 0xaf00d7,\n\t\t\t\t 0xaf00ff,\n\t\t\t\t 0xaf5f00,\n\t\t\t\t 0xaf5f5f,\n\t\t\t\t 0xaf5f87,\n\t\t\t\t 0xaf5faf,\n\t\t\t\t 0xaf5fd7,\n\t\t\t\t 0xaf5fff,\n\t\t\t\t 0xaf8700,\n\t\t\t\t 0xaf875f,\n\t\t\t\t 0xaf8787,\n\t\t\t\t 0xaf87af,\n\t\t\t\t 0xaf87d7,\n\t\t\t\t 0xaf87ff,\n\t\t\t\t 0xafaf00,\n\t\t\t\t 0xafaf5f,\n\t\t\t\t 0xafaf87,\n\t\t\t\t 0xafafaf,\n\t\t\t\t 0xafafd7,\n\t\t\t\t 0xafafff,\n\t\t\t\t 0xafd700,\n\t\t\t\t 0xafd75f,\n\t\t\t\t 0xafd787,\n\t\t\t\t 0xafd7af,\n\t\t\t\t 0xafd7d7,\n\t\t\t\t 0xafd7ff,\n\t\t\t\t 0xafff00,\n\t\t\t\t 0xafff5f,\n\t\t\t\t 0xafff87,\n\t\t\t\t 0xafffaf,\n\t\t\t\t 0xafffd7,\n\t\t\t\t 0xafffff,\n\t\t\t\t 0xd70000,\n\t\t\t\t 0xd7005f,\n\t\t\t\t 0xd70087,\n\t\t\t\t 0xd700af,\n\t\t\t\t 0xd700d7,\n\t\t\t\t 0xd700ff,\n\t\t\t\t 0xd75f00,\n\t\t\t\t 0xd75f5f,\n\t\t\t\t 0xd75f87,\n\t\t\t\t 0xd75faf,\n\t\t\t\t 0xd75fd7,\n\t\t\t\t 0xd75fff,\n\t\t\t\t 0xd78700,\n\t\t\t\t 0xd7875f,\n\t\t\t\t 0xd78787,\n\t\t\t\t 0xd787af,\n\t\t\t\t 0xd787d7,\n\t\t\t\t 0xd787ff,\n\t\t\t\t 0xd7af00,\n\t\t\t\t 0xd7af5f,\n\t\t\t\t 0xd7af87,\n\t\t\t\t 0xd7afaf,\n\t\t\t\t 0xd7afd7,\n\t\t\t\t 0xd7afff,\n\t\t\t\t 0xd7d700,\n\t\t\t\t 0xd7d75f,\n\t\t\t\t 0xd7d787,\n\t\t\t\t 0xd7d7af,\n\t\t\t\t 0xd7d7d7,\n\t\t\t\t 0xd7d7ff,\n\t\t\t\t 0xd7ff00,\n\t\t\t\t 0xd7ff5f,\n\t\t\t\t 0xd7ff87,\n\t\t\t\t 0xd7ffaf,\n\t\t\t\t 0xd7ffd7,\n\t\t\t\t 0xd7ffff,\n\t\t\t\t 0xff0000,\n\t\t\t\t 0xff005f,\n\t\t\t\t 0xff0087,\n\t\t\t\t 0xff00af,\n\t\t\t\t 0xff00d7,\n\t\t\t\t 0xff00ff,\n\t\t\t\t 0xff5f00,\n\t\t\t\t 0xff5f5f,\n\t\t\t\t 0xff5f87,\n\t\t\t\t 0xff5faf,\n\t\t\t\t 0xff5fd7,\n\t\t\t\t 0xff5fff,\n\t\t\t\t 0xff8700,\n\t\t\t\t 0xff875f,\n\t\t\t\t 0xff8787,\n\t\t\t\t 0xff87af,\n\t\t\t\t 0xff87d7,\n\t\t\t\t 0xff87ff,\n\t\t\t\t 0xffaf00,\n\t\t\t\t 0xffaf5f,\n\t\t\t\t 0xffaf87,\n\t\t\t\t 0xffafaf,\n\t\t\t\t 0xffafd7,\n\t\t\t\t 0xffafff,\n\t\t\t\t 0xffd700,\n\t\t\t\t 0xffd75f,\n\t\t\t\t 0xffd787,\n\t\t\t\t 0xffd7af,\n\t\t\t\t 0xffd7d7,\n\t\t\t\t 0xffd7ff,\n\t\t\t\t 0xffff00,\n\t\t\t\t 0xffff5f,\n\t\t\t\t 0xffff87,\n\t\t\t\t 0xffffaf,\n\t\t\t\t 0xffffd7,\n\t\t\t\t 0xffffff,\n\t\t\t\t 0x080808,\n\t\t\t\t 0x121212,\n\t\t\t\t 0x1c1c1c,\n\t\t\t\t 0x262626,\n\t\t\t\t 0x303030,\n\t\t\t\t 0x3a3a3a,\n\t\t\t\t 0x444444,\n\t\t\t\t 0x4e4e4e,\n\t\t\t\t 0x585858,\n\t\t\t\t 0x626262,\n\t\t\t\t 0x6c6c6c,\n\t\t\t\t 0x767676,\n\t\t\t\t 0x808080,\n\t\t\t\t 0x8a8a8a,\n\t\t\t\t 0x949494,\n\t\t\t\t 0x9e9e9e,\n\t\t\t\t 0xa8a8a8,\n\t\t\t\t 0xb2b2b2,\n\t\t\t\t 0xbcbcbc,\n\t\t\t\t 0xc6c6c6,\n\t\t\t\t 0xd0d0d0,\n\t\t\t\t 0xdadada,\n\t\t\t\t 0xe4e4e4,\n\t\t\t\t 0xeeeeee,\n};\n\n"
  },
  {
    "path": "apps/terminal-vga.c",
    "content": "/**\n * @brief Virtual terminal emulator, for VGA text mode.\n *\n * Basicall the same as @ref terminal.c but outputs to the VGA\n * text mode buffer instead of managing a graphical window.\n *\n * Supports >16 colors by using a dumb closest-match approach.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <signal.h>\n#include <time.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <pthread.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/time.h>\n#include <sys/wait.h>\n#include <getopt.h>\n#include <errno.h>\n#include <pty.h>\n#include <sys/fswait.h>\n\n#include <wchar.h>\n\n#include <kernel/video.h>\n\n#include <toaru/decodeutf8.h>\n#include <toaru/kbd.h>\n#include <toaru/graphics.h>\n#include <toaru/termemu.h>\n#include <toaru/mouse.h>\n#include <toaru/list.h>\n#include <toaru/spinlock.h>\n\n#include \"vga-palette.h\"\n\n#define USE_BELL 0\n\n/* master and slave pty descriptors */\nstatic int fd_master, fd_slave;\nstatic FILE * terminal;\n\nstatic ssize_t  term_width     = 80;    /* Width of the terminal (in cells) */\nstatic ssize_t  term_height    = 25;    /* Height of the terminal (in cells) */\nstatic uint16_t csr_x          = 0;    /* Cursor X */\nstatic uint16_t csr_y          = 0;    /* Cursor Y */\nstatic uint16_t csr_h          = 0;\nstatic term_cell_t * term_buffer    = NULL; /* The terminal cell buffer */\nstatic term_cell_t * term_buffer_a = NULL;\nstatic term_cell_t * term_buffer_b = NULL;\nstatic int active_buffer  = 0;\nstatic int _orig_x = 0;\nstatic int _orig_y = 0;\nstatic uint32_t _orig_fg = 7;\nstatic uint32_t _orig_bg = 0;\nstatic uint32_t current_fg     = 7;    /* Current foreground color */\nstatic uint32_t current_bg     = 0;    /* Current background color */\nstatic uint8_t  cursor_on      = 1;    /* Whether or not the cursor should be rendered */\n\nstatic uint8_t  _login_shell   = 0;    /* Whether we're going to display a login shell or not */\n\nstatic uint64_t mouse_ticks = 0;\n\nstatic int selection = 0;\nstatic int selection_start_x = 0;\nstatic int selection_start_y = 0;\nstatic int selection_end_x = 0;\nstatic int selection_end_y = 0;\nstatic char * selection_text = NULL;\n\n#define char_width 1\n#define char_height 1\n\nterm_state_t * ansi_state = NULL;\n\nvoid reinit(void); /* Defined way further down */\nvoid term_redraw_cursor();\n\nvoid term_clear();\n\nvoid dump_buffer();\n\nstatic uint64_t get_ticks(void) {\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\n\treturn (uint64_t)now.tv_sec * 1000000LL + (uint64_t)now.tv_usec;\n}\n\nstatic int color_distance(uint32_t a, uint32_t b) {\n\tint a_r = (a & 0xFF0000) >> 16;\n\tint a_g = (a & 0xFF00) >> 8;\n\tint a_b = (a & 0xFF);\n\n\tint b_r = (b & 0xFF0000) >> 16;\n\tint b_g = (b & 0xFF00) >> 8;\n\tint b_b = (b & 0xFF);\n\n\tint distance = 0;\n\tdistance += abs(a_r - b_r) * 3;\n\tdistance += abs(a_g - b_g) * 6;\n\tdistance += abs(a_b - b_b) * 10;\n\n\treturn distance;\n}\n\nstatic uint32_t vga_base_colors[] = {\n\t0x000000,\n\t0xAA0000,\n\t0x00AA00,\n\t0xAA5500,\n\t0x0000AA,\n\t0xAA00AA,\n\t0x00AAAA,\n\t0xAAAAAA,\n\t0x555555,\n\t0xFF5555,\n\t0x55AA55,\n\t0xFFFF55,\n\t0x5555FF,\n\t0xFF55FF,\n\t0x55FFFF,\n\t0xFFFFFF,\n};\n\n#if 0\nstatic int is_gray(uint32_t a) {\n\tint a_r = (a & 0xFF0000) >> 16;\n\tint a_g = (a & 0xFF00) >> 8;\n\tint a_b = (a & 0xFF);\n\n\treturn (a_r == a_g && a_g == a_b);\n}\n#endif\n\nstatic int best_match(uint32_t a) {\n\tint best_distance = INT32_MAX;\n\tint best_index = 0;\n\tfor (int j = 0; j < 16; ++j) {\n\t\tint distance = color_distance(a, vga_base_colors[j]);\n\t\tif (distance < best_distance) {\n\t\t\tbest_index = j;\n\t\t\tbest_distance = distance;\n\t\t}\n\t}\n\treturn best_index;\n}\n\n\nvolatile int exit_application = 0;\n\n/* Returns the lower of two shorts */\nuint16_t min(uint16_t a, uint16_t b) {\n\treturn (a < b) ? a : b;\n}\n\n/* Returns the higher of two shorts */\nuint16_t max(uint16_t a, uint16_t b) {\n\treturn (a > b) ? a : b;\n}\n\nvoid set_title(char * c) {\n\t/* Do nothing */\n}\n\nstatic void cell_redraw(uint16_t x, uint16_t y);\nstatic void cell_redraw_inverted(uint16_t x, uint16_t y);\n\nint is_in_selection(int x, int y) {\n\tif (!selection) return 0;\n\tif (selection_end_y < selection_start_y) {\n\t\tif (y == selection_end_y) {\n\t\t\treturn (x >= selection_end_x);\n\t\t} else if (y == selection_start_y) {\n\t\t\treturn (x <= selection_start_x);\n\t\t} else {\n\t\t\treturn (y > selection_end_y && y < selection_start_y);\n\t\t}\n\t} else if (selection_end_y > selection_start_y) {\n\t\tif (y == selection_start_y) {\n\t\t\treturn (x >= selection_start_x);\n\t\t} else if (y == selection_end_y) {\n\t\t\treturn (x <= selection_end_x);\n\t\t} else {\n\t\t\treturn (y > selection_start_y && y < selection_end_y);\n\t\t}\n\t} else if (selection_end_y == selection_start_y) {\n\t\tif (y != selection_end_y) return 0;\n\t\tif (selection_start_x > selection_end_x) {\n\t\t\treturn (x >= selection_end_x && x <= selection_start_x);\n\t\t} else if (selection_start_x < selection_end_x) {\n\t\t\treturn (x >= selection_start_x && x <= selection_end_x);\n\t\t} else {\n\t\t\treturn x == selection_start_x;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid iterate_selection(void (*func)(uint16_t x, uint16_t y)) {\n\tif (!selection) return;\n\tif (selection_end_y < selection_start_y) {\n\t\tfor (int x = selection_end_x; x < term_width; ++x) {\n\t\t\tfunc(x, selection_end_y);\n\t\t}\n\t\tfor (int y = selection_end_y + 1; y < selection_start_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tfunc(x, y);\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x <= selection_start_x; ++x) {\n\t\t\tfunc(x, selection_start_y);\n\t\t}\n\t} else if (selection_start_y == selection_end_y) {\n\t\tif (selection_start_x > selection_end_x) {\n\t\t\tfor (int x = selection_end_x; x <= selection_start_x; ++x) {\n\t\t\t\tfunc(x, selection_start_y);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (int x = selection_start_x; x <= selection_end_x; ++x) {\n\t\t\t\tfunc(x, selection_start_y);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (int x = selection_start_x; x < term_width; ++x) {\n\t\t\tfunc(x, selection_start_y);\n\t\t}\n\t\tfor (int y = selection_start_y + 1; y < selection_end_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tfunc(x, y);\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x <= selection_end_x; ++x) {\n\t\t\tfunc(x, selection_end_y);\n\t\t}\n\t}\n\n}\n\nvoid redraw_selection(void) {\n\titerate_selection(cell_redraw_inverted);\n}\n\nstatic term_cell_t * cell_at(uint16_t x, uint16_t y) {\n\treturn (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n}\n\nstatic void mark_cell(uint16_t x, uint16_t y) {\n\tterm_cell_t * c = cell_at(x,y);\n\tif (c) {\n\t\tc->flags |= 0x200;\n\t}\n}\n\nstatic void mark_selection(void) {\n\titerate_selection(mark_cell);\n}\n\nstatic void red_cell(uint16_t x, uint16_t y) {\n\tterm_cell_t * c = cell_at(x,y);\n\tif (c) {\n\t\tif (c->flags & 0x200) {\n\t\t\tc->flags &= ~(0x200);\n\t\t} else {\n\t\t\tc->flags |= 0x400;\n\t\t}\n\t}\n}\n\nstatic void flip_selection(void) {\n\titerate_selection(red_cell);\n\tfor (int y = 0; y < term_height; ++y) {\n\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\tterm_cell_t * c = cell_at(x,y);\n\t\t\tif (c) {\n\t\t\t\tif (c->flags & 0x200) cell_redraw(x,y);\n\t\t\t\tif (c->flags & 0x400) cell_redraw_inverted(x,y);\n\t\t\t\tc->flags &= ~(0x600);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic int _selection_count = 0;\nstatic int _selection_i = 0;\n\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\nvoid count_selection(uint16_t x, uint16_t y) {\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tif (((uint32_t *)cell)[0] != 0x00000000) {\n\t\tchar tmp[7];\n\t\t_selection_count += to_eight(cell->c, tmp);\n\t}\n\tif (x == term_width - 1) {\n\t\t_selection_count++;\n\t}\n}\n\nvoid write_selection(uint16_t x, uint16_t y) {\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tif (((uint32_t *)cell)[0] != 0x00000000) {\n\t\tchar tmp[7];\n\t\tint count = to_eight(cell->c, tmp);\n\t\tfor (int i = 0; i < count; ++i) {\n\t\t\tselection_text[_selection_i] = tmp[i];\n\t\t\t_selection_i++;\n\t\t}\n\t}\n\tif (x == term_width - 1) {\n\t\tselection_text[_selection_i] = '\\n';;\n\t\t_selection_i++;\n\t}\n}\n\n\nchar * copy_selection(void) {\n\t_selection_count = 0;\n\titerate_selection(count_selection);\n\n\tif (selection_text) {\n\t\tfree(selection_text);\n\t}\n\n\tif (_selection_count == 0) {\n\t\treturn NULL;\n\t}\n\n\tselection_text = malloc(_selection_count + 1);\n\tselection_text[_selection_count] = '\\0';\n\t_selection_i = 0;\n\titerate_selection(write_selection);\n\n\tif (selection_text[_selection_count-1] == '\\n') {\n\t\t/* Don't end on a line feed */\n\t\tselection_text[_selection_count-1] = '\\0';\n\t}\n\n\treturn selection_text;\n}\n\nstatic volatile int input_buffer_lock = 0;\nstatic int input_buffer_semaphore[2];\nstatic list_t * input_buffer_queue = NULL;\nstruct input_data {\n\tsize_t len;\n\tchar data[];\n};\n\nvoid * handle_input_writing(void * unused) {\n\t(void)unused;\n\n\twhile (1) {\n\n\t\t/* Read one byte from semaphore; as long as semaphore has data,\n\t\t * there is another input blob to write to the TTY */\n\t\tchar tmp[1];\n\t\tint c = read(input_buffer_semaphore[0],tmp,1);\n\t\tif (c > 0) {\n\t\t\t/* Retrieve blob */\n\t\t\tspin_lock(&input_buffer_lock);\n\t\t\tnode_t * blob = list_dequeue(input_buffer_queue);\n\t\t\tspin_unlock(&input_buffer_lock);\n\t\t\t/* No blobs? This shouldn't happen, but just in case, just continue */\n\t\t\tif (!blob) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Write blob data to the tty */\n\t\t\tstruct input_data * value = blob->value;\n\t\t\twrite(fd_master, value->data, value->len);\n\t\t\tfree(blob->value);\n\t\t\tfree(blob);\n\t\t} else {\n\t\t\t/* The pipe has closed, terminal is exiting */\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic void write_input_buffer(char * data, size_t len) {\n\tstruct input_data * d = malloc(sizeof(struct input_data) + len);\n\td->len = len;\n\tmemcpy(&d->data, data, len);\n\tspin_lock(&input_buffer_lock);\n\tlist_insert(input_buffer_queue, d);\n\tspin_unlock(&input_buffer_lock);\n\twrite(input_buffer_semaphore[1], d, 1);\n}\n\nvoid handle_input(char c) {\n\twrite_input_buffer(&c, 1);\n}\n\nvoid handle_input_s(char * c) {\n\tsize_t len = strlen(c);\n\twrite_input_buffer(c, len);\n}\n\nunsigned short * textmemptr = NULL;\nunsigned short * basecopy = NULL;\nunsigned short * flipcopy = NULL;\nvoid placech(unsigned char c, int x, int y, int attr) {\n\tunsigned int where = y * term_width + x;\n\tunsigned int att = (c | (attr << 8));\n\tbasecopy[where] = att;\n}\n\nstatic void maybe_write_screen(void) {\n\t/* This says \"maybe_\" but we always draw whatever\n\t * needs drawing... */\n\tfor (int i = 0; i < term_width * term_height; ++i) {\n\t\tif (basecopy[i] != flipcopy[i]) {\n\t\t\ttextmemptr[i] = flipcopy[i] = basecopy[i];\n\t\t}\n\t}\n}\n\n/* ANSI-to-VGA */\nchar vga_to_ansi[] = {\n\t0, 4, 2, 6, 1, 5, 3, 7,\n\t8,12,10,14, 9,13,11,15\n};\n\n#include \"ununicode.h\"\n\nvoid\nterm_write_char(\n\t\tuint32_t val,\n\t\tuint16_t x,\n\t\tuint16_t y,\n\t\tuint32_t fg,\n\t\tuint32_t bg,\n\t\tuint8_t flags\n\t\t) {\n\tif (val == L'▏') val = 179;\n\telse if (val > 128) val = ununicode(val);\n\tif (fg > 256) {\n\t\tfg = best_match(fg);\n\t}\n\tif (bg > 256) {\n\t\tbg = best_match(bg);\n\t}\n\tif (fg > 16) {\n\t\tfg = vga_colors[fg];\n\t}\n\tif (bg > 16) {\n\t\tbg = vga_colors[bg];\n\t}\n\tif (fg == 16) fg = 0;\n\tif (bg == 16) bg = 0;\n\tplacech(val, x, y, (vga_to_ansi[fg] & 0xF) | (vga_to_ansi[bg] << 4));\n}\n\nstatic void cell_set(uint16_t x, uint16_t y, uint32_t c, uint32_t fg, uint32_t bg, uint8_t flags) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tcell->c     = c;\n\tcell->fg    = fg;\n\tcell->bg    = bg;\n\tcell->flags = flags;\n}\n\nstatic void cell_redraw(uint16_t x, uint16_t y) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tif (((uint32_t *)cell)[0] == 0x00000000) {\n\t\tterm_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS);\n\t} else {\n\t\tterm_write_char(cell->c, x * char_width, y * char_height, cell->fg, cell->bg, cell->flags);\n\t}\n}\n\nstatic void cell_redraw_inverted(uint16_t x, uint16_t y) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tif (((uint32_t *)cell)[0] == 0x00000000) {\n\t\tterm_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_BG, TERM_DEFAULT_FG, TERM_DEFAULT_FLAGS | ANSI_SPECBG);\n\t} else {\n\t\tterm_write_char(cell->c, x * char_width, y * char_height, cell->bg, cell->fg, cell->flags | ANSI_SPECBG);\n\t}\n}\n\n#if 0\nstatic void cell_redraw_box(uint16_t x, uint16_t y) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t));\n\tif (((uint32_t *)cell)[0] == 0x00000000) {\n\t\tterm_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS | ANSI_BORDER);\n\t} else {\n\t\tterm_write_char(cell->c, x * char_width, y * char_height, cell->fg, cell->bg, cell->flags | ANSI_BORDER);\n\t}\n}\n#endif\n\nvoid render_cursor() {\n\tif (!cursor_on) return;\n\tcell_redraw_inverted(csr_x, csr_y);\n}\n\nstatic uint8_t cursor_flipped = 0;\nvoid draw_cursor() {\n\tif (!cursor_on) return;\n\tmouse_ticks = get_ticks();\n\tcursor_flipped = 0;\n\trender_cursor();\n}\n\nvoid term_redraw_all() {\n\t/* Redraw to a temp buffer */\n\tfor (uint16_t y = 0; y < term_height; ++y) {\n\t\tfor (uint16_t x = 0; x < term_width; ++x) {\n\t\t\tcell_redraw(x,y);\n\t\t}\n\t}\n}\n\nvoid term_shift_region(int top, int height, int how_much) {\n\tif (how_much == 0) return;\n\n\tint destination, source;\n\tint count, new_top, new_bottom;\n\tif (how_much > height) {\n\t\tcount = 0;\n\t\tnew_top = top;\n\t\tnew_bottom = top + height;\n\t} else if (how_much > 0) {\n\t\tdestination = term_width * top;\n\t\tsource = term_width * (top + how_much);\n\t\tcount = height - how_much;\n\t\tnew_top = top + height - how_much;\n\t\tnew_bottom = top + height;\n\t} else if (how_much < 0) {\n\t\tdestination = term_width * (top - how_much);\n\t\tsource = term_width * top;\n\t\tcount = height + how_much;\n\t\tnew_top = top;\n\t\tnew_bottom = top - how_much;\n\t}\n\n\t/* Move from top+how_much to top */\n\tif (count) {\n\t\tmemmove(term_buffer + destination, term_buffer + source, count * term_width * sizeof(term_cell_t));\n\t}\n\n\t/* Clear new lines at bottom */\n\tfor (int i = new_top; i < new_bottom; ++i) {\n\t\tfor (uint16_t x = 0; x < term_width; ++x) {\n\t\t\tcell_set(x, i, ' ', current_fg, current_bg, ansi_state->flags);\n\t\t}\n\t}\n\n\tterm_redraw_all();\n}\n\nvoid term_scroll(int how_much) {\n\tterm_shift_region(0,term_height,how_much);\n}\n\nvoid insert_delete_lines(int how_many) {\n\tif (how_many == 0) return;\n\n\tif (how_many > 0) {\n\t\t/* Insert lines is equivalent to scrolling from the current line */\n\t\tterm_shift_region(csr_y,term_height-csr_y,-how_many);\n\t} else {\n\t\tterm_shift_region(csr_y,term_height-csr_y,-how_many);\n\t}\n}\n\nint is_wide(uint32_t codepoint) {\n\tif (codepoint < 256) return 0;\n\treturn wcwidth(codepoint) == 2;\n}\n\nstatic void undraw_cursor(void) {\n\tcell_redraw(csr_x, csr_y);\n}\n\nstatic void normalize_x(int setting_lcf) {\n\tif (csr_x >= term_width) {\n\t\tcsr_x = term_width - 1;\n\t\tif (setting_lcf) {\n\t\t\tcsr_h = 1;\n\t\t}\n\t}\n}\n\nstatic void normalize_y(void) {\n\tif (csr_y == term_height) {\n\t\tterm_scroll(1);\n\t\tcsr_y = term_height - 1;\n\t}\n}\n\n\nvoid term_write(char c) {\n\tstatic uint32_t codepoint = 0;\n\tstatic uint32_t unicode_state = 0;\n\n\tif (!decode(&unicode_state, &codepoint, (uint8_t)c)) {\n\t\tuint32_t o = codepoint;\n\t\tcodepoint = 0;\n\n\t\tswitch (c) {\n\t\t\tcase '\\a':\n\t\t\t\t/* boop */\n\t\t\t\treturn;\n\n\t\t\tcase '\\r':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_x = csr_h = 0;\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\t':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_x += (8 - csr_x % 8);\n\t\t\t\tnormalize_x(0);\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\v':\n\t\t\tcase '\\f':\n\t\t\tcase '\\n':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_h = 0;\n\t\t\t\t++csr_y;\n\t\t\t\tnormalize_y();\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\b':\n\t\t\t\tif (csr_x > 0) {\n\t\t\t\t\tundraw_cursor();\n\t\t\t\t\t--csr_x;\n\t\t\t\t\tdraw_cursor();\n\t\t\t\t}\n\t\t\t\tcsr_h = 0;\n\t\t\t\treturn;\n\n\t\t\tdefault: {\n\t\t\t\tint wide = is_wide(o);\n\t\t\t\tuint8_t flags = ansi_state->flags;\n\n\t\t\t\tundraw_cursor();\n\n\t\t\t\tif (csr_h || (wide && csr_x == term_width - 1)) {\n\t\t\t\t\tcsr_x = csr_h = 0;\n\t\t\t\t\t++csr_y;\n\t\t\t\t\tnormalize_y();\n\t\t\t\t}\n\n\t\t\t\tif (wide) {\n\t\t\t\t\tflags = flags | ANSI_WIDE;\n\t\t\t\t}\n\n\t\t\t\tcell_set(csr_x,csr_y, o, current_fg, current_bg, flags);\n\t\t\t\tcell_redraw(csr_x,csr_y);\n\t\t\t\tcsr_x++;\n\n\t\t\t\tif (wide && csr_x != term_width) {\n\t\t\t\t\tcell_set(csr_x, csr_y, 0xFFFF, current_fg, current_bg, ansi_state->flags);\n\t\t\t\t\tcell_redraw(csr_x,csr_y);\n\t\t\t\t\tcell_redraw(csr_x-1,csr_y);\n\t\t\t\t\tcsr_x++;\n\t\t\t\t}\n\n\t\t\t\tnormalize_x(1);\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t} else if (unicode_state == UTF8_REJECT) {\n\t\tunicode_state = 0;\n\t\tcodepoint = 0;\n\t}\n}\n\nvoid term_set_csr(int x, int y) {\n\tcell_redraw(csr_x,csr_y);\n\tif (x < 0) x = 0;\n\tif (x >= term_width) x = term_width - 1;\n\tif (y < 0) y = 0;\n\tif (y >= term_height) y = term_height - 1;\n\tcsr_x = x;\n\tcsr_y = y;\n\tcsr_h = 0;\n\tdraw_cursor();\n}\n\nint term_get_csr_x() {\n\treturn csr_x;\n}\n\nint term_get_csr_y() {\n\treturn csr_y;\n}\n\nvoid term_set_csr_show(int on) {\n\tcursor_on = on;\n\tif (on) {\n\t\tdraw_cursor();\n\t}\n}\n\nvoid term_set_colors(uint32_t fg, uint32_t bg) {\n\tcurrent_fg = fg;\n\tcurrent_bg = bg;\n}\n\nvoid term_redraw_cursor() {\n\tif (term_buffer) {\n\t\tdraw_cursor();\n\t}\n}\n\nvoid flip_cursor() {\n\tif (cursor_flipped) {\n\t\tcell_redraw(csr_x, csr_y);\n\t} else {\n\t\trender_cursor();\n\t}\n\tcursor_flipped = 1 - cursor_flipped;\n}\n\nvoid term_set_cell(int x, int y, uint32_t c) {\n\tcell_set(x, y, c, current_fg, current_bg, ansi_state->flags);\n\tcell_redraw(x, y);\n}\n\nvoid term_redraw_cell(int x, int y) {\n\tif (x < 0 || y < 0 || x >= term_width || y >= term_height) return;\n\tcell_redraw(x,y);\n}\n\nvoid term_clear(int i) {\n\tif (i == 2) {\n\t\t/* Oh dear */\n\t\tcsr_x = 0;\n\t\tcsr_y = 0;\n\t\tcsr_h = 0;\n\t\tmemset((void *)term_buffer, 0x00, term_width * term_height * sizeof(term_cell_t));\n\t\tterm_redraw_all();\n\t} else if (i == 0) {\n\t\tfor (int x = csr_x; x < term_width; ++x) {\n\t\t\tterm_set_cell(x, csr_y, ' ');\n\t\t}\n\t\tfor (int y = csr_y + 1; y < term_height; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tterm_set_cell(x, y, ' ');\n\t\t\t}\n\t\t}\n\t} else if (i == 1) {\n\t\tfor (int y = 0; y < csr_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tterm_set_cell(x, y, ' ');\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x < csr_x; ++x) {\n\t\t\tterm_set_cell(x, csr_y, ' ');\n\t\t}\n\t}\n}\n\npid_t child_pid = 0;\n\nvoid key_event(int ret, key_event_t * event) {\n\tif (ret) {\n\t\t/* Special keys */\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t\t(event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == 'c')) {\n\t\t\tif (selection) {\n\t\t\t\t/* Copy selection */\n\t\t\t\tcopy_selection();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t\t(event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == 'v')) {\n\t\t\t/* Paste selection */\n\t\t\tif (selection_text) {\n\t\t\t\tif (ansi_state->paste_mode) {\n\t\t\t\t\thandle_input_s(\"\\033[200~\");\n\t\t\t\t\thandle_input_s(selection_text);\n\t\t\t\t\thandle_input_s(\"\\033[201~\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(selection_text);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (event->modifiers & KEY_MOD_LEFT_ALT || event->modifiers & KEY_MOD_RIGHT_ALT) {\n\t\t\thandle_input('\\033');\n\t\t}\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t    event->key == '\\t') {\n\t\t\thandle_input_s(\"\\033[Z\");\n\t\t\treturn;\n\t\t}\n\n\t\t/* ENTER = reads as linefeed, should be carriage return */\n\t\tif (event->keycode == 10) {\n\t\t\thandle_input('\\r');\n\t\t\treturn;\n\t\t}\n\n\t\t/* BACKSPACE = reads as ^H, should be ^? */\n\t\tif (event->keycode == 8) {\n\t\t\thandle_input(0x7F);\n\t\t\treturn;\n\t\t}\n\n\t\thandle_input(event->key);\n\t} else {\n\t\tif (event->action == KEY_ACTION_UP) return;\n\t\tswitch (event->keycode) {\n\t\t\tcase KEY_F1:\n\t\t\t\thandle_input_s(\"\\033OP\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F2:\n\t\t\t\thandle_input_s(\"\\033OQ\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F3:\n\t\t\t\thandle_input_s(\"\\033OR\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F4:\n\t\t\t\thandle_input_s(\"\\033OS\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F5:\n\t\t\t\thandle_input_s(\"\\033[15~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F6:\n\t\t\t\thandle_input_s(\"\\033[17~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F7:\n\t\t\t\thandle_input_s(\"\\033[18~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F8:\n\t\t\t\thandle_input_s(\"\\033[19~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F9:\n\t\t\t\thandle_input_s(\"\\033[20~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F10:\n\t\t\t\thandle_input_s(\"\\033[21~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F11:\n\t\t\t\thandle_input_s(\"\\033[23~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F12:\n\t\t\t\thandle_input_s(\"\\033[24~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_UP:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2A\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[A\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2B\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[B\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2C\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[C\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2D\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[D\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_PAGE_UP:\n\t\t\t\thandle_input_s(\"\\033[5~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_PAGE_DOWN:\n\t\t\t\thandle_input_s(\"\\033[6~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_HOME:\n\t\t\t\thandle_input_s(\"\\033[H\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_END:\n\t\t\t\thandle_input_s(\"\\033[F\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_DEL:\n\t\t\t\thandle_input_s(\"\\033[3~\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nvoid usage(char * argv[]) {\n\tprintf(\n\t\t\t\"VGA Terminal Emulator\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-b] [-F] [-h]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\"\n\t\t\t\"\\n\",\n\t\t\targv[0]);\n}\n\nint unsupported_int(void) { return 0; }\nvoid unsupported(int x, int y, char * data) { }\n\n#define SWAP(T,a,b) do { T _a = a; a = b; b = _a; } while(0);\nstatic void term_switch_buffer(int buffer) {\n\tif (buffer != 0 && buffer != 1) return;\n\tif (buffer != active_buffer) {\n\t\tactive_buffer = buffer;\n\t\tterm_buffer = active_buffer == 0 ? term_buffer_a : term_buffer_b;\n\n\t\tSWAP(int, csr_x, _orig_x);\n\t\tSWAP(int, csr_y, _orig_y);\n\t\tSWAP(uint32_t, current_fg, _orig_fg);\n\t\tSWAP(uint32_t, current_bg, _orig_bg);\n\n\t\tterm_redraw_all();\n\t}\n}\n\nstatic void full_reset(void) {\n\t/* Reset everything */\n\tcsr_x = 0;\n\tcsr_y = 0;\n\tcsr_h = 0;\n\n\t/* Huh, why don't we haven't an _orig_h - surely hold should be saved? */\n\t_orig_x = 0;\n\t_orig_y = 0;\n\n\tcurrent_fg = TERM_DEFAULT_FG;\n\tcurrent_bg = TERM_DEFAULT_BG;\n\t_orig_fg = TERM_DEFAULT_FG;\n\t_orig_bg = TERM_DEFAULT_BG;\n\n\tactive_buffer = 0;\n\tterm_buffer = term_buffer_a;\n\n\t/* Clear both buffers to 0 */\n\tmemset((void *)term_buffer_a, 0x00, term_width * term_height * sizeof(term_cell_t));\n\tmemset((void *)term_buffer_b, 0x00, term_width * term_height * sizeof(term_cell_t));\n\n\t/* Enable cursor */\n\tcursor_on = 1;\n}\n\nterm_callbacks_t term_callbacks = {\n\tterm_write,\n\tterm_set_colors,\n\tterm_set_csr,\n\tterm_get_csr_x,\n\tterm_get_csr_y,\n\tterm_set_cell,\n\tterm_clear,\n\tterm_scroll,\n\tterm_redraw_cursor,\n\thandle_input_s,\n\tset_title,\n\tunsupported,\n\tunsupported_int,\n\tunsupported_int,\n\tterm_set_csr_show,\n\tterm_switch_buffer,\n\tinsert_delete_lines,\n\tfull_reset,\n};\n\nvoid reinit(void) {\n\tif (term_buffer) {\n\t\t/* Do nothing */\n\t} else {\n\t\tterm_buffer_a = malloc(sizeof(term_cell_t) * term_width * term_height);\n\t\tmemset(term_buffer_a, 0x0, sizeof(term_cell_t) * term_width * term_height);\n\n\t\tterm_buffer_b = malloc(sizeof(term_cell_t) * term_width * term_height);\n\t\tmemset(term_buffer_b, 0x0, sizeof(term_cell_t) * term_width * term_height);\n\n\t\tterm_buffer = term_buffer_a;\n\t\tbasecopy = malloc(sizeof(unsigned short) * term_width * term_height);\n\t\tmemset(basecopy, 0, sizeof(unsigned short) * term_width * term_height);\n\t\tflipcopy = malloc(sizeof(unsigned short) * term_width * term_height);\n\t\tmemset(flipcopy, 0, sizeof(unsigned short) * term_width * term_height);\n\t}\n\n\tansi_state = ansi_init(ansi_state, term_width, term_height, &term_callbacks);\n\tterm_redraw_all();\n}\n\n\nvoid maybe_flip_cursor(void) {\n\tuint64_t ticks = get_ticks();\n\tif (ticks > mouse_ticks + 600000LL) {\n\t\tmouse_ticks = ticks;\n\t\tflip_cursor();\n\t}\n}\n\n\nvoid check_for_exit(void) {\n\tif (exit_application) return;\n\n\tpid_t pid = waitpid(-1, NULL, WNOHANG);\n\n\tif (pid != child_pid) return;\n\n\t/* Clean up */\n\texit_application = 1;\n\t/* Exit */\n\tchar exit_message[] = \"[Process terminated]\\n\";\n\twrite(fd_slave, exit_message, sizeof(exit_message));\n\tclose(input_buffer_semaphore[1]);\n}\n\nstatic int mouse_x = 0;\nstatic int mouse_y = 0;\nstatic int last_mouse_buttons = 0;\nstatic int mouse_is_dragging = 0;\n\n#define MOUSE_X_R 820\n#define MOUSE_Y_R 2621\n\nstatic int old_x = 0;\nstatic int old_y = 0;\n\nstatic void mouse_event(int button, int x, int y) {\n\tif (ansi_state->mouse_on & TERMEMU_MOUSE_SGR) {\n\t\tchar buf[100];\n\t\tsprintf(buf,\"\\033[<%d;%d;%d%c\", button == 3 ? 0 : button, x+1, y+1, button == 3 ? 'm' : 'M');\n\t\thandle_input_s(buf);\n\t} else {\n\t\tchar buf[7];\n\t\tsprintf(buf, \"\\033[M%c%c%c\", button + 32, x + 33, y + 33);\n\t\thandle_input_s(buf);\n\t}\n}\n\nstatic void redraw_mouse(void) {\n\t/* Redraw previous cursor position */\n\tif (is_in_selection(old_x, old_y)) {\n\t\tcell_redraw_inverted(old_x, old_y);\n\t} else {\n\t\tcell_redraw(old_x, old_y);\n\t}\n\tterm_cell_t * cell = term_buffer + (mouse_y * term_width + mouse_x);\n\tint current_background = cell->bg;\n\tif (is_in_selection(mouse_x, mouse_y)) {\n\t\tcurrent_background = (((uint32_t *)cell)[0] == 0) ? TERM_DEFAULT_FG : cell->fg;\n\t}\n\t/* Get new cursor position character */\n\tint cursor_color = (current_background == 12) ? 15 : 12;\n\tterm_write_char(L'▲', mouse_x, mouse_y, cursor_color, current_background, 0);\n\told_x = mouse_x;\n\told_y = mouse_y;\n}\n\nstatic unsigned int button_state = 0;\n\nvoid handle_mouse_event(mouse_device_packet_t * packet) {\n\tif (mouse_x < 0) mouse_x = 0;\n\tif (mouse_y < 0) mouse_y = 0;\n\tif (mouse_x >= term_width) mouse_x = term_width - 1;\n\tif (mouse_y >= term_height) mouse_y = term_height - 1;\n\n\tstatic uint64_t last_click = 0;\n\tif (ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE) {\n\t\t/* TODO: Handle shift */\n\t\tif (packet->buttons & MOUSE_SCROLL_UP) {\n\t\t\tmouse_event(32+32, mouse_x, mouse_y);\n\t\t} else if (packet->buttons & MOUSE_SCROLL_DOWN) {\n\t\t\tmouse_event(32+32+1, mouse_x, mouse_y);\n\t\t}\n\n\t\tif (packet->buttons != button_state) {\n\t\t\tif (packet->buttons & LEFT_CLICK && !(button_state & LEFT_CLICK)) mouse_event(0, mouse_x, mouse_y);\n\t\t\tif (packet->buttons & MIDDLE_CLICK && !(button_state & MIDDLE_CLICK)) mouse_event(1, mouse_x, mouse_y);\n\t\t\tif (packet->buttons & RIGHT_CLICK && !(button_state & MIDDLE_CLICK)) mouse_event(2, mouse_x, mouse_y);\n\t\t\tif (!(packet->buttons & LEFT_CLICK) && (button_state & LEFT_CLICK)) mouse_event(3, mouse_x, mouse_y);\n\t\t\tif (!(packet->buttons & MIDDLE_CLICK) && (button_state & MIDDLE_CLICK)) mouse_event(3, mouse_x, mouse_y);\n\t\t\tif (!(packet->buttons & RIGHT_CLICK) && (button_state & MIDDLE_CLICK)) mouse_event(3, mouse_x, mouse_y);\n\t\t\tbutton_state = packet->buttons;\n\t\t} else if (ansi_state->mouse_on & TERMEMU_MOUSE_DRAG) {\n\t\t\tif (old_x != mouse_x || old_y != mouse_y) {\n\t\t\t\tif (button_state & LEFT_CLICK) mouse_event(32, mouse_x, mouse_y);\n\t\t\t\tif (button_state & MIDDLE_CLICK) mouse_event(33, mouse_x, mouse_y);\n\t\t\t\tif (button_state & RIGHT_CLICK) mouse_event(34, mouse_x, mouse_y);\n\t\t\t}\n\t\t}\n\n\t\tredraw_mouse();\n\t\treturn;\n\t}\n\tif (mouse_is_dragging) {\n\t\tif (packet->buttons & LEFT_CLICK) {\n\t\t\tmark_selection();\n\t\t\tselection_end_x = mouse_x;\n\t\t\tselection_end_y = mouse_y;\n\t\t\tselection = 1;\n\t\t\tflip_selection();\n\t\t} else {\n\t\t\tmouse_is_dragging = 0;\n\t\t}\n\t} else {\n\t\tif (packet->buttons & LEFT_CLICK) {\n\t\t\tterm_redraw_all();\n\t\t\tuint64_t now = get_ticks();\n\t\t\tif (now - last_click < 500000UL && (mouse_x == selection_start_x && mouse_y == selection_start_y)) {\n\t\t\t\t/* Double click */\n\t\t\t\twhile (selection_start_x > 0) {\n\t\t\t\t\tterm_cell_t * c = cell_at(selection_start_x-1, selection_start_y);\n\t\t\t\t\tif (!c || c->c == ' ' || !c->c) break;\n\t\t\t\t\tselection_start_x--;\n\t\t\t\t}\n\t\t\t\twhile (selection_end_x < term_width - 1) {\n\t\t\t\t\tterm_cell_t * c = cell_at(selection_end_x+1, selection_end_y);\n\t\t\t\t\tif (!c || c->c == ' ' || !c->c) break;\n\t\t\t\t\tselection_end_x++;\n\t\t\t\t}\n\t\t\t\tselection = 1;\n\t\t\t} else {\n\t\t\t\tlast_click = get_ticks();\n\t\t\t\tselection_start_x = mouse_x;\n\t\t\t\tselection_start_y = mouse_y;\n\t\t\t\tselection_end_x = mouse_x;\n\t\t\t\tselection_end_y = mouse_y;\n\t\t\t\tselection = 0;\n\t\t\t}\n\t\t\tredraw_selection();\n\t\t\tmouse_is_dragging = 1;\n\t\t} else {\n\t\t\tredraw_mouse();\n\t\t}\n\t}\n\n\n}\n\nstatic int rel_mouse_x = 0;\nstatic int rel_mouse_y = 0;\n\nvoid handle_mouse(mouse_device_packet_t * packet) {\n\trel_mouse_x += packet->x_difference;\n\trel_mouse_y -= packet->y_difference;\n\n\tmouse_x = rel_mouse_x / 20;\n\tmouse_y = rel_mouse_y / 40;\n\n\thandle_mouse_event(packet);\n}\n\nvoid handle_mouse_abs(mouse_device_packet_t * packet) {\n\tmouse_x = packet->x_difference / MOUSE_X_R;\n\tmouse_y = packet->y_difference / MOUSE_Y_R;\n\n\trel_mouse_x = mouse_x * 20;\n\trel_mouse_y = mouse_y * 40;\n\n\thandle_mouse_event(packet);\n}\n\nstatic int input_stopped = 0;\n\nvoid sig_suspend_input(int sig) {\n\t(void)sig;\n\tchar exit_message[] = \"[Input stopped]\\n\";\n\twrite(fd_slave, exit_message, sizeof(exit_message));\n\n\tinput_stopped = 1;\n\n\tsignal(SIGUSR2, sig_suspend_input);\n}\n\nint main(int argc, char ** argv) {\n\n\t_login_shell = 0;\n\n\tstatic struct option long_opts[] = {\n\t\t{\"login\",      no_argument,       0, 'l'},\n\t\t{\"help\",       no_argument,       0, 'h'},\n\t\t{0,0,0,0}\n\t};\n\n\t/* Read some arguments */\n\tint index, c;\n\twhile ((c = getopt_long(argc, argv, \"hl\", long_opts, &index)) != -1) {\n\t\tswitch (c) {\n\t\t\tcase 'l':\n\t\t\t\t_login_shell = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tusage(argv);\n\t\t\t\treturn 0;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tint vga_text_fd = open(\"/dev/vga0\", 0, 0);\n\tif (vga_text_fd < 0) return 1;\n\tioctl(vga_text_fd, IO_VID_WIDTH,  &term_width);\n\tioctl(vga_text_fd, IO_VID_HEIGHT, &term_height);\n\tioctl(vga_text_fd, IO_VID_ADDR,   &textmemptr);\n\n\tputenv(\"TERM=toaru-vga\");\n\n\topenpty(&fd_master, &fd_slave, NULL, NULL, NULL);\n\n\tterminal = fdopen(fd_slave, \"w\");\n\n\tstruct winsize w;\n\tw.ws_row = term_height;\n\tw.ws_col = term_width;\n\tw.ws_xpixel = 0;\n\tw.ws_ypixel = 0;\n\tioctl(fd_master, TIOCSWINSZ, &w);\n\n\treinit();\n\n\tpthread_t input_buffer_thread;\n\tpipe(input_buffer_semaphore);\n\tinput_buffer_queue = list_create();\n\tpthread_create(&input_buffer_thread, NULL, handle_input_writing, NULL);\n\n\tfflush(stdin);\n\n\tsystem(\"cursor-off\");\n\n\tsignal(SIGUSR2, sig_suspend_input);\n\n\tint pid = getpid();\n\tuint32_t f = fork();\n\n\tif (getpid() != pid) {\n\t\tsetsid();\n\t\tdup2(fd_slave, 0);\n\t\tdup2(fd_slave, 1);\n\t\tdup2(fd_slave, 2);\n\t\tioctl(STDIN_FILENO, TIOCSCTTY, &(int){1});\n\t\ttcsetpgrp(STDIN_FILENO, getpid());\n\n\t\tif (argv[optind] != NULL) {\n\t\t\tchar * tokens[] = {argv[optind], NULL};\n\t\t\texecvp(tokens[0], tokens);\n\t\t\tfprintf(stderr, \"Failed to launch requested startup application.\\n\");\n\t\t} else {\n\t\t\tif (_login_shell) {\n\t\t\t\tchar * tokens[] = {\"/bin/login-loop\",NULL};\n\t\t\t\texecvp(tokens[0], tokens);\n\t\t\t\texit(1);\n\t\t\t} else {\n\t\t\t\tchar * shell = getenv(\"SHELL\");\n\t\t\t\tif (!shell) shell = \"/bin/sh\"; /* fallback */\n\t\t\t\tchar * tokens[] = {shell,NULL};\n\t\t\t\texecvp(tokens[0], tokens);\n\t\t\t\texit(1);\n\t\t\t}\n\t\t}\n\n\t\texit_application = 1;\n\n\t\treturn 1;\n\t} else {\n\n\t\tchild_pid = f;\n\n\t\tint kfd = open(\"/dev/kbd\", O_RDONLY);\n\t\tkey_event_t event;\n\t\tint vmmouse = 0;\n\t\tmouse_device_packet_t packet;\n\n\t\tint mfd = open(\"/dev/mouse\", O_RDONLY);\n\t\tint amfd = open(\"/dev/absmouse\", O_RDONLY);\n\t\tif (amfd == -1) {\n\t\t\tamfd = open(\"/dev/vmmouse\", O_RDONLY);\n\t\t\tvmmouse = 1;\n\t\t}\n\n\t\tkey_event_state_t kbd_state = {0};\n\n\t\t/* Prune any keyboard input we got before the terminal started. */\n\t\tstruct stat s;\n\t\tfstat(kfd, &s);\n\t\tfor (unsigned int i = 0; i < s.st_size; i++) {\n\t\t\tchar tmp[1];\n\t\t\tread(kfd, tmp, 1);\n\t\t}\n\n\t\tint fds[] = {fd_master, kfd, mfd, amfd};\n\n\t\t#define BUF_SIZE 4096\n\t\tunsigned char buf[4096];\n\t\twhile (!exit_application) {\n\n\t\t\tint res[] = {0,0,0,0};\n\t\t\tfswait3(amfd == -1 ? 3 : 4,fds,200,res);\n\n\t\t\tcheck_for_exit();\n\n\t\t\tif (input_stopped) continue;\n\n\t\t\tmaybe_flip_cursor();\n\t\t\tif (res[0]) {\n\t\t\t\tint r = read(fd_master, buf, BUF_SIZE);\n\t\t\t\tfor (int i = 0; i < r; ++i) {\n\t\t\t\t\tansi_put(ansi_state, buf[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res[1]) {\n\t\t\t\tint r = read(kfd, buf, 1);\n\t\t\t\tfor (int i = 0; i < r; ++i) {\n\t\t\t\t\tif (kbd_scancode(&kbd_state, buf[i], &event)) {\n\t\t\t\t\t\tkey_event(event.action == KEY_ACTION_DOWN && event.key, &event);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (res[2]) {\n\t\t\t\t/* mouse event */\n\t\t\t\tint r = read(mfd, (char *)&packet, sizeof(mouse_device_packet_t));\n\t\t\t\tif (r > 0) {\n\t\t\t\t\tlast_mouse_buttons = packet.buttons;\n\t\t\t\t\thandle_mouse(&packet);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (amfd != -1 && res[3]) {\n\t\t\t\tint r = read(amfd, (char *)&packet, sizeof(mouse_device_packet_t));\n\t\t\t\tif (r > 0) {\n\t\t\t\t\tif (!vmmouse) {\n\t\t\t\t\t\tpacket.buttons = last_mouse_buttons & 0xF;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlast_mouse_buttons = packet.buttons;\n\t\t\t\t\t}\n\t\t\t\t\thandle_mouse_abs(&packet);\n\t\t\t\t}\n\t\t\t}\n\t\t\tmaybe_write_screen();\n\t\t}\n\n\t}\n\n\tclose(input_buffer_semaphore[1]);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/terminal.c",
    "content": "/**\n * @brief Virtual terminal emulator.\n *\n * Provides a graphical character cell terminal with support for\n * antialiased text, basic Unicode, bitmap fallbacks, nearly\n * complete ANSI escape sequence support, 256- and 24-bit color,\n * scrollback, selection, alternate screens, and various scroll\n * methods.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2022 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <signal.h>\n#include <time.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <errno.h>\n#include <pty.h>\n#include <wchar.h>\n#include <dlfcn.h>\n#include <pthread.h>\n\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/wait.h>\n#include <sys/time.h>\n#include <sys/fswait.h>\n\n#define TRACE_APP_NAME \"terminal\"\n#include <toaru/trace.h>\n#include <toaru/decodeutf8.h>\n#include <toaru/yutani.h>\n#include <toaru/decorations.h>\n#include <toaru/graphics.h>\n#include <toaru/kbd.h>\n#include <toaru/termemu.h>\n#include <toaru/spinlock.h>\n#include <toaru/list.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n\n/* 16- and 256-color palette */\n#include \"terminal-palette.h\"\n/* Bitmap font */\n#include \"terminal-font.h\"\n\n/* Show help text */\nstatic void usage(char * argv[]) {\n\tprintf(\n\t\t\t\"Terminal Emulator\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-Fbxn] [-s SCALE] [-g WIDTHxHEIGHT] [COMMAND...]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -F --fullscreen \\033[3mRun in fullscreen (background) mode.\\033[0m\\n\"\n\t\t\t\" -b --bitmap     \\033[3mUse the integrated bitmap font.\\033[0m\\n\"\n\t\t\t\" -s --scale      \\033[3mScale the font in antialiased mode by a given amount.\\033[0m\\n\"\n\t\t\t\" -h --help       \\033[3mShow this help message.\\033[0m\\n\"\n\t\t\t\" -x --grid       \\033[3mMake resizes round to nearest match for character cell size.\\033[0m\\n\"\n\t\t\t\" -n --no-frame   \\033[3mDisable decorations.\\033[0m\\n\"\n\t\t\t\" -g --geometry   \\033[3mSet requested terminal size WIDTHxHEIGHT\\033[0m\\n\"\n\t\t\t\" -B --blurred    \\033[3mBlur background behind terminal.\\033[0m\\n\"\n\t\t\t\" -S --scrollback \\033[3mSet the scrollback buffer size, 0 for unlimited.\\033[0m\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" This terminal emulator provides basic support for VT220 escapes and\\n\"\n\t\t\t\" XTerm extensions, including 256 color support and font effects.\\n\",\n\t\t\targv[0]);\n}\n\n/* master and slave pty descriptors */\nstatic int fd_master, fd_slave;\nstatic FILE * terminal;\nstatic pid_t child_pid = 0;\n\nstatic int      scale_fonts    = 0;    /* Whether fonts should be scaled */\nstatic float    font_scaling   = 1.0;  /* How much they should be scaled by */\nstatic uint16_t term_width     = 0;    /* Width of the terminal (in cells) */\nstatic uint16_t term_height    = 0;    /* Height of the terminal (in cells) */\nstatic uint16_t font_size      = 16;   /* Font size according to tt library */\nstatic uint16_t char_width     = 8;    /* Width of a cell in pixels */\nstatic uint16_t char_height    = 17;   /* Height of a cell in pixels */\nstatic uint16_t char_offset    = 0;    /* Offset of the font within the cell */\nstatic int      csr_x          = 0;    /* Cursor X */\nstatic int      csr_y          = 0;    /* Cursor Y */\nstatic int      csr_h          = 0;    /* Cursor last column hold flag */\nstatic uint32_t current_fg     = 7;    /* Current foreground color */\nstatic uint32_t current_bg     = 0;    /* Current background color */\n\nstatic term_cell_t * term_buffer = NULL; /* The active terminal cell buffer */\nstatic term_cell_t * term_buffer_a = NULL; /* The main buffer */\nstatic term_cell_t * term_buffer_b = NULL; /* The secondary buffer */\n\nstatic term_cell_t * term_mirror = NULL;  /* What we want to draw */\nstatic term_cell_t * term_display = NULL; /* What we think we've drawn already */\n\nstatic term_state_t * ansi_state = NULL; /* ANSI parser library state */\nstatic int active_buffer  = 0;\nstatic int _orig_x = 0;\nstatic int _orig_y = 0;\nstatic uint32_t _orig_fg = 7;\nstatic uint32_t _orig_bg = 0;\n\nstatic bool cursor_on      = 1;    /* Whether or not the cursor should be rendered */\nstatic bool _fullscreen    = 0;    /* Whether or not we are running in fullscreen mode (GUI only) */\nstatic bool _no_frame      = 0;    /* Whether to disable decorations or not */\nstatic bool _use_aa        = 1;    /* Whether or not to use best-available anti-aliased renderer */\nstatic bool _free_size     = 1;    /* Disable rounding when resized */\n\nstatic struct TT_Font * _tt_font_normal = NULL;\nstatic struct TT_Font * _tt_font_bold = NULL;\nstatic struct TT_Font * _tt_font_oblique = NULL;\nstatic struct TT_Font * _tt_font_bold_oblique = NULL;\n\nstatic struct TT_Font * _tt_font_fallback = NULL;\nstatic struct TT_Font * _tt_font_japanese = NULL;\n\nstatic list_t * images_list = NULL;\n\nstatic int menu_bar_height = 24;\n\n/* Text selection information */\nstatic int selection = 0;\nstatic int selection_start_x = 0;\nstatic int selection_start_y = 0;\nstatic int selection_end_x = 0;\nstatic int selection_end_y = 0;\nstatic char * selection_text = NULL;\nstatic int _selection_count = 0;\nstatic int _selection_i = 0;\n\n/* Mouse state */\nstatic int last_mouse_x   = -1;\nstatic int last_mouse_y   = -1;\nstatic int button_state   = 0;\nstatic unsigned long long mouse_ticks = 0;\n\nstatic yutani_window_t * window       = NULL; /* GUI window */\nstatic yutani_t * yctx = NULL;\n\n/* Window flip bounds */\nstatic int32_t l_x = INT32_MAX;\nstatic int32_t l_y = INT32_MAX;\nstatic int32_t r_x = -1;\nstatic int32_t r_y = -1;\n\nstatic uint32_t window_width  = 640;\nstatic uint32_t window_height = 480;\nstatic bool     window_position_set = 0;\nstatic int32_t  window_left   = 0;\nstatic int32_t  window_top    = 0;\n#define TERMINAL_TITLE_SIZE 512\nstatic char   terminal_title[TERMINAL_TITLE_SIZE];\nstatic size_t terminal_title_length = 0;\nstatic gfx_context_t * ctx;\nstatic struct MenuList * menu_right_click = NULL;\n\nstatic void render_decors(void);\nstatic void term_clear(int i);\nstatic void reinit(void);\nstatic void term_redraw_cursor();\n\nstatic int decor_left_width = 0;\nstatic int decor_top_height = 0;\nstatic int decor_right_width = 0;\nstatic int decor_bottom_height = 0;\nstatic int decor_width = 0;\nstatic int decor_height = 0;\n\nstruct scrollback_row {\n\tunsigned short width;\n\tterm_cell_t cells[];\n};\n\nstatic size_t max_scrollback = 10000;\nstatic list_t * scrollback_list = NULL;\nstatic int scrollback_offset = 0;\n\n/* Menu bar entries */\nstruct menu_bar terminal_menu_bar = {0};\nstruct menu_bar_entries terminal_menu_entries[] = {\n\t{\"File\", \"file\"},\n\t{\"Edit\", \"edit\"},\n\t{\"View\", \"view\"},\n\t{\"Help\", \"help\"},\n\t{NULL, NULL},\n};\n\n/* We need to track these so we can update their states*/\nstatic struct MenuEntry * _menu_toggle_borders_context = NULL;\nstatic struct MenuEntry * _menu_toggle_borders_bar = NULL;\nstatic struct MenuEntry * _menu_exit = NULL;\nstatic struct MenuEntry * _menu_copy = NULL;\nstatic struct MenuEntry * _menu_paste = NULL;\nstatic struct MenuEntry * _menu_scale_075 = NULL;\nstatic struct MenuEntry * _menu_scale_100 = NULL;\nstatic struct MenuEntry * _menu_scale_150 = NULL;\nstatic struct MenuEntry * _menu_scale_200 = NULL;\nstatic struct MenuEntry * _menu_set_zoom = NULL;\n\n/* Trigger to exit the terminal when the child process dies or\n * we otherwise receive an exit signal */\nstatic volatile int exit_application = 0;\n\nstatic void cell_redraw(uint16_t x, uint16_t y);\nstatic void cell_redraw_inverted(uint16_t x, uint16_t y);\nstatic void cell_redraw_offset(uint16_t x, uint16_t y);\nstatic void cell_redraw_offset_inverted(uint16_t x, uint16_t y);\nstatic void update_bounds(void);\nstatic void update_scale_menu(void);\n\nstatic uint64_t get_ticks(void) {\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\n\treturn (uint64_t)now.tv_sec * 1000000LL + (uint64_t)now.tv_usec;\n}\n\nstatic void display_flip(void) {\n\tif (l_x != INT32_MAX && l_y != INT32_MAX) {\n\t\tflip(ctx);\n\t\tyutani_flip_region(yctx, window, l_x, l_y, r_x - l_x, r_y - l_y);\n\t\tl_x = INT32_MAX;\n\t\tl_y = INT32_MAX;\n\t\tr_x = -1;\n\t\tr_y = -1;\n\t}\n}\n\n/* Returns the lower of two shorts */\nstatic int32_t min(int32_t a, int32_t b) {\n\treturn (a < b) ? a : b;\n}\n\n/* Returns the higher of two shorts */\nstatic int32_t max(int32_t a, int32_t b) {\n\treturn (a > b) ? a : b;\n}\n\n/*\n * Convert codepoint to UTF-8\n *\n * Returns length of byte sequence written.\n */\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\n/* Set the terminal title string */\nstatic void set_title(char * c) {\n\tint len = min(TERMINAL_TITLE_SIZE, strlen(c)+1);\n\tmemcpy(terminal_title, c, len);\n\tterminal_title[len-1] = '\\0';\n\tterminal_title_length = len - 1;\n\trender_decors();\n}\n\n/* Call a function for each selected cell */\nstatic void iterate_selection(void (*func)(uint16_t x, uint16_t y)) {\n\tif (!selection) return;\n\tif (selection_end_y < selection_start_y) {\n\t\tfor (int x = selection_end_x; x < term_width; ++x) {\n\t\t\tfunc(x, selection_end_y);\n\t\t}\n\t\tfor (int y = selection_end_y + 1; y < selection_start_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tfunc(x, y);\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x <= selection_start_x; ++x) {\n\t\t\tfunc(x, selection_start_y);\n\t\t}\n\t} else if (selection_start_y == selection_end_y) {\n\t\tif (selection_start_x > selection_end_x) {\n\t\t\tfor (int x = selection_end_x; x <= selection_start_x; ++x) {\n\t\t\t\tfunc(x, selection_start_y);\n\t\t\t}\n\t\t} else {\n\t\t\tfor (int x = selection_start_x; x <= selection_end_x; ++x) {\n\t\t\t\tfunc(x, selection_start_y);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (int x = selection_start_x; x < term_width; ++x) {\n\t\t\tfunc(x, selection_start_y);\n\t\t}\n\t\tfor (int y = selection_start_y + 1; y < selection_end_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tfunc(x, y);\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x <= selection_end_x; ++x) {\n\t\t\tfunc(x, selection_end_y);\n\t\t}\n\t}\n\n}\n\n/* Redraw the selection with the selection hint (inversion) */\nstatic void redraw_selection(void) {\n\titerate_selection(cell_redraw_offset_inverted);\n}\n\nstatic term_cell_t * cell_at(uint16_t x, uint16_t _y) {\n\tint y = _y;\n\ty -= scrollback_offset;\n\tif (y >= 0) {\n\t\treturn &term_buffer[y * term_width + x];\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (; y < -1; y++) {\n\t\t\tif (!node) break;\n\t\t\tnode = node->prev;\n\t\t}\n\t\tif (node) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tif (row && x < row->width) {\n\t\t\t\treturn &row->cells[x];\n\t\t\t}\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic void mark_cell(uint16_t x, uint16_t y) {\n\tterm_cell_t * c = cell_at(x,y);\n\tif (c) {\n\t\tc->flags |= 0x200;\n\t}\n}\n\nstatic void mark_selection(void) {\n\titerate_selection(mark_cell);\n}\n\nstatic void red_cell(uint16_t x, uint16_t y) {\n\tterm_cell_t * c = cell_at(x,y);\n\tif (c) {\n\t\tif (c->flags & 0x200) {\n\t\t\tc->flags &= ~(0x200);\n\t\t} else {\n\t\t\tc->flags |= 0x400;\n\t\t}\n\t}\n}\n\nstatic void flip_selection(void) {\n\titerate_selection(red_cell);\n\tfor (int y = 0; y < term_height; ++y) {\n\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\tterm_cell_t * c = cell_at(x,y);\n\t\t\tif (c) {\n\t\t\t\tif (c->flags & 0x200) cell_redraw_offset(x,y);\n\t\t\t\tif (c->flags & 0x400) cell_redraw_offset_inverted(x,y);\n\t\t\t\tc->flags &= ~(0x600);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Figure out how long the UTF-8 selection string should be. */\nstatic void count_selection(uint16_t x, uint16_t _y) {\n\tint y = _y;\n\ty -= scrollback_offset;\n\tif (y >= 0) {\n\t\tterm_cell_t * cell = &term_buffer[y * term_width + x];\n\t\tif (!(cell->flags & ANSI_EXT_IMG)) {\n\t\t\tif (((uint32_t *)cell)[0] != 0x00000000) {\n\t\t\t\tchar tmp[7];\n\t\t\t\t_selection_count += to_eight(cell->c, tmp);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (; y < -1; y++) {\n\t\t\tif (!node) break;\n\t\t\tnode = node->prev;\n\t\t}\n\t\tif (node) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tif (row && x < row->width) {\n\t\t\t\tterm_cell_t * cell = &row->cells[x];\n\t\t\t\tif (cell && ((uint32_t *)cell)[0] != 0x00000000) {\n\t\t\t\t\tchar tmp[7];\n\t\t\t\t\t_selection_count += to_eight(cell->c, tmp);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (x == term_width - 1) {\n\t\t_selection_count++;\n\t}\n}\n\n/* Fill the selection text buffer with the selected text. */\nvoid write_selection(uint16_t x, uint16_t _y) {\n\tint y = _y;\n\ty -= scrollback_offset;\n\tif (y >= 0) {\n\t\tterm_cell_t * cell = &term_buffer[y * term_width + x];\n\t\tif (!(cell->flags & ANSI_EXT_IMG)) {\n\t\t\tif (((uint32_t *)cell)[0] != 0x00000000 && cell->c != 0xFFFF) {\n\t\t\t\tchar tmp[7];\n\t\t\t\tint count = to_eight(cell->c, tmp);\n\t\t\t\tfor (int i = 0; i < count; ++i) {\n\t\t\t\t\tselection_text[_selection_i] = tmp[i];\n\t\t\t\t\t_selection_i++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (; y < -1; y++) {\n\t\t\tif (!node) break;\n\t\t\tnode = node->prev;\n\t\t}\n\t\tif (node) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tif (row && x < row->width) {\n\t\t\t\tterm_cell_t * cell = &row->cells[x];\n\t\t\t\tif (cell && ((uint32_t *)cell)[0] != 0x00000000 && cell->c != 0xFFFF) {\n\t\t\t\t\tchar tmp[7];\n\t\t\t\t\tint count = to_eight(cell->c, tmp);\n\t\t\t\t\tfor (int i = 0; i < count; ++i) {\n\t\t\t\t\t\tselection_text[_selection_i] = tmp[i];\n\t\t\t\t\t\t_selection_i++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (x == term_width - 1) {\n\t\tselection_text[_selection_i] = '\\n';;\n\t\t_selection_i++;\n\t}\n}\n\n/* Copy the selection text to the clipboard. */\nstatic char * copy_selection(void) {\n\t_selection_count = 0;\n\titerate_selection(count_selection);\n\n\tif (selection_text) {\n\t\tfree(selection_text);\n\t}\n\n\tif (_selection_count == 0) {\n\t\treturn NULL;\n\t}\n\n\tselection_text = malloc(_selection_count + 1);\n\tselection_text[_selection_count] = '\\0';\n\t_selection_i = 0;\n\titerate_selection(write_selection);\n\n\tif (selection_text[_selection_count-1] == '\\n') {\n\t\t/* Don't end on a line feed */\n\t\tselection_text[_selection_count-1] = '\\0';\n\t}\n\n\tyutani_set_clipboard(yctx, selection_text);\n\n\treturn selection_text;\n}\n\nstatic volatile int input_buffer_lock = 0;\nstatic int input_buffer_semaphore[2];\nstatic list_t * input_buffer_queue = NULL;\nstruct input_data {\n\tsize_t len;\n\tchar data[];\n};\n\nvoid * handle_input_writing(void * unused) {\n\t(void)unused;\n\n\twhile (1) {\n\n\t\t/* Read one byte from semaphore; as long as semaphore has data,\n\t\t * there is another input blob to write to the TTY */\n\t\tchar tmp[1];\n\t\tint c = read(input_buffer_semaphore[0],tmp,1);\n\t\tif (c > 0) {\n\t\t\t/* Retrieve blob */\n\t\t\tspin_lock(&input_buffer_lock);\n\t\t\tnode_t * blob = list_dequeue(input_buffer_queue);\n\t\t\tspin_unlock(&input_buffer_lock);\n\t\t\t/* No blobs? This shouldn't happen, but just in case, just continue */\n\t\t\tif (!blob) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* Write blob data to the tty */\n\t\t\tstruct input_data * value = blob->value;\n\t\t\twrite(fd_master, value->data, value->len);\n\t\t\tfree(blob->value);\n\t\t\tfree(blob);\n\t\t} else {\n\t\t\t/* The pipe has closed, terminal is exiting */\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstatic void write_input_buffer(char * data, size_t len) {\n\tstruct input_data * d = malloc(sizeof(struct input_data) + len);\n\td->len = len;\n\tmemcpy(&d->data, data, len);\n\tspin_lock(&input_buffer_lock);\n\tlist_insert(input_buffer_queue, d);\n\tspin_unlock(&input_buffer_lock);\n\twrite(input_buffer_semaphore[1], d, 1);\n}\n\n/* Stuffs a string into the stdin of the terminal's child process\n * Useful for things like the ANSI DSR command. */\nstatic void input_buffer_stuff(char * str) {\n\tsize_t len = strlen(str);\n\twrite_input_buffer(str, len);\n}\n\n\n/* Redraw the decorations */\nstatic void render_decors(void) {\n\t/* Don't draw decorations or bother advertising the window if in \"fullscreen mode\" */\n\tif (_fullscreen) return;\n\n\tif (!_no_frame) {\n\t\t/* Draw the decorations */\n\t\trender_decorations(window, ctx, terminal_title_length ? terminal_title : \"Terminal\");\n\t\t/* Update menu bar position and size */\n\t\tterminal_menu_bar.x = decor_left_width;\n\t\tterminal_menu_bar.y = decor_top_height;\n\t\tterminal_menu_bar.width = window_width;\n\t\tterminal_menu_bar.window = window;\n\t\t/* Redraw the menu bar */\n\t\tmenu_bar_render(&terminal_menu_bar, ctx);\n\t}\n\n\t/* Advertise the window icon to the panel. */\n\tyutani_window_advertise_icon(yctx, window, terminal_title_length ? terminal_title : \"Terminal\", \"utilities-terminal\");\n\n\t/*\n\t * Flip the whole window\n\t * We do this regardless of whether we drew decorations to catch\n\t * a case where decorations are toggled.\n\t */\n\tl_x = 0; l_y = 0;\n\tr_x = window->width;\n\tr_y = window->height;\n\tdisplay_flip();\n}\n\n/* Set a pixel in the terminal cell area */\nstatic inline void term_set_point(uint16_t x, uint16_t y, uint32_t color ) {\n\tGFX(ctx, (x+decor_left_width),(y+decor_top_height+menu_bar_height)) = color;\n}\n\nstatic void _fill_region(uint32_t _bg, uint16_t x, uint16_t y, uint16_t width, uint16_t height) {\n\tfor (uint8_t i = 0; i < height; ++i) {\n\t\tfor (uint8_t j = 0; j < width; ++j) {\n\t\t\tterm_set_point(x+j,y+i,_bg);\n\t\t}\n\t}\n}\n\n/* Draw a partial block character. */\nstatic void draw_semi_block(int c, int x, int y, uint32_t fg, uint32_t bg) {\n\tbg = premultiply(bg);\n\tfg = alpha_blend_rgba(bg, premultiply(fg));\n\t_fill_region(bg, x, y, char_width, char_height);\n\tif (c == 0x2580) {\n\t\tfor (uint8_t i = 0; i < char_height / 2; ++i) {\n\t\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\t\tterm_set_point(x+j,y+i,fg);\n\t\t\t}\n\t\t}\n\t} else if (c >= 0x2589) {\n\t\tc -= 0x2588;\n\t\tint width = char_width - ((c * char_width) / 8);\n\t\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\t\tfor (uint8_t j = 0; j < width; ++j) {\n\t\t\t\tterm_set_point(x+j, y+i, fg);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tc -= 0x2580;\n\t\tint height = char_height - ((c * char_height) / 8);\n\t\tfor (uint8_t i = height; i < char_height; ++i) {\n\t\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\t\tterm_set_point(x+j, y+i,fg);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void draw_box_drawing(int c, int x, int y, uint32_t fg, uint32_t bg) {\n\tbg = premultiply(bg);\n\tfg = alpha_blend_rgba(bg, premultiply(fg));\n\t_fill_region(bg, x, y, char_width, char_height);\n\n\tint lineheight = char_height / 16;\n\tint linewidth = char_width / 8;\n\n\tlineheight = lineheight < 1 ? 1 : lineheight;\n\tlinewidth = linewidth < 1 ? 1 : linewidth;\n\n\tint mid_x = char_width / 2 - linewidth / 2;\n\tint mid_y = char_height / 2 - lineheight / 2;\n\tint extra_x = (mid_x * 2 < char_width) ? char_width - mid_x * 2 : 0;\n\tint extra_y = (mid_y * 2 < char_height) ? char_height - mid_y * 2 : 0;\n\n#define UP    _fill_region(fg, x + mid_x, y, linewidth, mid_y + lineheight)\n#define DOWN  _fill_region(fg, x + mid_x, y + mid_y, linewidth, mid_y + extra_y)\n#define LEFT  _fill_region(fg, x, y + mid_y, mid_x + linewidth, lineheight)\n#define RIGHT _fill_region(fg, x + mid_x, y + mid_y, mid_x + extra_x, lineheight)\n#define VERT  _fill_region(fg, x + mid_x, y, linewidth, char_height)\n#define HORI  _fill_region(fg, x, y + mid_y, char_width, lineheight)\n\n\tswitch (c) {\n\t\tcase 0x2500: HORI; break;\n\t\tcase 0x2502: VERT; break;\n\t\tcase 0x250c: RIGHT; DOWN; break;\n\t\tcase 0x2510: LEFT; DOWN; break;\n\t\tcase 0x2514: UP; RIGHT; break;\n\t\tcase 0x2518: UP; LEFT; break;\n\t\tcase 0x251c: VERT; RIGHT; break;\n\t\tcase 0x2524: VERT; LEFT; break;\n\t\tcase 0x252c: HORI; DOWN; break;\n\t\tcase 0x2534: UP; HORI; break;\n\t\tcase 0x253c: HORI; VERT; break;\n\t\tcase 0x2574: LEFT; break;\n\t\tcase 0x2575: UP; break;\n\t\tcase 0x2576: RIGHT; break;\n\t\tcase 0x2577: DOWN; break;\n\t}\n}\n\n#include \"apps/ununicode.h\"\n\nstruct GlyphCacheEntry {\n\tstruct TT_Font * font;\n\tsprite_t * sprite;\n\tuint32_t size;\n\tuint32_t glyph;\n\tuint32_t color;\n};\n\nstatic struct GlyphCacheEntry glyph_cache[1024];\nstatic unsigned long _hits = 0;\nstatic unsigned long _misses = 0;\nstatic unsigned long _wrongcolor = 0;\n\nstatic void _menu_action_cache_stats(struct MenuEntry * self) {\n\tchar msg[400];\n\n\tunsigned long count = 0;\n\tunsigned long size = 0;\n\n\tfor (int i = 0; i < 1024; ++i) {\n\t\tif (glyph_cache[i].sprite) {\n\t\t\tcount++;\n\t\t\tsize += glyph_cache[i].sprite->width * glyph_cache[i].sprite->height * 4;\n\t\t}\n\t}\n\n\tsnprintf(msg, 400,\n\t\t\"Hits: %lu\\n\"\n\t\t\"Misses: %lu\\n\"\n\t\t\"Wrong color: %lu\\n\"\n\t\t\"Populated cache entries: %lu\\n\"\n\t\t\"Size of sprites: %lu\\n\",\n\t\t_hits, _misses, _wrongcolor, count, size);\n\n\twrite(fd_slave, msg, strlen(msg));\n}\n\nstatic void _menu_action_clear_cache(struct MenuEntry * self) {\n\tfor (int i = 0; i < 1024; ++i) {\n\t\tif (glyph_cache[i].sprite) {\n\t\t\tsprite_free(glyph_cache[i].sprite);\n\t\t}\n\t}\n\tmemset(glyph_cache,0,sizeof(glyph_cache));\n}\n\nstatic void draw_cached_glyph(gfx_context_t * ctx, struct TT_Font * _font, uint32_t size, int x, int y, uint32_t glyph, uint32_t fg, int flags) {\n\tunsigned int hash = (((uintptr_t)_font >> 8) ^ (glyph * size)) & 1023;\n\n\tstruct GlyphCacheEntry * entry = &glyph_cache[hash];\n\n\tif (entry->font != _font || entry->size != size || entry->glyph != glyph) {\n\t\tif (entry->sprite) sprite_free(entry->sprite);\n\t\tint wide = (flags & ANSI_WIDE) ? 2 : 1;\n\t\ttt_set_size(_font, size);\n\n\t\tentry->font = _font;\n\t\tentry->size = size;\n\t\tentry->glyph = glyph;\n\t\tentry->sprite = create_sprite(char_width * wide, char_height, ALPHA_EMBEDDED);\n\t\tentry->color = _ALP(fg) == 255 ? fg : 0xFFFFFFFF;\n\n\t\tgfx_context_t * _ctx = init_graphics_sprite(entry->sprite);\n\t\tdraw_fill(_ctx, 0);\n\t\ttt_draw_glyph(_ctx, entry->font, 0, char_offset, glyph, entry->color);\n\t\tfree(_ctx);\n\t\t_misses++;\n\t} else {\n\t\t_hits++;\n\t}\n\n\tif (entry->color != fg) {\n\t\t_wrongcolor++;\n\t\tdraw_sprite_alpha_paint(ctx, entry->sprite, x, y, 1.0, fg);\n\t} else {\n\t\tdraw_sprite(ctx, entry->sprite, x, y);\n\t}\n}\n\n/* Write a character to the window. */\nstatic void term_write_char(uint32_t val, uint16_t x, uint16_t y, uint32_t fg, uint32_t bg, uint8_t flags) {\n\tuint32_t _fg, _bg;\n\n\t/* Select foreground color from palette. */\n\tif (fg < PALETTE_COLORS) {\n\t\t_fg = term_colors[fg];\n\t\t_fg |= 0xFF << 24;\n\t} else {\n\t\t_fg = fg;\n\t}\n\n\t/* Select background color from aplette. */\n\tif (bg < PALETTE_COLORS) {\n\t\t_bg = term_colors[bg];\n\t\tif (flags & ANSI_SPECBG) {\n\t\t\t_bg |= 0xFF << 24;\n\t\t} else {\n\t\t\t_bg |= TERM_DEFAULT_OPAC << 24;\n\t\t}\n\t} else {\n\t\t_bg = bg;\n\t}\n\n\tif (_fullscreen) {\n\t\t_bg |= 0xFF << 24;\n\t}\n\n\tswitch (val) {\n\t\t/* Line drawing */\n\t\tcase 0x2500:\n\t\tcase 0x2502:\n\t\tcase 0x250c:\n\t\tcase 0x2510:\n\t\tcase 0x2514:\n\t\tcase 0x2518:\n\t\tcase 0x251c:\n\t\tcase 0x2524:\n\t\tcase 0x252c:\n\t\tcase 0x2534:\n\t\tcase 0x253c:\n\t\tcase 0x2574:\n\t\tcase 0x2575:\n\t\tcase 0x2576:\n\t\tcase 0x2577:\n\t\t\tdraw_box_drawing(val, x, y, _fg, _bg);\n\t\t\tgoto _extra_stuff;\n\n\t\t/* Semi-filled blocks */\n\t\tcase 0x2580 ... 0x258f: {\n\t\t\tdraw_semi_block(val, x, y, _fg, _bg);\n\t\t\tgoto _extra_stuff;\n\t\t}\n\n\t\t/* Instead of checker, does 50% opacity fill */\n\t\tcase 0x2591:\n\t\tcase 0x2592:\n\t\tcase 0x2593:\n\t\t\t_fill_region(alpha_blend_rgba(premultiply(_bg), interp_colors(rgb(0,0,0), premultiply(_fg), 255 * (val - 0x2590) / 4)), x, y, char_width, char_height);\n\t\t\tgoto _extra_stuff;\n\n\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\n\t/* Draw glyphs */\n\tif (_use_aa) {\n\t\tif (val == 0xFFFF) return;\n\t\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\t\tterm_set_point(x+j,y+i,_bg);\n\t\t\t}\n\t\t}\n\t\tif (flags & ANSI_WIDE) {\n\t\t\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\t\t\tfor (uint8_t j = char_width; j < 2 * char_width; ++j) {\n\t\t\t\t\tterm_set_point(x+j,y+i,_bg);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (val < 32 || val == ' ') {\n\t\t\tgoto _extra_stuff;\n\t\t}\n\t\tstruct TT_Font * _font = _tt_font_normal;\n\t\tif (flags & ANSI_BOLD && flags & ANSI_ITALIC) {\n\t\t\t_font = _tt_font_bold_oblique;\n\t\t} else if (flags & ANSI_BOLD) {\n\t\t\t_font = _tt_font_bold;\n\t\t} else if (flags & ANSI_ITALIC) {\n\t\t\t_font = _tt_font_oblique;\n\t\t}\n\t\tunsigned int glyph = tt_glyph_for_codepoint(_font, val);\n\n\t\t/* Try the regular sans serif font as a fallback */\n\t\tif (!glyph) {\n\t\t\tint nglyph = tt_glyph_for_codepoint(_tt_font_fallback, val);\n\t\t\tif (nglyph) {\n\t\t\t\t_font = _tt_font_fallback;\n\t\t\t\tglyph = nglyph;\n\t\t\t}\n\t\t}\n\n\t\t/* Try the VL Gothic, if it's installed and this is a reasonably high codepoint */\n\t\tif (!glyph && _tt_font_japanese && val >= 0x2E80) {\n\t\t\tint nglyph = tt_glyph_for_codepoint(_tt_font_japanese, val);\n\t\t\tif (nglyph) {\n\t\t\t\t_font = _tt_font_japanese;\n\t\t\t\tglyph = nglyph;\n\t\t\t}\n\t\t}\n\n\t\tint _x = x + decor_left_width;\n\t\tint _y = y + decor_top_height + menu_bar_height;\n\t\tdraw_cached_glyph(ctx, _font, font_size, _x,_y, glyph, _fg, flags);\n\t} else {\n\t\t/* Convert other unicode characters. */\n\t\tif (val > 128) {\n\t\t\tval = ununicode(val);\n\t\t}\n\t\t/* Draw using the bitmap font. */\n\t\tuint8_t * c = large_font[val];\n\t\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\t\tif (c[i] & (1 << (LARGE_FONT_MASK-j))) {\n\t\t\t\t\tterm_set_point(x+j,y+i,_fg);\n\t\t\t\t} else {\n\t\t\t\t\tterm_set_point(x+j,y+i,_bg);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Draw additional text elements, like underlines and cross-outs. */\n_extra_stuff:\n\tif (flags & ANSI_UNDERLINE) {\n\t\tfor (uint8_t i = 0; i < char_width; ++i) {\n\t\t\tterm_set_point(x + i, y + char_height - 1, _fg);\n\t\t}\n\t}\n\tif (flags & ANSI_CROSS) {\n\t\tfor (uint8_t i = 0; i < char_width; ++i) {\n\t\t\tterm_set_point(x + i, y + char_height - 7, _fg);\n\t\t}\n\t}\n\tif (flags & ANSI_BORDER) {\n\t\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\t\tterm_set_point(x , y + i, _fg);\n\t\t\tterm_set_point(x + (char_width - 1), y + i, _fg);\n\t\t}\n\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\tterm_set_point(x + j, y, _fg);\n\t\t\tterm_set_point(x + j, y + (char_height - 1), _fg);\n\t\t}\n\t}\n\n\t/* Calculate the bounds of the updated region of the window */\n\tl_x = min(l_x, decor_left_width + x);\n\tl_y = min(l_y, decor_top_height+menu_bar_height + y);\n\n\tif (flags & ANSI_WIDE) {\n\t\tr_x = max(r_x, decor_left_width + x + char_width * 2);\n\t\tr_y = max(r_y, decor_top_height+menu_bar_height + y + char_height * 2);\n\t} else {\n\t\tr_x = max(r_x, decor_left_width + x + char_width);\n\t\tr_y = max(r_y, decor_top_height+menu_bar_height + y + char_height);\n\t}\n}\n\nstatic void term_mirror_set(uint16_t x, uint16_t y, uint32_t val, uint32_t fg, uint32_t bg, uint8_t flags) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = &term_mirror[y * term_width + x];\n\tcell->c = val;\n\tcell->fg = fg;\n\tcell->bg = bg;\n\tcell->flags = flags;\n}\n\nstatic void term_mirror_copy(uint16_t x, uint16_t y, term_cell_t * from) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = &term_mirror[y * term_width + x];\n\tif (!from->c && !from->fg && !from->bg) {\n\t\tcell->c = ' ';\n\t\tcell->fg = TERM_DEFAULT_FG;\n\t\tcell->bg = TERM_DEFAULT_BG;\n\t\tcell->flags = from->flags;\n\t} else {\n\t\t*cell = *from;\n\t}\n}\n\nstatic void term_mirror_copy_inverted(uint16_t x, uint16_t y, term_cell_t * from) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t * cell = &term_mirror[y * term_width + x];\n\tif (!from->c && !from->fg && !from->bg) {\n\t\tcell->c = ' ';\n\t\tcell->fg = TERM_DEFAULT_BG;\n\t\tcell->bg = TERM_DEFAULT_FG;\n\t\tcell->flags = from->flags;\n\t} else if (from->flags & ANSI_EXT_IMG) {\n\t\tcell->c = ' ';\n\t\tcell->fg = from->fg;\n\t\tcell->bg = from->bg;\n\t\tcell->flags = from->flags | ANSI_SPECBG;\n\t} else {\n\t\tcell->c = from->c;\n\t\tcell->fg = from->bg;\n\t\tcell->bg = from->fg;\n\t\tcell->flags = from->flags | ANSI_SPECBG;\n\t}\n}\n\n/* Set a terminal cell */\nstatic void cell_set(uint16_t x, uint16_t y, uint32_t c, uint32_t fg, uint32_t bg, uint32_t flags) {\n\t/* Avoid setting cells out of range. */\n\tif (x >= term_width || y >= term_height) return;\n\n\t/* Calculate the cell position in the terminal buffer */\n\tterm_cell_t * cell = &term_buffer[y * term_width + x];\n\n\t/* Set cell attributes */\n\tcell->c     = c;\n\tcell->fg    = fg;\n\tcell->bg    = bg;\n\tcell->flags = flags;\n}\n\n/* Redraw an embedded image cell */\nstatic void redraw_cell_image(uint16_t x, uint16_t y, term_cell_t * cell, int inverted) {\n\t/* Avoid setting cells out of range. */\n\tif (x >= term_width || y >= term_height) return;\n\n\t/* Draw the image data */\n\tuint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg);\n\tif (inverted) {\n\t\tfor (uint32_t yy = 0; yy < char_height; ++yy) {\n\t\t\tfor (uint32_t xx = 0; xx < char_width; ++xx) {\n\t\t\t\tuint32_t alpha = 0xFF000000 & *data;\n\t\t\t\tuint32_t color = 0xFFFFFF - (*data & 0xFFFFFF);\n\t\t\t\tterm_set_point(x * char_width + xx, y * char_height + yy, color | alpha);\n\t\t\t\tdata++;\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor (uint32_t yy = 0; yy < char_height; ++yy) {\n\t\t\tfor (uint32_t xx = 0; xx < char_width; ++xx) {\n\t\t\t\tterm_set_point(x * char_width + xx, y * char_height + yy, *data);\n\t\t\t\tdata++;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Update bounds */\n\tl_x = min(l_x, decor_left_width + x * char_width);\n\tl_y = min(l_y, decor_top_height+menu_bar_height + y * char_height);\n\tr_x = max(r_x, decor_left_width + x * char_width + char_width);\n\tr_y = max(r_y, decor_top_height+menu_bar_height + y * char_height + char_height);\n}\n\nstatic void maybe_flip_display(int force) {\n\tstatic uint64_t last_refresh;\n\tuint64_t ticks = get_ticks();\n\tif (!force) {\n\t\tif (ticks < last_refresh + 33330L) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tlast_refresh = ticks;\n\n\tfor (unsigned int y = 0; y < term_height; ++y) {\n\t\tfor (unsigned int x = 0; x < term_width; ++x) {\n\t\t\tterm_cell_t * cell_m = &term_mirror[y * term_width + x];\n\t\t\tterm_cell_t * cell_d = &term_display[y * term_width + x];\n\t\t\tif (memcmp(cell_m, cell_d, sizeof(term_cell_t))) {\n\t\t\t\t*cell_d = *cell_m;\n\t\t\t\tif (cell_m->flags & ANSI_EXT_IMG) {\n\t\t\t\t\tredraw_cell_image(x,y,cell_m,cell_m->flags & ANSI_SPECBG);\n\t\t\t\t} else {\n\t\t\t\t\tterm_write_char(cell_m->c, x * char_width, y * char_height, cell_m->fg, cell_m->bg, cell_m->flags);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tdisplay_flip();\n}\n\nstatic void cell_redraw_offset(uint16_t x, uint16_t _y) {\n\tint y = _y;\n\tint i = y;\n\n\ty -= scrollback_offset;\n\n\tif (y >= 0) {\n\t\tterm_mirror_copy(x,i,&term_buffer[y * term_width + x]);\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (; y < -1; y++) {\n\t\t\tif (!node) break;\n\t\t\tnode = node->prev;\n\t\t}\n\t\tif (node) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tif (row && x < row->width) {\n\t\t\t\tterm_mirror_copy(x,i,&row->cells[x]);\n\t\t\t} else {\n\t\t\t\tterm_mirror_set(x,i,' ',TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void cell_redraw_offset_inverted(uint16_t x, uint16_t _y) {\n\tint y = _y;\n\tint i = y;\n\n\ty -= scrollback_offset;\n\n\tif (y >= 0) {\n\t\tterm_mirror_copy_inverted(x,i,&term_buffer[y * term_width + x]);\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (; y < -1; y++) {\n\t\t\tif (!node) break;\n\t\t\tnode = node->prev;\n\t\t}\n\t\tif (node) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tif (row && x < row->width) {\n\t\t\t\tterm_mirror_copy_inverted(x,i,&row->cells[x]);\n\t\t\t} else {\n\t\t\t\tterm_mirror_set(x, i, ' ', TERM_DEFAULT_BG, TERM_DEFAULT_FG, TERM_DEFAULT_FLAGS|ANSI_SPECBG);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Redraw a text cell normally. */\nstatic void cell_redraw(uint16_t x, uint16_t y) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_mirror_copy(x,y,&term_buffer[y * term_width + x]);\n}\n\n/* Redraw text cell inverted. */\nstatic void cell_redraw_inverted(uint16_t x, uint16_t y) {\n\t/* Avoid cells out of range. */\n\tif (x >= term_width || y >= term_height) return;\n\tterm_mirror_copy_inverted(x,y,&term_buffer[y * term_width + x]);\n}\n\n/* Redraw text cell with a surrounding box (used by cursor) */\nstatic void cell_redraw_box(uint16_t x, uint16_t y) {\n\tif (x >= term_width || y >= term_height) return;\n\tterm_cell_t cell = term_buffer[y * term_width + x];\n\tcell.flags |= ANSI_BORDER;\n\tterm_mirror_copy(x,y,&cell);\n}\n\n/* Draw the cursor cell */\nstatic void render_cursor() {\n\tif (!cursor_on) return;\n\tif (!window->focused) {\n\t\t/* An unfocused terminal should draw an unfilled box. */\n\t\tcell_redraw_box(csr_x, csr_y);\n\t} else {\n\t\t/* A focused terminal draws a solid box. */\n\t\tcell_redraw_inverted(csr_x, csr_y);\n\t}\n}\n\nstatic uint8_t cursor_flipped = 0;\n/* A soft request to draw the cursor. */\nstatic void draw_cursor() {\n\tif (!cursor_on) return;\n\tcursor_flipped = 0;\n\trender_cursor();\n}\n\n/* Timer callback to flip (flash) the cursor */\nstatic void maybe_flip_cursor(void) {\n\tuint64_t ticks = get_ticks();\n\tif (ticks > mouse_ticks + 600000LL) {\n\t\tmouse_ticks = ticks;\n\t\tif (scrollback_offset != 0) {\n\t\t\treturn; /* Don't flip cursor while drawing scrollback */\n\t\t}\n\t\tif (window->focused && cursor_flipped) {\n\t\t\tcell_redraw(csr_x, csr_y);\n\t\t} else {\n\t\t\trender_cursor();\n\t\t}\n\t\tcursor_flipped = 1 - cursor_flipped;\n\t}\n}\n\n/* Draw all cells. Duplicates code from cell_redraw to avoid unecessary bounds checks. */\nstatic void term_redraw_all() {\n\tfor (int i = 0; i < term_height; i++) {\n\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\tterm_mirror_copy(x,i,&term_buffer[i * term_width + x]);\n\t\t}\n\t}\n}\n\nstatic void _menu_action_redraw(struct MenuEntry * self) {\n\tterm_redraw_all();\n}\n\n/* Remove no-longer-visible image cell data. */\nstatic void flush_unused_images(void) {\n\tif (!images_list->length) return;\n\n\tlist_t * tmp = list_create();\n\n\t/* Go through scrollback, too */\n\tif (scrollback_list) {\n\t\tforeach(node, scrollback_list) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\t\t\tfor (unsigned int x = 0; x < row->width; ++x) {\n\t\t\t\tterm_cell_t * cell = &row->cells[x];\n\t\t\t\tif (cell->flags & ANSI_EXT_IMG) {\n\t\t\t\t\tuint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg);\n\t\t\t\t\tlist_insert(tmp, data);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (int y = 0; y < term_height; ++y) {\n\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\tterm_cell_t * cell = &term_buffer_a[y * term_width + x];\n\t\t\tif (cell->flags & ANSI_EXT_IMG) {\n\t\t\t\tuint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg);\n\t\t\t\tlist_insert(tmp, data);\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (int y = 0; y < term_height; ++y) {\n\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\tterm_cell_t * cell = &term_buffer_b[y * term_width + x];\n\t\t\tif (cell->flags & ANSI_EXT_IMG) {\n\t\t\t\tuint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg);\n\t\t\t\tlist_insert(tmp, data);\n\t\t\t}\n\t\t}\n\t}\n\n\tforeach(node, images_list) {\n\t\tif (!list_find(tmp, node->value)) {\n\t\t\tfree(node->value);\n\t\t}\n\t}\n\n\tlist_free(images_list);\n\timages_list = tmp;\n}\n\nstatic void term_shift_region(int top, int height, int how_much) {\n\tif (how_much == 0) return;\n\n\tint destination, source;\n\tint count, new_top, new_bottom;\n\tif (how_much > height) {\n\t\tcount = 0;\n\t\tnew_top = top;\n\t\tnew_bottom = top + height;\n\t} else if (how_much > 0) {\n\t\tdestination = term_width * top;\n\t\tsource = term_width * (top + how_much);\n\t\tcount = height - how_much;\n\t\tnew_top = top + height - how_much;\n\t\tnew_bottom = top + height;\n\t} else if (how_much < 0) {\n\t\tdestination = term_width * (top - how_much);\n\t\tsource = term_width * top;\n\t\tcount = height + how_much;\n\t\tnew_top = top;\n\t\tnew_bottom = top - how_much;\n\t}\n\n\t/* Move from top+how_much to top */\n\tif (count) {\n\t\tmemmove(term_buffer + destination, term_buffer + source, count * term_width * sizeof(term_cell_t));\n\t\tmemmove(term_mirror + destination, term_mirror + source, count * term_width * sizeof(term_cell_t));\n\t}\n\n\tl_x = 0; l_y = 0;\n\tr_x = window->width;\n\tr_y = window->height;\n\n\t/* Clear new lines at bottom */\n\tfor (int i = new_top; i < new_bottom; ++i) {\n\t\tfor (uint16_t x = 0; x < term_width; ++x) {\n\t\t\tcell_set(x, i, ' ', current_fg, current_bg, ansi_state->flags);\n\t\t\tcell_redraw(x, i);\n\t\t}\n\t}\n}\n\n/* Scroll the terminal up or down. */\nstatic void term_scroll(int how_much) {\n\tterm_shift_region(0, term_height, how_much);\n\n\t/* Remove image data for image cells that are no longer on screen. */\n\tflush_unused_images();\n}\n\nstatic void insert_delete_lines(int how_many) {\n\tif (how_many == 0) return;\n\n\tif (how_many > 0) {\n\t\t/* Insert lines is equivalent to scrolling from the current line */\n\t\tterm_shift_region(csr_y,term_height-csr_y,-how_many);\n\t} else {\n\t\tterm_shift_region(csr_y,term_height-csr_y,-how_many);\n\t}\n}\n\n/* Is this a wide character? (does wcwidth == 2) */\nstatic int is_wide(uint32_t codepoint) {\n\tif (codepoint < 256) return 0;\n\treturn wcwidth(codepoint) == 2;\n}\n\n/* Save the row that is about to be scrolled offscreen into the scrollback buffer. */\nstatic void save_scrollback(void) {\n\t/* If the scrollback is already full, remove the oldest element. */\n\tstruct scrollback_row * row = NULL;\n\tnode_t * n = NULL;\n\n\tif (max_scrollback && scrollback_list->length == max_scrollback) {\n\t\tn = list_dequeue(scrollback_list);\n\t\trow = n->value;\n\t\tif (row->width < term_width) {\n\t\t\tfree(row);\n\t\t\trow = NULL;\n\t\t}\n\t}\n\n\tif (!row) {\n\t\trow = malloc(sizeof(struct scrollback_row) + sizeof(term_cell_t) * term_width);\n\t\trow->width = term_width;\n\t}\n\n\tif (!n) {\n\t\tlist_insert(scrollback_list, row);\n\t} else {\n\t\tn->value = row;\n\t\tlist_append(scrollback_list, n);\n\t}\n\n\tfor (int i = 0; i < term_width; ++i) {\n\t\tterm_cell_t * cell = &term_buffer[i];\n\t\tmemcpy(&row->cells[i], cell, sizeof(term_cell_t));\n\t}\n}\n\n/* Draw the scrollback. */\nstatic void redraw_scrollback(void) {\n\tif (!scrollback_offset) {\n\t\tterm_redraw_all();\n\t\treturn;\n\t}\n\tif (scrollback_offset < term_height) {\n\t\tfor (int i = scrollback_offset; i < term_height; i++) {\n\t\t\tint y = i - scrollback_offset;\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tterm_mirror_copy(x,i,&term_buffer[y * term_width + x]);\n\t\t\t}\n\t\t}\n\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (int i = 0; i < scrollback_offset; ++i) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\n\t\t\tint y = scrollback_offset - 1 - i;\n\t\t\tint width = row->width;\n\t\t\tif (width > term_width) {\n\t\t\t\twidth = term_width;\n\t\t\t} else {\n\t\t\t\tfor (int x = row->width; x < term_width; ++x) {\n\t\t\t\t\tterm_mirror_set(x, y, ' ', TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int x = 0; x < width; ++x) {\n\t\t\t\tterm_mirror_copy(x,y,&row->cells[x]);\n\t\t\t}\n\n\t\t\tnode = node->prev;\n\t\t}\n\t} else {\n\t\tnode_t * node = scrollback_list->tail;\n\t\tfor (int i = 0; i < scrollback_offset - term_height; ++i) {\n\t\t\tnode = node->prev;\n\t\t}\n\t\tfor (int i = scrollback_offset - term_height; i < scrollback_offset; ++i) {\n\t\t\tstruct scrollback_row * row = (struct scrollback_row *)node->value;\n\n\t\t\tint y = scrollback_offset - 1 - i;\n\t\t\tint width = row->width;\n\t\t\tif (width > term_width) {\n\t\t\t\twidth = term_width;\n\t\t\t} else {\n\t\t\t\tfor (int x = row->width; x < term_width; ++x) {\n\t\t\t\t\tterm_mirror_set(x, y, ' ', TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (int x = 0; x < width; ++x) {\n\t\t\t\tterm_mirror_copy(x,y,&row->cells[x]);\n\t\t\t}\n\n\t\t\tnode = node->prev;\n\t\t}\n\t}\n}\n\nstatic void undraw_cursor(void) {\n\tcell_redraw(csr_x, csr_y);\n}\n\nstatic void normalize_x(int setting_lcf) {\n\tif (csr_x >= term_width) {\n\t\tcsr_x = term_width - 1;\n\t\tif (setting_lcf) {\n\t\t\tcsr_h = 1;\n\t\t}\n\t}\n}\n\nstatic void normalize_y(void) {\n\tif (csr_y == term_height) {\n\t\tsave_scrollback();\n\t\tterm_scroll(1);\n\t\tcsr_y = term_height - 1;\n\t}\n}\n\n/*\n * ANSI callback for writing characters.\n * Parses some things (\\n\\r, etc.) itself that should probably\n * be moved into the ANSI library.\n */\nstatic void term_write(char c) {\n\tstatic uint32_t unicode_state = 0;\n\tstatic uint32_t codepoint = 0;\n\n\tif (!decode(&unicode_state, &codepoint, (uint8_t)c)) {\n\t\tuint32_t o = codepoint;\n\t\tcodepoint = 0;\n\n\t\tswitch (c) {\n\t\t\tcase '\\a':\n\t\t\t\t/* boop */\n\t\t\t\treturn;\n\n\t\t\tcase '\\r':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_x = csr_h = 0;\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\t':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_x += (8 - csr_x % 8);\n\t\t\t\tnormalize_x(0);\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\v':\n\t\t\tcase '\\f':\n\t\t\tcase '\\n':\n\t\t\t\tundraw_cursor();\n\t\t\t\tcsr_h = 0;\n\t\t\t\t++csr_y;\n\t\t\t\tnormalize_y();\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\n\t\t\tcase '\\b':\n\t\t\t\tif (csr_x > 0) {\n\t\t\t\t\tundraw_cursor();\n\t\t\t\t\t--csr_x;\n\t\t\t\t\tdraw_cursor();\n\t\t\t\t}\n\t\t\t\tcsr_h = 0;\n\t\t\t\treturn;\n\n\t\t\tdefault: {\n\t\t\t\tint wide = is_wide(o);\n\t\t\t\tuint8_t flags = ansi_state->flags;\n\n\t\t\t\tundraw_cursor();\n\n\t\t\t\tif (csr_h || (wide && csr_x == term_width - 1)) {\n\t\t\t\t\tcsr_x = csr_h = 0;\n\t\t\t\t\t++csr_y;\n\t\t\t\t\tnormalize_y();\n\t\t\t\t}\n\n\t\t\t\tif (wide) {\n\t\t\t\t\tflags = flags | ANSI_WIDE;\n\t\t\t\t}\n\n\t\t\t\tcell_set(csr_x,csr_y, o, current_fg, current_bg, flags);\n\t\t\t\tcell_redraw(csr_x,csr_y);\n\t\t\t\tcsr_x++;\n\n\t\t\t\tif (wide && csr_x != term_width) {\n\t\t\t\t\tcell_set(csr_x, csr_y, 0xFFFF, current_fg, current_bg, ansi_state->flags);\n\t\t\t\t\tcell_redraw(csr_x,csr_y);\n\t\t\t\t\tcell_redraw(csr_x-1,csr_y);\n\t\t\t\t\tcsr_x++;\n\t\t\t\t}\n\n\t\t\t\tnormalize_x(1);\n\t\t\t\tdraw_cursor();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t} else if (unicode_state == UTF8_REJECT) {\n\t\tunicode_state = 0;\n\t\tcodepoint = 0;\n\t}\n}\n\n/* ANSI callback to set cursor position */\nstatic void term_set_csr(int x, int y) {\n\tcell_redraw(csr_x,csr_y);\n\tif (x < 0) x = 0;\n\tif (x >= term_width) x = term_width - 1;\n\tif (y < 0) y = 0;\n\tif (y >= term_height) y = term_height - 1;\n\tcsr_x = x;\n\tcsr_y = y;\n\tcsr_h = 0;\n\tdraw_cursor();\n}\n\n/* ANSI callback to get cursor x position */\nstatic int term_get_csr_x(void) {\n\treturn csr_x;\n}\n\n/* ANSI callback to get cursor y position */\nstatic int term_get_csr_y(void) {\n\treturn csr_y;\n}\n\n/* ANSI callback to set cell image data. */\nstatic void term_set_cell_contents(int x, int y, char * data) {\n\tchar * cell_data = malloc(char_width * char_height * sizeof(uint32_t));\n\tmemcpy(cell_data, data, char_width * char_height * sizeof(uint32_t));\n\tlist_insert(images_list, cell_data);\n\tcell_set(x, y, ' ',\n\t\t(uintptr_t)(cell_data) & 0xFFFFFFFF,\n\t\t(uintptr_t)(cell_data) >> 32,\n\t\tANSI_EXT_IMG);\n}\n\n/* ANSI callback to get character cell width */\nstatic int term_get_cell_width(void) {\n\treturn char_width;\n}\n\n/* ANSI callback to get character cell height */\nstatic int term_get_cell_height(void) {\n\treturn char_height;\n}\n\n/* ANSI callback to set cursor visibility */\nstatic void term_set_csr_show(int on) {\n\tcursor_on = on;\n\tif (on) {\n\t\tdraw_cursor();\n\t}\n}\n\n/* ANSI callback to set the foreground/background colors. */\nstatic void term_set_colors(uint32_t fg, uint32_t bg) {\n\tcurrent_fg = fg;\n\tcurrent_bg = bg;\n}\n\n/* ANSI callback to force the cursor to draw */\nstatic void term_redraw_cursor() {\n\tif (term_buffer) {\n\t\tdraw_cursor();\n\t}\n}\n\n/* ANSI callback to set a cell to a codepoint (only ever used to set spaces) */\nstatic void term_set_cell(int x, int y, uint32_t c) {\n\tcell_set(x, y, c, current_fg, current_bg, ansi_state->flags);\n\tcell_redraw(x, y);\n}\n\n/* ANSI callback to clear the terminal. */\nstatic void term_clear(int i) {\n\tif (i == 2) {\n\t\t/* Clear all */\n\t\tcsr_x = 0;\n\t\tcsr_y = 0;\n\t\tcsr_h = 0;\n\t\tmemset((void *)term_buffer, 0x00, term_width * term_height * sizeof(term_cell_t));\n\t\tif (!_no_frame) {\n\t\t\trender_decors();\n\t\t}\n\t\tterm_redraw_all();\n\t} else if (i == 0) {\n\t\t/* Clear after cursor */\n\t\tfor (int x = csr_x; x < term_width; ++x) {\n\t\t\tterm_set_cell(x, csr_y, ' ');\n\t\t}\n\t\tfor (int y = csr_y + 1; y < term_height; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tterm_set_cell(x, y, ' ');\n\t\t\t}\n\t\t}\n\t} else if (i == 1) {\n\t\t/* Clear before cursor */\n\t\tfor (int y = 0; y < csr_y; ++y) {\n\t\t\tfor (int x = 0; x < term_width; ++x) {\n\t\t\t\tterm_set_cell(x, y, ' ');\n\t\t\t}\n\t\t}\n\t\tfor (int x = 0; x < csr_x; ++x) {\n\t\t\tterm_set_cell(x, csr_y, ' ');\n\t\t}\n\t} else if (i == 3) {\n\t\t/* Clear scrollback */\n\t\tif (scrollback_list) {\n\t\t\twhile (scrollback_list->length) {\n\t\t\t\tnode_t * n = list_dequeue(scrollback_list);\n\t\t\t\tfree(n->value);\n\t\t\t\tfree(n);\n\t\t\t}\n\t\t\tscrollback_offset = 0;\n\t\t}\n\n\t}\n\tflush_unused_images();\n}\n\n\n#define SWAP(T,a,b) do { T _a = a; a = b; b = _a; } while(0);\n\nstatic void term_switch_buffer(int buffer) {\n\tif (buffer != 0 && buffer != 1) return;\n\tif (buffer != active_buffer) {\n\t\tactive_buffer = buffer;\n\t\tterm_buffer = active_buffer == 0 ? term_buffer_a : term_buffer_b;\n\n\t\tSWAP(int, csr_x, _orig_x);\n\t\tSWAP(int, csr_y, _orig_y);\n\t\tSWAP(uint32_t, current_fg, _orig_fg);\n\t\tSWAP(uint32_t, current_bg, _orig_bg);\n\n\t\tterm_redraw_all();\n\t}\n}\n\nstatic void full_reset(void) {\n\t/* Reset everything */\n\tcsr_x = 0;\n\tcsr_y = 0;\n\tcsr_h = 0;\n\n\t/* Huh, why don't we haven't an _orig_h - surely hold should be saved? */\n\t_orig_x = 0;\n\t_orig_y = 0;\n\n\tcurrent_fg = TERM_DEFAULT_FG;\n\tcurrent_bg = TERM_DEFAULT_BG;\n\t_orig_fg = TERM_DEFAULT_FG;\n\t_orig_bg = TERM_DEFAULT_BG;\n\n\tactive_buffer = 0;\n\tterm_buffer = term_buffer_a;\n\n\t/* Clear both buffers to 0 */\n\tmemset((void *)term_buffer_a, 0x00, term_width * term_height * sizeof(term_cell_t));\n\tmemset((void *)term_buffer_b, 0x00, term_width * term_height * sizeof(term_cell_t));\n\n\t/* Clear the mirror; do not clear the display buffer */\n\tmemset((void *)term_mirror, 0x00, term_width * term_height * sizeof(term_cell_t));\n\n\t/* Enable cursor */\n\tcursor_on = 1;\n\n\t/* Clear title */\n\tterminal_title_length = 0;\n}\n\n/* ANSI callbacks */\nterm_callbacks_t term_callbacks = {\n\tterm_write,\n\tterm_set_colors,\n\tterm_set_csr,\n\tterm_get_csr_x,\n\tterm_get_csr_y,\n\tterm_set_cell,\n\tterm_clear,\n\tterm_scroll,\n\tterm_redraw_cursor,\n\tinput_buffer_stuff,\n\tset_title,\n\tterm_set_cell_contents,\n\tterm_get_cell_width,\n\tterm_get_cell_height,\n\tterm_set_csr_show,\n\tterm_switch_buffer,\n\tinsert_delete_lines,\n\tfull_reset,\n};\n\nstatic void handle_input(char c) {\n\twrite_input_buffer(&c, 1);\n\tif (scrollback_offset != 0) {\n\t\tscrollback_offset = 0;\n\t\tterm_redraw_all();\n\t}\n}\n\nstatic void handle_input_s(char * c) {\n\tsize_t len = strlen(c);\n\twrite_input_buffer(c, len);\n\tif (scrollback_offset != 0) {\n\t\tscrollback_offset = 0;\n\t\tterm_redraw_all();\n\t}\n}\n\n\n/* Scroll the view up (scrollback) */\nstatic void scroll_up(int amount) {\n\tint i = 0;\n\twhile (i < amount && scrollback_list && scrollback_offset < (int)scrollback_list->length) {\n\t\tscrollback_offset ++;\n\t\ti++;\n\t}\n\tredraw_scrollback();\n}\n\n/* Scroll the view down (scrollback) */\nvoid scroll_down(int amount) {\n\tint i = 0;\n\twhile (i < amount && scrollback_list && scrollback_offset != 0) {\n\t\tscrollback_offset -= 1;\n\t\ti++;\n\t}\n\tredraw_scrollback();\n}\n\n/* Handle a key press from Yutani */\nstatic void key_event(int ret, key_event_t * event) {\n\tif (ret) {\n\t\t/* Ctrl-Shift-C - Copy selection */\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t\t(event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == 'c')) {\n\t\t\tif (selection) {\n\t\t\t\t/* Copy selection */\n\t\t\t\tcopy_selection();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t/* Ctrl-Shift-V - Paste selection */\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t\t(event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == 'v')) {\n\t\t\t/* Paste selection */\n\t\t\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD);\n\t\t\treturn;\n\t\t}\n\n\t\tif ((event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == '0')) {\n\t\t\tscale_fonts  = 0;\n\t\t\tfont_scaling = 1.0;\n\t\t\tupdate_scale_menu();\n\t\t\treinit();\n\t\t\treturn;\n\t\t}\n\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t\t(event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == '=')) {\n\t\t\tscale_fonts  = 1;\n\t\t\tfont_scaling = font_scaling * 1.2;\n\t\t\tupdate_scale_menu();\n\t\t\treinit();\n\t\t\treturn;\n\t\t}\n\n\t\tif ((event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) &&\n\t\t\t(event->keycode == '-')) {\n\t\t\tscale_fonts  = 1;\n\t\t\tfont_scaling = font_scaling * 0.8333333;\n\t\t\tupdate_scale_menu();\n\t\t\treinit();\n\t\t\treturn;\n\t\t}\n\n\t\t/* Left alt */\n\t\tif (event->modifiers & KEY_MOD_LEFT_ALT || event->modifiers & KEY_MOD_RIGHT_ALT) {\n\t\t\thandle_input('\\033');\n\t\t}\n\n\t\t/* Shift-Tab */\n\t\tif ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) &&\n\t\t    event->key == '\\t') {\n\t\t\thandle_input_s(\"\\033[Z\");\n\t\t\treturn;\n\t\t}\n\n\t\t/* ENTER = reads as linefeed, should be carriage return */\n\t\tif (event->keycode == 10) {\n\t\t\thandle_input('\\r');\n\t\t\treturn;\n\t\t}\n\n\t\t/* BACKSPACE = reads as ^H, should be ^? */\n\t\tif (event->keycode == 8) {\n\t\t\thandle_input(0x7F);\n\t\t\treturn;\n\t\t}\n\n\t\t/* Pass key value to PTY */\n\t\thandle_input(event->key);\n\t} else {\n\t\t/* Special keys without ->key values */\n\n\t\t/* Only trigger on key down */\n\t\tif (event->action == KEY_ACTION_UP) return;\n\n\t\tswitch (event->keycode) {\n\t\t\tcase KEY_F1:\n\t\t\t\thandle_input_s(\"\\033OP\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F2:\n\t\t\t\thandle_input_s(\"\\033OQ\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F3:\n\t\t\t\thandle_input_s(\"\\033OR\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F4:\n\t\t\t\thandle_input_s(\"\\033OS\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F5:\n\t\t\t\thandle_input_s(\"\\033[15~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F6:\n\t\t\t\thandle_input_s(\"\\033[17~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F7:\n\t\t\t\thandle_input_s(\"\\033[18~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F8:\n\t\t\t\thandle_input_s(\"\\033[19~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F9:\n\t\t\t\thandle_input_s(\"\\033[20~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F10:\n\t\t\t\thandle_input_s(\"\\033[21~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F11:\n\t\t\t\thandle_input_s(\"\\033[23~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_F12:\n\t\t\t\t/* Toggle decorations */\n\t\t\t\tif (!_fullscreen) {\n\t\t\t\t\t_no_frame = !_no_frame;\n\t\t\t\t\tupdate_bounds();\n\t\t\t\t\twindow_width = window->width - decor_width;\n\t\t\t\t\twindow_height = window->height - (decor_height + menu_bar_height);\n\t\t\t\t\treinit();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_UP:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3A\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2A\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[A\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_DOWN:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3B\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2B\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[B\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_RIGHT:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3C\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2C\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[C\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_ARROW_LEFT:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[6D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_CTRL) {\n\t\t\t\t\thandle_input_s(\"\\033[5D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[4D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_ALT) {\n\t\t\t\t\thandle_input_s(\"\\033[3D\");\n\t\t\t\t} else if (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\thandle_input_s(\"\\033[2D\");\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[D\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_PAGE_UP:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\tscroll_up(term_height/2);\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[5~\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_PAGE_DOWN:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\tscroll_down(term_height/2);\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[6~\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_HOME:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\tif (scrollback_list) {\n\t\t\t\t\t\tscrollback_offset = scrollback_list->length;\n\t\t\t\t\t\tredraw_scrollback();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[H\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_END:\n\t\t\t\tif (event->modifiers & KEY_MOD_LEFT_SHIFT) {\n\t\t\t\t\tscrollback_offset = 0;\n\t\t\t\t\tredraw_scrollback();\n\t\t\t\t} else {\n\t\t\t\t\thandle_input_s(\"\\033[F\");\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase KEY_DEL:\n\t\t\t\thandle_input_s(\"\\033[3~\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_INSERT:\n\t\t\t\thandle_input_s(\"\\033[2~\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/* Check if the Terminal should close. */\nstatic void check_for_exit(void) {\n\n\t/* If something has set exit_application, we should exit. */\n\tif (exit_application) return;\n\n\tpid_t pid = waitpid(-1, NULL, WNOHANG);\n\n\t/* If the child has exited, we should exit. */\n\tif (pid != child_pid) return;\n\n\t/* Clean up */\n\texit_application = 1;\n\n\t/* Write [Process terminated] */\n\tchar exit_message[] = \"[Process terminated]\\n\";\n\twrite(fd_slave, exit_message, sizeof(exit_message));\n\tclose(input_buffer_semaphore[1]);\n}\n\nstatic term_cell_t * copy_terminal(int old_width, int old_height, term_cell_t * term_buffer) {\n\tterm_cell_t * new_term_buffer = malloc(sizeof(term_cell_t) * term_width * term_height);\n\n\tmemset(new_term_buffer, 0x0, sizeof(term_cell_t) * term_width * term_height);\n\n\tint offset = 0;\n\tif (term_height < old_height) {\n\t\twhile (csr_y >= term_height) {\n\t\t\toffset++;\n\t\t\told_height--;\n\t\t\tcsr_y--;\n\t\t}\n\t}\n\tfor (int row = 0; row < min(old_height, term_height); ++row) {\n\t\tfor (int col = 0; col < min(old_width, term_width); ++col) {\n\t\t\tterm_cell_t * old_cell = &term_buffer[(row+offset) * old_width + col];\n\t\t\tterm_cell_t * new_cell = &new_term_buffer[row * term_width + col];\n\t\t\t*new_cell = *old_cell;\n\t\t}\n\t}\n\tif (csr_x >= term_width) {\n\t\tcsr_x = term_width-1;\n\t}\n\n\treturn new_term_buffer;\n}\n\n/* Reinitialize the terminal after a resize. */\nstatic void reinit(void) {\n\n\t/* Figure out character sizes if fonts have changed. */\n\tif (_use_aa) {\n\t\tchar_width = 8;\n\t\tchar_height = 17;\n\t\tfont_size = 13;\n\t\tchar_offset = 13;\n\t\tif (scale_fonts) {\n\t\t\tfont_size   *= font_scaling;\n\t\t\tchar_height *= font_scaling;\n\t\t\tchar_width  *= font_scaling;\n\t\t\tchar_offset *= font_scaling;\n\t\t}\n\t} else {\n\t\tchar_width = LARGE_FONT_CELL_WIDTH;\n\t\tchar_height = LARGE_FONT_CELL_HEIGHT;\n\t}\n\n\tint old_width  = term_width;\n\tint old_height = term_height;\n\n\t/* Resize the terminal buffer */\n\tterm_width  = window_width  / char_width;\n\tterm_height = window_height / char_height;\n\n\tif (term_width == old_width && term_height == old_height) {\n\t\tmemset(term_display, 0xFF, sizeof(term_cell_t) * term_width * term_height);\n\t\tdraw_fill(ctx, rgba(0,0,0, TERM_DEFAULT_OPAC));\n\t\trender_decors();\n\t\tmaybe_flip_display(1);\n\t\treturn;\n\t}\n\n\tif (term_buffer) {\n\t\tterm_cell_t * new_a = copy_terminal(old_width, old_height, term_buffer_a);\n\t\tterm_cell_t * new_b = copy_terminal(old_width, old_height, term_buffer_b);\n\t\tfree(term_buffer_a);\n\t\tterm_buffer_a = new_a;\n\t\tfree(term_buffer_b);\n\t\tterm_buffer_b = new_b;\n\t\tif (active_buffer == 0) {\n\t\t\tterm_buffer = new_a;\n\t\t} else {\n\t\t\tterm_buffer = new_b;\n\t\t}\n\t} else {\n\t\tterm_buffer_a = malloc(sizeof(term_cell_t) * term_width * term_height);\n\t\tmemset(term_buffer_a, 0x0, sizeof(term_cell_t) * term_width * term_height);\n\n\t\tterm_buffer_b = malloc(sizeof(term_cell_t) * term_width * term_height);\n\t\tmemset(term_buffer_b, 0x0, sizeof(term_cell_t) * term_width * term_height);\n\n\t\tterm_buffer = term_buffer_a;\n\t}\n\n\tterm_mirror = realloc(term_mirror, sizeof(term_cell_t) * term_width * term_height);\n\tmemcpy(term_mirror, term_buffer, sizeof(term_cell_t) * term_width * term_height);\n\n\tterm_display = realloc(term_display, sizeof(term_cell_t) * term_width * term_height);\n\tmemset(term_display, 0xFF, sizeof(term_cell_t) * term_width * term_height);\n\n\t/* Reset the ANSI library, ensuring we keep certain values */\n\tint old_mouse_state = 0;\n\tif (ansi_state) old_mouse_state = ansi_state->mouse_on;\n\tansi_state = ansi_init(ansi_state, term_width, term_height, &term_callbacks);\n\tansi_state->mouse_on = old_mouse_state;\n\n\t/* Send window size change ioctl */\n\tstruct winsize w;\n\tw.ws_row = term_height;\n\tw.ws_col = term_width;\n\tw.ws_xpixel = term_width * char_width;\n\tw.ws_ypixel = term_height * char_height;\n\tioctl(fd_master, TIOCSWINSZ, &w);\n\n\t/* Redraw the window */\n\tdraw_fill(ctx, rgba(0,0,0, TERM_DEFAULT_OPAC));\n\trender_decors();\n\tterm_redraw_all();\n}\n\nstatic void update_bounds(void) {\n\tif (!_no_frame) {\n\t\tstruct decor_bounds bounds;\n\t\tdecor_get_bounds(window, &bounds);\n\n\t\tdecor_left_width = bounds.left_width;\n\t\tdecor_top_height = bounds.top_height;\n\t\tdecor_right_width = bounds.right_width;\n\t\tdecor_bottom_height = bounds.bottom_height;\n\t\tdecor_width = bounds.width;\n\t\tdecor_height = bounds.height;\n\t\tmenu_bar_height = 24;\n\t} else {\n\t\tdecor_left_width = 0;\n\t\tdecor_top_height = 0;\n\t\tdecor_right_width = 0;\n\t\tdecor_bottom_height = 0;\n\t\tdecor_width = 0;\n\t\tdecor_height = 0;\n\t\tmenu_bar_height = 0;\n\t}\n}\n\n/* Handle window resize event. */\nstatic void resize_finish(int width, int height) {\n\tstatic int resize_attempts = 0;\n\n\t/* Calculate window size */\n\tupdate_bounds();\n\tint extra_x = decor_width;\n\tint extra_y = decor_height + menu_bar_height;\n\n\tint t_window_width  = width  - extra_x;\n\tint t_window_height = height - extra_y;\n\n\t/* Prevent the terminal from becoming too small. */\n\tif (t_window_width < char_width * 20 || t_window_height < char_height * 10) {\n\t\tresize_attempts++;\n\t\tint n_width  = extra_x + max(char_width * 20, t_window_width);\n\t\tint n_height = extra_y + max(char_height * 10, t_window_height);\n\t\tyutani_window_resize_offer(yctx, window, n_width, n_height);\n\t\treturn;\n\t}\n\n\t/* If requested, ensure the terminal resizes to a fixed size based on the cell size. */\n\tif (!_free_size && ((t_window_width % char_width != 0 || t_window_height % char_height != 0) && resize_attempts < 3)) {\n\t\tresize_attempts++;\n\t\tint n_width  = extra_x + t_window_width  - (t_window_width  % char_width);\n\t\tint n_height = extra_y + t_window_height - (t_window_height % char_height);\n\t\tyutani_window_resize_offer(yctx, window, n_width, n_height);\n\t\treturn;\n\t}\n\n\tresize_attempts = 0;\n\n\t/* Accept new window size */\n\tyutani_window_resize_accept(yctx, window, width, height);\n\twindow_width  = window->width  - extra_x;\n\twindow_height = window->height - extra_y;\n\n\t/* Reinitialize the graphics library */\n\treinit_graphics_yutani(ctx, window);\n\n\t/* Reinitialize the terminal buffer and ANSI library */\n\treinit();\n\tmaybe_flip_display(1);\n\n\t/* We are done resizing. */\n\tyutani_window_resize_done(yctx, window);\n\tyutani_flip(yctx, window);\n}\n\n/* Insert a mouse event sequence into the PTY */\nstatic void mouse_event(int button, int x, int y) {\n\tif (ansi_state->mouse_on & TERMEMU_MOUSE_SGR) {\n\t\tchar buf[100];\n\t\tsprintf(buf,\"\\033[<%d;%d;%d%c\", button == 3 ? 0 : button, x+1, y+1, button == 3 ? 'm' : 'M');\n\t\thandle_input_s(buf);\n\t} else {\n\t\tchar buf[7];\n\t\tsprintf(buf, \"\\033[M%c%c%c\", button + 32, x + 33, y + 33);\n\t\thandle_input_s(buf);\n\t}\n}\n\n/* Handle Yutani messages */\nstatic void * handle_incoming(void) {\n\n\tstatic uint64_t last_click = 0;\n\n\tyutani_msg_t * m = yutani_poll(yctx);\n\twhile (m) {\n\t\tif (menu_process_event(yctx, m)) {\n\t\t\trender_decors();\n\t\t}\n\t\tswitch (m->type) {\n\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\tint ret = (ke->event.action == KEY_ACTION_DOWN) && (ke->event.key);\n\t\t\t\t\tif (ke->wid == window->wid) key_event(ret, &ke->event);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\tif (win == window) {\n\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\trender_decors();\n\t\t\t\t\t\tdraw_cursor();\n\t\t\t\t\t\tmaybe_flip_display(1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_close * wc = (void*)m->data;\n\t\t\t\t\tif (wc->wid == window->wid) {\n\t\t\t\t\t\tkill(child_pid, SIGKILL);\n\t\t\t\t\t\texit_application = 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t{\n\t\t\t\t\tkill(child_pid, SIGKILL);\n\t\t\t\t\texit_application = 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_CLIPBOARD:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_clipboard * cb = (void *)m->data;\n\t\t\t\t\tif (selection_text) {\n\t\t\t\t\t\tfree(selection_text);\n\t\t\t\t\t}\n\t\t\t\t\tif (*cb->content == '\\002') {\n\t\t\t\t\t\tint size = atoi(&cb->content[2]);\n\t\t\t\t\t\tFILE * clipboard = yutani_open_clipboard(yctx);\n\t\t\t\t\t\tselection_text = malloc(size + 1);\n\t\t\t\t\t\tfread(selection_text, 1, size, clipboard);\n\t\t\t\t\t\tselection_text[size] = '\\0';\n\t\t\t\t\t\tfclose(clipboard);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tselection_text = malloc(cb->size+1);\n\t\t\t\t\t\tmemcpy(selection_text, cb->content, cb->size);\n\t\t\t\t\t\tselection_text[cb->size] = '\\0';\n\t\t\t\t\t}\n\t\t\t\t\tif (ansi_state->paste_mode) {\n\t\t\t\t\t\thandle_input_s(\"\\033[200~\");\n\t\t\t\t\t\thandle_input_s(selection_text);\n\t\t\t\t\t\thandle_input_s(\"\\033[201~\");\n\t\t\t\t\t} else {\n\t\t\t\t\t\thandle_input_s(selection_text);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\tif (me->wid != window->wid) break;\n\t\t\t\t\tif (!_no_frame) {\n\t\t\t\t\t\tint decor_response = decor_handle_event(yctx, m);\n\n\t\t\t\t\t\tswitch (decor_response) {\n\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\tkill(child_pid, SIGKILL);\n\t\t\t\t\t\t\t\texit_application = 1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tmenu_bar_mouse_event(yctx, window, &terminal_menu_bar, me, me->new_x, me->new_y);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (me->new_x < 0 || me->new_y < 0 ||\n\t\t\t\t\t\t(!_no_frame && (me->new_x >= (int)window_width + (int)decor_width ||\n\t\t\t\t\t\t\tme->new_y < (int)decor_top_height+menu_bar_height ||\n\t\t\t\t\t\t\tme->new_y >= (int)(window_height + decor_top_height+menu_bar_height) ||\n\t\t\t\t\t\t\tme->new_x < (int)decor_left_width ||\n\t\t\t\t\t\t\tme->new_x >= (int)(window_width + decor_left_width))) ||\n\t\t\t\t\t\t(_no_frame && (me->new_x >= (int)window_width || me->new_y >= (int)window_height))) {\n\t\t\t\t\t\tif (window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) {\n\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!(ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE)) {\n\t\t\t\t\t\tif (window->mouse_state == YUTANI_CURSOR_TYPE_RESET) {\n\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_IBEAM);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) {\n\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tint new_x = me->new_x;\n\t\t\t\t\tint new_y = me->new_y;\n\t\t\t\t\tif (!_no_frame) {\n\t\t\t\t\t\tnew_x -= decor_left_width;\n\t\t\t\t\t\tnew_y -= decor_top_height+menu_bar_height;\n\t\t\t\t\t}\n\t\t\t\t\t/* Convert from coordinate to cell positon */\n\t\t\t\t\tnew_x /= char_width;\n\t\t\t\t\tnew_y /= char_height;\n\n\t\t\t\t\tif (new_x < 0 || new_y < 0) break;\n\t\t\t\t\tif (new_x >= term_width || new_y >= term_height) break;\n\n\t\t\t\t\t/* Map Cursor Action */\n\t\t\t\t\tif ((ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE) && !(me->modifiers & YUTANI_KEY_MODIFIER_SHIFT)) {\n\n\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\tmouse_event(32+32, new_x, new_y);\n\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\tmouse_event(32+32+1, new_x, new_y);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (me->buttons != button_state) {\n\t\t\t\t\t\t\t/* Figure out what changed */\n\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_BUTTON_LEFT &&\n\t\t\t\t\t\t\t\t\t!(button_state & YUTANI_MOUSE_BUTTON_LEFT))\n\t\t\t\t\t\t\t\tmouse_event(0, new_x, new_y);\n\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_BUTTON_MIDDLE &&\n\t\t\t\t\t\t\t\t\t!(button_state & YUTANI_MOUSE_BUTTON_MIDDLE))\n\t\t\t\t\t\t\t\tmouse_event(1, new_x, new_y);\n\t\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT &&\n\t\t\t\t\t\t\t\t\t!(button_state & YUTANI_MOUSE_BUTTON_RIGHT))\n\t\t\t\t\t\t\t\tmouse_event(2, new_x, new_y);\n\t\t\t\t\t\t\tif (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT) &&\n\t\t\t\t\t\t\t\t\tbutton_state & YUTANI_MOUSE_BUTTON_LEFT)\n\t\t\t\t\t\t\t\tmouse_event(3, new_x, new_y);\n\t\t\t\t\t\t\tif (!(me->buttons & YUTANI_MOUSE_BUTTON_MIDDLE) &&\n\t\t\t\t\t\t\t\t\tbutton_state & YUTANI_MOUSE_BUTTON_MIDDLE)\n\t\t\t\t\t\t\t\tmouse_event(3, new_x, new_y);\n\t\t\t\t\t\t\tif (!(me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) &&\n\t\t\t\t\t\t\t\t\tbutton_state & YUTANI_MOUSE_BUTTON_RIGHT)\n\t\t\t\t\t\t\t\tmouse_event(3, new_x, new_y);\n\t\t\t\t\t\t\tlast_mouse_x = new_x;\n\t\t\t\t\t\t\tlast_mouse_y = new_y;\n\t\t\t\t\t\t\tbutton_state = me->buttons;\n\t\t\t\t\t\t} else if (ansi_state->mouse_on & TERMEMU_MOUSE_DRAG) {\n\t\t\t\t\t\t\t/* Report motion for pressed buttons */\n\t\t\t\t\t\t\tif (last_mouse_x == new_x && last_mouse_y == new_y) break;\n\t\t\t\t\t\t\tif (button_state & YUTANI_MOUSE_BUTTON_LEFT) mouse_event(32, new_x, new_y);\n\t\t\t\t\t\t\tif (button_state & YUTANI_MOUSE_BUTTON_MIDDLE) mouse_event(33, new_x, new_y);\n\t\t\t\t\t\t\tif (button_state & YUTANI_MOUSE_BUTTON_RIGHT) mouse_event(34, new_x, new_y);\n\t\t\t\t\t\t\tlast_mouse_x = new_x;\n\t\t\t\t\t\t\tlast_mouse_y = new_y;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\tredraw_scrollback();\n\t\t\t\t\t\t\tuint64_t now = get_ticks();\n\t\t\t\t\t\t\tif (now - last_click < 500000UL && (new_x == selection_start_x && new_y == selection_start_y)) {\n\t\t\t\t\t\t\t\t/* Double click */\n\t\t\t\t\t\t\t\twhile (selection_start_x > 0) {\n\t\t\t\t\t\t\t\t\tterm_cell_t * c = cell_at(selection_start_x-1, selection_start_y);\n\t\t\t\t\t\t\t\t\tif (!c || c->c == ' ' || !c->c) break;\n\t\t\t\t\t\t\t\t\tselection_start_x--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\twhile (selection_end_x < term_width - 1) {\n\t\t\t\t\t\t\t\t\tterm_cell_t * c = cell_at(selection_end_x+1, selection_end_y);\n\t\t\t\t\t\t\t\t\tif (!c || c->c == ' ' || !c->c) break;\n\t\t\t\t\t\t\t\t\tselection_end_x++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tselection = 1;\n\t\t\t\t\t\t\t\tif (_menu_copy) menu_update_enabled(_menu_copy, selection);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tlast_click = get_ticks();\n\t\t\t\t\t\t\t\tselection_start_x = new_x;\n\t\t\t\t\t\t\t\tselection_start_y = new_y;\n\t\t\t\t\t\t\t\tselection_end_x = new_x;\n\t\t\t\t\t\t\t\tselection_end_y = new_y;\n\t\t\t\t\t\t\t\tselection = 0;\n\t\t\t\t\t\t\t\tif (_menu_copy) menu_update_enabled(_menu_copy, selection);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tredraw_selection();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DRAG && me->buttons & YUTANI_MOUSE_BUTTON_LEFT ){\n\t\t\t\t\t\t\tmark_selection();\n\t\t\t\t\t\t\tselection_end_x = new_x;\n\t\t\t\t\t\t\tselection_end_y = new_y;\n\t\t\t\t\t\t\tselection = 1;\n\t\t\t\t\t\t\tif (_menu_copy) menu_update_enabled(_menu_copy, selection);\n\t\t\t\t\t\t\tflip_selection();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_RAISE) {\n\t\t\t\t\t\t\tif (me->new_x == me->old_x && me->new_y == me->old_y) {\n\t\t\t\t\t\t\t\tselection = 0;\n\t\t\t\t\t\t\t\tif (_menu_copy) menu_update_enabled(_menu_copy, selection);\n\t\t\t\t\t\t\t\tterm_redraw_all();\n\t\t\t\t\t\t\t\tredraw_scrollback();\n\t\t\t\t\t\t\t} /* else selection */\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->buttons & YUTANI_MOUSE_SCROLL_UP) {\n\t\t\t\t\t\t\tscroll_up(5);\n\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) {\n\t\t\t\t\t\t\tscroll_down(5);\n\t\t\t\t\t\t} else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {\n\t\t\t\t\t\t\tif (!menu_right_click->window) {\n\t\t\t\t\t\t\t\tmenu_show_at(menu_right_click, window, me->new_x, me->new_y);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t\tfree(m);\n\t\tm = yutani_poll_async(yctx);\n\t}\n\n\treturn NULL;\n}\n\n/*\n * Menu Actions\n */\n\n/* File > Exit */\nstatic void _menu_action_exit(struct MenuEntry * self) {\n\tkill(child_pid, SIGKILL);\n\texit_application = 1;\n}\n\nstatic void _menu_action_hide_borders(struct MenuEntry * self) {\n\t_no_frame = !(_no_frame);\n\tupdate_bounds();\n\twindow_width = window->width - decor_width;\n\twindow_height = window->height - (decor_height + menu_bar_height);\n\tmenu_update_toggle_state(_menu_toggle_borders_context, !_no_frame);\n\tmenu_update_toggle_state(_menu_toggle_borders_bar, !_no_frame);\n\t\n\treinit();\n}\n\nstatic struct MenuEntry * _menu_toggle_bitmap_context = NULL;\nstatic struct MenuEntry * _menu_toggle_bitmap_bar = NULL;\n\nstatic void _menu_action_toggle_tt(struct MenuEntry * self) {\n\t_use_aa = !(_use_aa);\n\tmenu_update_toggle_state(_menu_toggle_bitmap_context, !_use_aa);\n\tmenu_update_toggle_state(_menu_toggle_bitmap_bar, !_use_aa);\n\tmenu_update_enabled(_menu_set_zoom, _use_aa);\n\treinit();\n}\n\nstatic void _menu_action_toggle_free_size(struct MenuEntry * self) {\n\t_free_size = !(_free_size);\n\tmenu_update_toggle_state(self, !_free_size);\n}\n\nstatic void _menu_action_show_about(struct MenuEntry * self) {\n\tchar about_cmd[1024] = \"\\0\";\n\tstrcat(about_cmd, \"about \\\"About Terminal\\\" /usr/share/icons/48/utilities-terminal.png \\\"ToaruOS Terminal\\\" \\\"© 2013-2025 K. Lange\\n-\\nPart of ToaruOS, which is free software\\nreleased under the NCSA/University of Illinois\\nlicense.\\n-\\n%https://toaruos.org\\n%https://github.com/klange/toaruos\\\" \");\n\tchar coords[100];\n\tsprintf(coords, \"%d %d &\", (int)window->x + (int)window->width / 2, (int)window->y + (int)window->height / 2);\n\tstrcat(about_cmd, coords);\n\tsystem(about_cmd);\n\trender_decors();\n}\n\nstatic void _menu_action_show_help(struct MenuEntry * self) {\n\tsystem(\"help-browser terminal.trt &\");\n\trender_decors();\n}\n\nstatic void _menu_action_copy(struct MenuEntry * self) {\n\tcopy_selection();\n}\n\nstatic void _menu_action_paste(struct MenuEntry * self) {\n\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD);\n}\n\nstatic void update_scale_menu(void) {\n\tmenu_update_toggle_state(_menu_scale_075, font_scaling == 0.75);\n\tmenu_update_toggle_state(_menu_scale_100, font_scaling == 1.00);\n\tmenu_update_toggle_state(_menu_scale_150, font_scaling == 1.50);\n\tmenu_update_toggle_state(_menu_scale_200, font_scaling == 2.00);\n}\n\nstatic void _menu_action_set_scale(struct MenuEntry * self) {\n\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\tif (!_self->action) {\n\t\tscale_fonts  = 0;\n\t\tfont_scaling = 1.0;\n\t\tupdate_scale_menu();\n\t} else {\n\t\tscale_fonts  = 1;\n\t\tfont_scaling = atof(_self->action);\n\t\tupdate_scale_menu();\n\t}\n\treinit();\n}\n\nstatic void render_decors_callback(struct menu_bar * self) {\n\t(void)self;\n\trender_decors();\n}\n\n/**\n * Geometry argument follows this format:\n *    [@]WxH[+X,Y]\n *\n * If @ is present, W and H are in characters.\n * If + is present, X and Y are the left and top offset.\n */\nstatic void parse_geometry(char ** argv, char * str) {\n\n\tint in_chars = 0;\n\tif (*str == '@') {\n\t\tin_chars = 1;\n\t\tstr++;\n\t}\n\n\t/* Split on 'x', which is required. */\n\tchar * c = strstr(str, \"x\");\n\tif (!c) return; /* Ignore invalid arg */\n\t*c = '\\0';\n\tc++;\n\n\t/* Find optional + that starts position */\n\tchar * plus = strstr(c, \"+\");\n\tif (plus) {\n\t\t*plus = '\\0';\n\t\tplus++;\n\t}\n\n\t/* Parse size */\n\twindow_width = atoi(str) * (in_chars ? char_width : 1);\n\twindow_height = atoi(c) * (in_chars ? char_height : 1);\n\n\tif (plus) {\n\t\t/* If there was a plus, let's look for a comma */\n\t\tchar * comma = strstr(plus, \",\");\n\t\tif (!comma) return; /* Skip invalid position */\n\t\t*comma = '\\0';\n\t\tcomma++;\n\n\t\twindow_position_set = 1;\n\t\twindow_left = atoi(plus);\n\t\twindow_top  = atoi(comma);\n\t}\n}\n\nint main(int argc, char ** argv) {\n\n\tint _flags = 0;\n\twindow_width  = char_width * 80;\n\twindow_height = char_height * 24;\n\n\tstatic struct option long_opts[] = {\n\t\t{\"fullscreen\", no_argument,       0, 'F'},\n\t\t{\"bitmap\",     no_argument,       0, 'b'},\n\t\t{\"scale\",      required_argument, 0, 's'},\n\t\t{\"help\",       no_argument,       0, 'h'},\n\t\t{\"grid\",       no_argument,       0, 'x'},\n\t\t{\"no-frame\",   no_argument,       0, 'n'},\n\t\t{\"geometry\",   required_argument, 0, 'g'},\n\t\t{\"blurred\",    no_argument,       0, 'B'},\n\t\t{\"scrollback\", required_argument, 0, 'S'},\n\t\t{0,0,0,0}\n\t};\n\n\t/* Read some arguments */\n\tint index, c;\n\twhile ((c = getopt_long(argc, argv, \"bhxnFls:g:BS:\", long_opts, &index)) != -1) {\n\t\tif (!c) {\n\t\t\tif (long_opts[index].flag == 0) {\n\t\t\t\tc = long_opts[index].val;\n\t\t\t}\n\t\t}\n\t\tswitch (c) {\n\t\t\tcase 'x':\n\t\t\t\t_free_size = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\t_no_frame = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'F':\n\t\t\t\t_fullscreen = 1;\n\t\t\t\t_no_frame = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'b':\n\t\t\t\t_use_aa = 0;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tusage(argv);\n\t\t\t\treturn 0;\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tscale_fonts = 1;\n\t\t\t\tfont_scaling = atof(optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\tparse_geometry(argv,optarg);\n\t\t\t\tbreak;\n\t\t\tcase 'B':\n\t\t\t\t_flags = YUTANI_WINDOW_FLAG_BLUR_BEHIND;\n\t\t\t\tbreak;\n\t\t\tcase 'S':\n\t\t\t\tmax_scrollback = strtoull(optarg,NULL,10);\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Initialize the windowing library */\n\tyctx = yutani_init();\n\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\t_tt_font_normal       = tt_font_from_shm(\"monospace\");\n\t_tt_font_bold         = tt_font_from_shm(\"monospace.bold\");\n\t_tt_font_oblique      = tt_font_from_shm(\"monospace.italic\");\n\t_tt_font_bold_oblique = tt_font_from_shm(\"monospace.bolditalic\");\n\t_tt_font_fallback     = _tt_font_normal;\n\t_tt_font_japanese     = tt_font_from_file(\"/usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf\"); /* Might not be present */\n\n\t/* Full screen mode forces window size to be that the display server */\n\tif (_fullscreen) {\n\t\twindow_width = yctx->display_width;\n\t\twindow_height = yctx->display_height;\n\t}\n\n\tif (_no_frame) {\n\t\twindow = yutani_window_create_flags(yctx, window_width, window_height, YUTANI_WINDOW_FLAG_NO_ANIMATION);\n\t} else {\n\t\tinit_decorations();\n\t\tstruct decor_bounds bounds;\n\t\tdecor_get_bounds(NULL, &bounds);\n\t\twindow = yutani_window_create_flags(yctx, window_width + bounds.width, window_height + bounds.height + menu_bar_height, _flags);\n\t\tyutani_window_update_shape(yctx, window, 20);\n\t}\n\n\tif (_fullscreen) {\n\t\t/* If fullscreen, assume we're always focused and put us on the bottom. */\n\t\tyutani_set_stack(yctx, window, YUTANI_ZORDER_BOTTOM);\n\t\twindow->focused = 1;\n\t} else {\n\t\twindow->focused = 0;\n\t}\n\n\tupdate_bounds();\n\n\t/* Set up menus */\n\tterminal_menu_bar.entries = terminal_menu_entries;\n\tterminal_menu_bar.redraw_callback = render_decors_callback;\n\n\t_menu_exit = menu_create_normal(\"exit\",\"exit\",\"Exit\", _menu_action_exit);\n\t_menu_copy = menu_create_normal(NULL, NULL, \"Copy\", _menu_action_copy);\n\t_menu_paste = menu_create_normal(NULL, NULL, \"Paste\", _menu_action_paste);\n\n\tmenu_update_enabled(_menu_copy, selection);\n\n\tmenu_right_click = menu_create();\n\tmenu_insert(menu_right_click, _menu_copy);\n\tmenu_insert(menu_right_click, _menu_paste);\n\tmenu_insert(menu_right_click, menu_create_separator());\n\tif (!_fullscreen) {\n\t\t_menu_toggle_borders_context = menu_create_toggle(NULL, \"Show borders\", !_no_frame, _menu_action_hide_borders);\n\t\tmenu_insert(menu_right_click, _menu_toggle_borders_context);\n\t}\n\t_menu_toggle_bitmap_context = menu_create_toggle(NULL, \"Bitmap font\", !_use_aa, _menu_action_toggle_tt);\n\tmenu_insert(menu_right_click, _menu_toggle_bitmap_context);\n\tmenu_insert(menu_right_click, menu_create_separator());\n\tmenu_insert(menu_right_click, _menu_exit);\n\n\t/* Menu Bar menus */\n\tterminal_menu_bar.set = menu_set_create();\n\tstruct MenuList * m;\n\tm = menu_create(); /* File */\n\tmenu_insert(m, _menu_exit);\n\tmenu_set_insert(terminal_menu_bar.set, \"file\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, _menu_copy);\n\tmenu_insert(m, _menu_paste);\n\tmenu_set_insert(terminal_menu_bar.set, \"edit\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, (_menu_scale_075 = menu_create_toggle(\"0.75\", \"75%\", 0, _menu_action_set_scale)));\n\tmenu_insert(m, (_menu_scale_100 = menu_create_toggle(NULL,  \"100%\", 0, _menu_action_set_scale)));\n\tmenu_insert(m, (_menu_scale_150 = menu_create_toggle(\"1.5\", \"150%\", 0, _menu_action_set_scale)));\n\tmenu_insert(m, (_menu_scale_200 = menu_create_toggle(\"2.0\", \"200%\", 0, _menu_action_set_scale)));\n\tupdate_scale_menu();\n\tmenu_set_insert(terminal_menu_bar.set, \"zoom\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(NULL, NULL, \"View stats\", _menu_action_cache_stats));\n\tmenu_insert(m, menu_create_normal(NULL, NULL, \"Clear cache\", _menu_action_clear_cache));\n\tmenu_set_insert(terminal_menu_bar.set, \"cache\", m);\n\n\tm = menu_create();\n\t_menu_toggle_borders_bar = menu_create_toggle(NULL, \"Show borders\", !_no_frame, _menu_action_hide_borders);\n\tmenu_insert(m, _menu_toggle_borders_bar);\n\tmenu_insert(m, (_menu_set_zoom = menu_create_submenu(NULL,\"zoom\",\"Set zoom...\")));\n\tmenu_update_enabled(_menu_set_zoom, _use_aa);\n\t_menu_toggle_bitmap_bar = menu_create_toggle(NULL, \"Bitmap font\", !_use_aa, _menu_action_toggle_tt);\n\tmenu_insert(m, _menu_toggle_bitmap_bar);\n\tmenu_insert(m, menu_create_toggle(NULL, \"Snap to Cell Size\", !_free_size, _menu_action_toggle_free_size));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(NULL, NULL, \"Redraw\", _menu_action_redraw));\n\tmenu_insert(m, menu_create_submenu(NULL,\"cache\",\"Glyph cache...\"));\n\tmenu_set_insert(terminal_menu_bar.set, \"view\", m);\n\n\tm = menu_create();\n\tmenu_insert(m, menu_create_normal(\"help\",\"help\",\"Contents\", _menu_action_show_help));\n\tmenu_insert(m, menu_create_separator());\n\tmenu_insert(m, menu_create_normal(\"star\",\"star\",\"About Terminal\", _menu_action_show_about));\n\tmenu_set_insert(terminal_menu_bar.set, \"help\", m);\n\n\tscrollback_list = list_create();\n\timages_list = list_create();\n\n\t/* Initialize the graphics context */\n\tctx = init_graphics_yutani_double_buffer(window);\n\n\t/* Clear to black */\n\tdraw_fill(ctx, rgba(0,0,0,0));\n\n\tif (window_position_set) {\n\t\t/* Move to requested position */\n\t\tyutani_window_move(yctx, window, window_left, window_top);\n\t} else {\n\t\t/* Move window to screen center */\n\t\tyutani_window_move(yctx, window, yctx->display_width / 2 - window->width / 2, yctx->display_height / 2 - window->height / 2);\n\t}\n\n\t/* Open a PTY */\n\topenpty(&fd_master, &fd_slave, NULL, NULL, NULL);\n\tterminal = fdopen(fd_slave, \"w\");\n\n\t/* Initialize the terminal buffer and ANSI library for the first time. */\n\treinit();\n\n\t/* Run thread to handle asynchronous writes to the tty */\n\tpthread_t input_buffer_thread;\n\tpipe(input_buffer_semaphore);\n\tinput_buffer_queue = list_create();\n\tpthread_create(&input_buffer_thread, NULL, handle_input_writing, NULL);\n\n\t/* Make sure we're not passing anything to stdin on the child */\n\tfflush(stdin);\n\n\t/* Fork off child */\n\tchild_pid = fork();\n\n\tif (!child_pid) {\n\t\tsetsid();\n\t\t/* Prepare stdin/out/err */\n\t\tdup2(fd_slave, 0);\n\t\tdup2(fd_slave, 1);\n\t\tdup2(fd_slave, 2);\n\n\t\tioctl(STDIN_FILENO, TIOCSCTTY, &(int){1});\n\t\ttcsetpgrp(STDIN_FILENO, getpid());\n\n\t\t/* Set the TERM environment variable. */\n\t\tputenv(\"TERM=toaru\");\n\n\t\t/* Execute requested initial process */\n\t\tif (argv[optind] != NULL) {\n\t\t\t/* Run something specified by the terminal startup */\n\t\t\texecvp(argv[optind], &argv[optind]);\n\t\t\tfprintf(stderr, \"Failed to launch requested startup application.\\n\");\n\t\t} else {\n\t\t\t/* Run the user's shell */\n\t\t\tchar * shell = getenv(\"SHELL\");\n\t\t\tif (!shell) shell = \"/bin/sh\"; /* fallback */\n\t\t\tchar * tokens[] = {shell,NULL};\n\t\t\texecvp(tokens[0], tokens);\n\t\t\texit(1);\n\t\t}\n\n\t\t/* Failed to start */\n\t\texit_application = 1;\n\t\treturn 1;\n\t} else {\n\n\t\t/* Set up fswait to check Yutani and the PTY master */\n\t\tint fds[2] = {fileno(yctx->sock), fd_master};\n\n\t\t/* PTY read buffer */\n\t\tunsigned char buf[4096];\n\t\tint next_wait = 200;\n\n\t\twhile (!exit_application) {\n\n\t\t\t/* Wait for something to happen. */\n\t\t\tint res[] = {0,0};\n\t\t\tfswait3(2,fds,next_wait,res);\n\n\t\t\t/* Check if the child application has closed. */\n\t\t\tcheck_for_exit();\n\t\t\tmaybe_flip_cursor();\n\n\t\t\tint force_flip = (!res[1] && (next_wait == 10));\n\n\t\t\tif (res[1]) {\n\t\t\t\t/* Read from PTY */\n\t\t\t\tssize_t r = read(fd_master, buf, 4096);\n\t\t\t\tfor (ssize_t i = 0; i < r; ++i) {\n\t\t\t\t\tansi_put(ansi_state, buf[i]);\n\t\t\t\t}\n\t\t\t\tnext_wait = 10;\n\t\t\t} else {\n\t\t\t\tnext_wait = 200;\n\t\t\t}\n\t\t\tif (res[0]) {\n\t\t\t\t/* Handle Yutani events. */\n\t\t\t\thandle_incoming();\n\t\t\t}\n\t\t\tmaybe_flip_display(force_flip);\n\t\t}\n\t}\n\n\tclose(input_buffer_semaphore[1]);\n\n\t/* Windows will close automatically on exit. */\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-badwrite.c",
    "content": "/**\n * @brief Test tool for examining a bug that was crashing the audio subsystem.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n\nint main(int argc, char * argv[]) {\n\tint fd = open(\"/dev/dsp\",O_WRONLY);\n\tif (fd < 0) {\n\t\tfprintf(stderr, \"failed to open dsp\\n\");\n\t\treturn 1;\n\t}\n\treturn write(fd, &fd, -1);\n}\n"
  },
  {
    "path": "apps/test-conf.c",
    "content": "/**\n * @brief Test tool for the INI confreader library.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n#include <toaru/confreader.h>\n\nint main(int argc, char * argv[]) {\n\tconfreader_t * conf = confreader_load(\"/etc/demo.conf\");\n\n\tfprintf(stderr, \"test 1\\n\");\n\tassert(confreader_get(conf, \"\", \"test\") != NULL);\n\tassert(!strcmp(confreader_get(conf, \"\", \"test\"),\"hello\"));\n\n\tfprintf(stderr, \"test 2\\n\");\n\tassert(!strcmp(confreader_get(conf,\"sec\",\"tion\"),\"test\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-fpclassify.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <math.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) return 1;\n\n\tunion {\n\t\tdouble asFloat;\n\t\tuint64_t asInt;\n\t} bits;\n\n\tif (!strcmp(argv[1], \"inf\")) {\n\t\tbits.asFloat = INFINITY;\n\t} else if (!strcmp(argv[1], \"nan\")) {\n\t\tbits.asFloat = NAN;\n\t} else {\n\t\tbits.asFloat = strtod(argv[1], NULL);\n\t}\n\n\tprintf(\"0x%016zx %d\\n\", bits.asInt, fpclassify(bits.asFloat));\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-ftruncate.c",
    "content": "/**\n * @brief Quick spot check of ftruncate\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2026 K. Lange\n */\n#include <sys/types.h>\n#include <fcntl.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\n\tint fd = open(\"test.file\", O_RDWR | O_CREAT);\n\n\tftruncate(fd, 7000);\n\n\tlseek(fd, 0, SEEK_SET);\n\n\tunsigned char buf[10000];\n\n\tssize_t resp = read(fd, buf, 8000);\n\n\tif (resp != 7000) {\n\t\tfprintf(stderr, \"expected resp to be 7000, was %zd\\n\", resp);\n\t\treturn 1;\n\t}\n\n\t/* Assert that 7000 bytes were empty */\n\tfor (size_t i = 0; i < 7000; ++i) {\n\t\tif (buf[i] != 0) {\n\t\t\tfprintf(stderr, \"Byte %zu was not 0 (%#x)\\n\", i, (unsigned int)buf[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\t/* Write into the whole thing */\n\tmemset(buf, 0xAA, 7000);\n\tpwrite(fd, buf, 7000, 0);\n\n\t/* Assert we can read back out */\n\tresp = pread(fd, buf, 8000, 0);\n\n\tif (resp != 7000) {\n\t\tfprintf(stderr, \"pread: expected resp to be 7000, was %zd\\n\", resp);\n\t\treturn 1;\n\t}\n\n\tfor (size_t i = 0; i < 7000; ++i) {\n\t\tif (buf[i] != 0xaa) {\n\t\t\tfprintf(stderr, \"Byte %zu was not 0xaa (%#x)\\n\", i, (unsigned int)buf[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tftruncate(fd, 2000);\n\n\tresp = pread(fd, buf, 8000, 0);\n\n\tif (resp != 2000) {\n\t\tfprintf(stderr, \"pread: expected resp to be 2000, was %zd\\n\", resp);\n\t\treturn 1;\n\t}\n\n\tfor (size_t i = 0; i < 2000; ++i) {\n\t\tif (buf[i] != 0xaa) {\n\t\t\tfprintf(stderr, \"Byte %zu was not 0xaa (%#x)\\n\", i, (unsigned int)buf[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tftruncate(fd, 6000);\n\n\tresp = pread(fd, buf, 8000, 0);\n\tif (resp != 6000) {\n\t\tfprintf(stderr, \"pread: expected resp to be 6000, was %zd\\n\", resp);\n\t\treturn 1;\n\t}\n\n\tfor (size_t i = 0; i < 2000; ++i) {\n\t\tif (buf[i] != 0xaa) {\n\t\t\tfprintf(stderr, \"Byte %zu was not 0xaa (%#x)\\n\", i, (unsigned int)buf[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tfor (size_t i = 2000; i < 6000; ++i) {\n\t\tif (buf[i] != 0) {\n\t\t\tfprintf(stderr, \"Byte %zu was not 0 (%#x)\\n\", i, (unsigned int)buf[i]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n\n}\n"
  },
  {
    "path": "apps/test-localtime.c",
    "content": "/**\n * @brief Test tool for the libc localtime() function.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <time.h>\n\nint main(int argc, char * argv[]) {\n\ttime_t i = 1576000000;\n\twhile (i < 2000000000) {\n\t\tstruct tm * t = localtime(&i);\n\n\t\tif (t->tm_sec < 0 || t->tm_sec >= 60) fprintf(stderr, \"Erroneous value at %ld: sec = %d\\n\", i, t->tm_sec);\n\t\tif (t->tm_min < 0 || t->tm_min >= 60) fprintf(stderr, \"Erroneous value at %ld: min = %d\\n\", i, t->tm_min);\n\t\tif (t->tm_hour < 0 || t->tm_hour >= 24) fprintf(stderr, \"Erroneous value at %ld (%s) hour = %d\\n\", i, asctime(t), t->tm_hour);\n\n\t\ti++;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-lock.c",
    "content": "/**\n * @brief Test tool for filesystem locks (O_EXCL)\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <string.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2 ){\n\t\tfprintf(stderr, \"usage: test-lock LOCKPATH\\n\");\n\t\treturn 1;\n\t}\n\tint fd = open(argv[1],O_RDWR|O_CREAT|O_EXCL);\n\tif (fd < 0) {\n\t\tif (errno == EEXIST) {\n\t\t\tfprintf(stderr, \"Lock is already held.\\n\");\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tfprintf(stderr, \"Some other error? %d = %s\\n\", errno, strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t} else {\n\t\tfprintf(stderr, \"I have the lock, the fd is %d.\\n\", fd);\n\t\tfprintf(stderr, \"Press Enter to release lock.\\n\");\n\t\twhile (!feof(stdin) && fgetc(stdin) != '\\n') {\n\t\t\t/* nothing */\n\t\t}\n\t\tclose(fd);\n\t\tunlink(argv[1]);\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "apps/test-loop.c",
    "content": "/**\n * @brief Spins in a loop. Useful for testing preemption, cpu usage.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\nint main(int argc, char * argv[]) {\n\twhile (1);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-printf.c",
    "content": "/**\n * @brief Test tool for libc printf formatters.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020-2021 K. Lange\n */\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\tprintf(\"%.3d\\n\", 42);\n\tprintf(\"%.10d\\n\", 12345);\n\tprintf(\"%.1d\\n\", 0);\n\tprintf(\"%.0d\\n\", 0);\n\tprintf(\"%.0d\\n\", 1);\n\tprintf(\"%.0d\\n\", 123);\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-ptrace-syscall.c",
    "content": "#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <getopt.h>\n#include <signal.h>\n#include <sys/ptrace.h>\n#include <sys/wait.h>\n#include <sys/signal.h>\n#include <sys/signal_defs.h>\n#include <sys/sysfunc.h>\n#include <sys/utsname.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <sys/uregs.h>\n#include <syscall_nums.h>\n\nint main(int argc, char * argv[]) {\n\tpid_t p = fork();\n\tif (!p) {\n\t\tif (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) {\n\t\t\tfprintf(stderr, \"%s: ptrace: %s\\n\", argv[0], strerror(errno));\n\t\t\treturn 1;\n\t\t}\n\t\texecvp(argv[optind], &argv[optind]);\n\t\treturn 1;\n\t}\n\n\twhile (1) {\n\t\tint status = 0;\n\t\tpid_t res = waitpid(p, &status, WSTOPPED);\n\n\t\tif (res < 0) {\n\t\t\tfprintf(stderr, \"%s: waitpid: %s\\n\", argv[0], strerror(errno));\n\t\t} else {\n\t\t\tif (WIFSTOPPED(status)) {\n\t\t\t\tif (WSTOPSIG(status) == SIGTRAP) {\n\t\t\t\t\tstruct URegs regs;\n\t\t\t\t\tptrace(PTRACE_GETREGS, p, NULL, &regs);\n\n\t\t\t\t\t/* Event type */\n\t\t\t\t\tint event = (status >> 16) & 0xFF;\n\t\t\t\t\tswitch (event) {\n\t\t\t\t\t\tcase PTRACE_EVENT_SYSCALL_ENTER:\n\t\t\t\t\t\t\tif (uregs_syscall_num(&regs) == SYS_SLEEP) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: sleep called, rewriting to yield\\n\", argv[0]);\n\t\t\t\t\t\t\t\turegs_syscall_num(&regs) = SYS_YIELD;\n\t\t\t\t\t\t\t\tptrace(PTRACE_SETREGS, p, NULL, &regs);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tptrace(PTRACE_CONT, p, NULL, NULL);\n\t\t\t\t} else {\n\t\t\t\t\tptrace(PTRACE_CONT, p, NULL, (void*)(uintptr_t)WSTOPSIG(status));\n\t\t\t\t}\n\t\t\t} else if (WIFSIGNALED(status)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (WIFEXITED(status)) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/test-sigsegv.c",
    "content": "/**\n * @brief Test tool for producing segmentation faults.\n *\n * Useful for testing the debugger.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdint.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc > 1) {\n\t\tfprintf(stderr, \"%s\\n\", (char*)(uintptr_t)strtoul(argv[1],NULL,0));\n\t} else {\n\t\t*(volatile int*)0x12345 = 42;\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-sigsuspend.c",
    "content": "#include <stdio.h>\n#include <signal.h>\n#include <errno.h>\n#include <string.h>\n#include <unistd.h>\n\nvoid handler(int sig) {\n\tfprintf(stderr, \"received %d\\n\", sig);\n}\n\nint main(int argc, char * argv[]) {\n\tsignal(SIGINT, handler);\n\tsignal(SIGWINCH, handler);\n\n\tsigset_t all, prev;\n\tsigfillset(&all);\n\tsigprocmask(SIG_SETMASK,&all,&prev);\n\n\tfprintf(stderr, \"Ignoring signals and pausing for three seconds.\\n\");\n\tsleep(3);\n\tfprintf(stderr, \"Sleep is over, calling sigsuspend.\\n\");\n\n\tint result = sigsuspend(&prev);\n\tfprintf(stderr, \"result = %d, errno = %s\\n\", result, strerror(errno));\n\n\tsigprocmask(SIG_SETMASK,&prev,NULL);\n\tfprintf(stderr, \"Restoring mask\\n\");\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/test-sigwait.c",
    "content": "#include <stdio.h>\n#include <signal.h>\n#include <errno.h>\n#include <string.h>\n\nvoid handler(int sig) {\n\tfprintf(stderr, \"received %d\\n\", sig);\n}\n\nint main(int argc, char * argv[]) {\n\tsignal(SIGINT, handler);\n\tsignal(SIGWINCH, handler);\n\n\tsigset_t mask;\n\tsigemptyset(&mask);\n\tsigaddset(&mask, SIGINT);\n\tsigprocmask(SIG_BLOCK, &mask, NULL);\n\n\twhile (1) {\n\t\tint sig = 0;\n\t\tint result = sigwait(&mask, &sig);\n\t\tfprintf(stderr, \"result = %d, sig = %d, errno = %s\\n\", result, sig, strerror(errno));\n\t}\n\n\treturn 0;\n}\n\n\n"
  },
  {
    "path": "apps/test-syscall-sysret.c",
    "content": "#include <unistd.h>\n#include <stdio.h>\n#include <syscall_nums.h>\n\nint main(int argc, char * argv[]){\n\tlong ret = 0;\n#ifdef __x86_64__\n\t__asm__ __volatile__(\"syscall\" : \"=a\"(ret) : \"a\"(SYS_WRITE), \"D\"(STDOUT_FILENO), \"S\"(\"Hello, world.\\n\"), \"d\"((long)14) : \"rcx\", \"r11\", \"memory\");\n\t__asm__ __volatile__(\"syscall\" : \"=a\"(ret) : \"a\"(SYS_WRITE), \"D\"(STDOUT_FILENO), \"S\"(\"Hello, world.\\n\"), \"d\"((long)14) : \"rcx\", \"r11\", \"memory\");\n\t__asm__ __volatile__(\"syscall\" : \"=a\"(ret) : \"a\"(SYS_WRITE), \"D\"(STDOUT_FILENO), \"S\"(\"Hello, world.\\n\"), \"d\"((long)14) : \"rcx\", \"r11\", \"memory\");\n#endif\n\n\treturn ret;\n}\n"
  },
  {
    "path": "apps/test-tls.c",
    "content": "/**\n * @brief Test tool for thread local storage.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020-2021 K. Lange\n */\n#include <stdio.h>\n#include <pthread.h>\n#include <sys/sysfunc.h>\n\n__thread volatile int myvalue;\n\nvoid * getaddressinthread(void * _unused) {\n\tfprintf(stderr, \"in thread before:\\n\");\n\tfprintf(stderr, \"&myvalue = %p\\n\", (void*)&myvalue);\n\tfprintf(stderr, \"myvalue  = %d\\n\", myvalue);\n\tmyvalue = 1234;\n\tfprintf(stderr, \"in thread after:\\n\");\n\tfprintf(stderr, \"&myvalue = %p\\n\", (void*)&myvalue);\n\tfprintf(stderr, \"myvalue  = %d\\n\", myvalue);\n\treturn NULL;\n}\n\nint main(int argc, char * argv[]) {\n\n\tmyvalue = 42;\n\n\tfprintf(stderr, \"main thread before:\\n\");\n\tfprintf(stderr, \"&myvalue = %p\\n\", (void*)&myvalue);\n\tfprintf(stderr, \"myvalue  = %d\\n\", myvalue);\n\n\tpthread_t mythread;\n\tpthread_create(&mythread, NULL, getaddressinthread, NULL);\n\n\tvoid * retval;\n\tpthread_join(mythread, &retval);\n\n\tfprintf(stderr, \"main thread after:\\n\");\n\tfprintf(stderr, \"&myvalue = %p\\n\", (void*)&myvalue);\n\tfprintf(stderr, \"myvalue  = %d\\n\", myvalue);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/test-tty-read.c",
    "content": "#include <string.h>\n#include <stdio.h>\n#include <termios.h>\n#include <unistd.h>\n#include <signal.h>\n\nstruct termios old;\nstruct termios new;\nvoid get_initial_termios(void) {\n\ttcgetattr(STDIN_FILENO, &old);\n}\n\nvoid on_sigusr1(int sig) {\n\tfprintf(stderr, \"received SIGUSR1\\n\");\n}\n\nint action = TCSADRAIN;\n\nvoid set_unbuffered(void) {\n\tmemcpy(&new,&old,sizeof(struct termios));\n\tnew.c_iflag &= (~ICRNL) & (~IXON);\n\tnew.c_lflag &= (~ICANON) & (~ECHO) & (~ISIG);\n#ifdef VLNEXT\n\tnew.c_cc[VLNEXT] = 0;\n#endif\n\tnew.c_cc[VMIN] = 2;\n\ttcsetattr(STDIN_FILENO, action, &new);\n}\n\nvoid set_buffered(void) {\n\ttcsetattr(STDIN_FILENO, action, &old);\n}\n\nint main(int argc, char * argv[]) {\n\n\tif (argc > 1 && !strcmp(argv[1], \"flush\")) {\n\t\taction = TCSAFLUSH;\n\t}\n\n\tget_initial_termios();\n\tfprintf(stderr, \"was VMIN=%d, VTIME=%d\\n\", old.c_cc[VMIN], old.c_cc[VTIME]);\n\tset_unbuffered();\n\tfprintf(stderr, \"now VMIN=%d, VTIME=%d\\n\", new.c_cc[VMIN], new.c_cc[VTIME]);\n\n\tsignal(SIGUSR1, on_sigusr1);\n\n\tchar buf[4096];\n\tssize_t r = read(STDIN_FILENO, buf, 4096);\n\n\tfprintf(stderr, \"read=%zd\\n\", r);\n\n\tset_buffered();\n}\n"
  },
  {
    "path": "apps/test-udp-recv.krk",
    "content": "#!/bin/kuroko\nimport socket\n\nlet s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n\nprint(\"Binding to :1234\")\ns.bind(('0.0.0.0',1234))\n\nprint(\"Waiting.\")\n\nwhile True:\n    print(s.recv(1024))\n"
  },
  {
    "path": "apps/toaru_logo.h",
    "content": "/**\n * @file apps/toaru_logo.sh\n * @brief Generated by GIMP, ToaruOS logo\n *\n * Used by sysinfo. Can be used by other things as well.\n * TODO: sysinfo should probably just load a bitmap?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\nstatic const struct {\n  unsigned int \t width;\n  unsigned int \t height;\n  unsigned int \t bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ \n  unsigned char\t pixel_data[23 * 23 * 4 + 1];\n} gimp_image = {\n  23, 23, 4,\n  \"\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\"\n  \"\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\"\n  \"\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\"\n  \"\\0\\260\\377\\377\\0\\260\\377\\377\\0\\0\\0\\0\\0\\260\\377\\377\\0\\260\\377\\377\\0\\253\\377\"\n  \"\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\"\n  \"\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\"\n  \"\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\"\n  \"\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\253\\377\\377\\0\\253\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\241\\377\\377\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\"\n  \"\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\"\n  \"\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\"\n  \"\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\\0\\241\\377\\377\\0\\0\\0\\0\\0\\241\\377\\377\"\n  \"\\0\\241\\377\\377\\0\\234\\377\\377\\0\\234\\377\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\"\n  \"\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\"\n  \"\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\"\n  \"\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\\377\\0\\0\\0\\0\\0\\234\\377\\377\\0\\234\\377\"\n  \"\\377\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\222\\377\\377\\0\\222\\377\\377\"\n  \"\\0\\0\\0\\0\\0\\222\\377\\377\\0\\222\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\215\\377\\377\\0\\215\\377\\377\\0\\0\\0\\0\"\n  \"\\0\\215\\377\\377\\0\\215\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\\202\"\n  \"\\377\\377\\0\\202\\377\\377\\0\\0\\0\\0\\0\\202\\377\\377\\0\\202\\377\\377\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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}\\377\\377\\0}\"\n  \"\\377\\377\\0\\0\\0\\0\\0}\\377\\377\\0}\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0s\\377\\377\\0s\\377\\377\\0\\0\\0\\0\\0s\\377\\377\\0s\\377\\377\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\\0n\\377\\377\\0n\"\n  \"\\377\\377\\0\\0\\0\\0\\0n\\377\\377\\0n\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0d\\377\\377\\0d\\377\\377\\0\\0\\0\\0\\0d\\377\\377\\0d\\377\\377\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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^\\377\\377\\0^\"\n  \"\\377\\377\\0\\0\\0\\0\\0^\\377\\377\\0^\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0T\\377\\377\\0T\\377\\377\\0\\0\\0\\0\\0T\\377\\377\\0T\\377\\377\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\\0O\\377\\377\\0O\"\n  \"\\377\\377\\0\\0\\0\\0\\0O\\377\\377\\0O\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\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\"\n  \"\\0\\0\\0\\0E\\377\\377\\0E\\377\\377\\0\\0\\0\\0\\0E\\377\\377\\0E\\377\\377\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0E\\377\\377\\0E\\377\\377\\0\\0\\0\\0\"\n  \"\\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@\\377\\377\"\n  \"\\0@\\377\\377\\0\\0\\0\\0\\0@\\377\\377\\0@\\377\\377\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\"\n  \"\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0@\\377\\377\\0@\\377\\377\",\n};\n\n"
  },
  {
    "path": "apps/toast.krk",
    "content": "#!/bin/kuroko\nimport kuroko\nimport os\n\ndef jsonFilter(c):\n    let mapping = {\n        '\"': '\\\\\"',\n        '\\\\': '\\\\\\\\',\n        '\\b': '\\\\b',\n        '\\f': '\\\\f',\n        '\\n': '\\\\n',\n        '\\r': '\\\\r',\n        '\\t': '\\\\t',\n    }\n    return mapping[c] if c in mapping else c\n\ndef dumpJson(o):\n    if isinstance(o,list):\n        return '[' + ','.join([dumpJson(i) for i in o]) + ']'\n    else if isinstance(o,int):\n        return repr(o)\n    else if isinstance(o,str):\n        return '\"' + ''.join([jsonFilter(c) for c in o]) + '\"'\n    else if isinstance(o,dict):\n        return '{' + ','.join([dumpJson(k) + ':' + dumpJson(v) for k, v in o.items()]) + '}'\n    else:\n        return dumpJson(str(o))\n\nlet msg = {}\nlet args = kuroko.argv[1:]\nlet sock = os.open(\"/dev/pex/toast\",os.O_WRONLY)\n\nif not sock:\n    print(\"No toast daemon.\")\n    return 1\n\nwhile args:\n    let arg = args.pop(0)\n    if arg == '--icon':\n        msg['icon'] = args.pop(0)\n    else if arg == '--duration':\n        msg['duration'] = int(args.pop(0))\n    else if arg.startswith('--'):\n        print(\"Unrecognized option:\",arg)\n    else:\n        msg['body'] = arg\n        print(\"Sending toast:\", dumpJson(msg),\"to\",sock)\n        os.write(sock, dumpJson(msg).encode())\n\n\n"
  },
  {
    "path": "apps/toastd.c",
    "content": "/**\n * @brief Toast notification daemon.\n * @file  apps/toastd.c\n *\n * Provides an endpoint for applications to post notifications\n * which are displayed in pop-up \"toasts\" in the upper-right\n * corner of the screen without stealing focus.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <sched.h>\n#include <time.h>\n#include <sys/fswait.h>\n#include <toaru/pex.h>\n#include <toaru/yutani.h>\n#include <toaru/markup_text.h>\n#include <toaru/graphics.h>\n#include <toaru/json.h>\n#include <toaru/list.h>\n\ntypedef struct JSON_Value JSON_Value;\n\nstatic yutani_t * yctx;\nstatic FILE * pex_endpoint = NULL;\nstatic sprite_t background_sprite;\nstatic list_t * windows = NULL;\nstatic list_t * garbage = NULL;\n\nstruct ToastNotification {\n\tyutani_window_t * window;\n\tstruct timespec created;\n\tint duration;\n};\n\n#define PAD_RIGHT 10\n#define PAD_TOP   48\n\nstatic void handle_msg(JSON_Value * msg) {\n\tif (msg->type != JSON_TYPE_OBJECT) {\n\t\tfprintf(stderr, \"expected an object, but json value was of type %d\\n\", msg->type);\n\t\treturn;\n\t}\n\n\tJSON_Value * msg_body = JSON_KEY(msg, \"body\");\n\n\tif (!msg_body) {\n\t\tfprintf(stderr, \"missing 'body'\\n\");\n\t\treturn;\n\t}\n\tif (msg_body->type != JSON_TYPE_STRING) {\n\t\tfprintf(stderr, \"'body' should have been a string, but got type %d instead\\n\", msg_body->type);\n\t\treturn;\n\t}\n\n\t/* At this point, we're going to show something, at least... */\n\tyutani_window_t * win = yutani_window_create_flags(yctx,\n\t\tbackground_sprite.width,\n\t\tbackground_sprite.height,\n\t\tYUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION);\n\n\tyutani_set_stack(yctx, win, YUTANI_ZORDER_OVERLAY);\n\n\t/* TODO: We need to figure out how to place this... */\n\tyutani_window_move(yctx, win, yctx->display_width - background_sprite.width - PAD_RIGHT, PAD_TOP + background_sprite.height * windows->length);\n\n\tstruct ToastNotification * notification = malloc(sizeof(struct ToastNotification));\n\tnotification->window = win;\n\tclock_gettime(CLOCK_MONOTONIC, &notification->created);\n\tlist_insert(windows, notification);\n\n\tJSON_Value * msg_duration = JSON_KEY(msg,\"duration\");\n\tif (msg_duration && msg_duration->type == JSON_TYPE_NUMBER) {\n\t\tnotification->duration = msg_duration->number;\n\t} else {\n\t\tnotification->duration = 5;\n\t}\n\n\t/* Establish the rendering context for this window, we'll only need it for a bit.\n\t * We won't even both double buffering... */\n\tgfx_context_t * ctx = init_graphics_yutani(win);\n\tdraw_fill(ctx, rgba(0,0,0,0));\n\tdraw_sprite(ctx, &background_sprite, 0, 0);\n\tint textOffset = 0;\n\n\t/* Does it have an icon? */\n\tJSON_Value * msg_icon = JSON_KEY(msg, \"icon\");\n\tif (msg_icon && msg_icon->type == JSON_TYPE_STRING) {\n\t\t/* Just ignore the icon if it's not a string... */\n\t\tsprite_t myIcon;\n\t\tif (!load_sprite(&myIcon, msg_icon->string)) {\n\t\t\t/* Is this a reasonable icon to display? */\n\t\t\tif (myIcon.width < 100) {\n\t\t\t\ttextOffset = myIcon.width + 8; /* Sounds like a fine padding... */\n\t\t\t\tdraw_sprite(ctx, &myIcon, 10, (background_sprite.height - myIcon.height) / 2);\n\t\t\t} else {\n\t\t\t\tint h = myIcon.height * 100 / myIcon.width;\n\t\t\t\ttextOffset = 100 + 8;\n\t\t\t\tdraw_sprite_scaled(ctx, &myIcon, 10, (background_sprite.height - h) / 2, 100, h);\n\t\t\t}\n\t\t\tfree(myIcon.bitmap);\n\t\t}\n\t}\n\n\tint height = markup_string_height(msg_body->string);\n\n\tmarkup_draw_string(ctx, 10 + textOffset, (ctx->height - height) / 2, msg_body->string, rgb(255,255,255));\n\tyutani_flip(yctx, win);\n}\n\nint main(int argc, char * argv[]) {\n\t/* Make sure we were actually expecting to be run... */\n\tif (argc < 2 || strcmp(argv[1],\"--really\")) {\n\t\tfprintf(stderr,\n\t\t\t\t\"%s: Toast notification daemon\\n\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\" Displays popup notifications from other\\n\"\n\t\t\t\t\" applications in the corner of the screen.\\n\"\n\t\t\t\t\" You probably don't want to run this directly - it is\\n\"\n\t\t\t\t\" started automatically by the session manager.\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\t/* Daemonize... */\n\tif (!fork()) {\n\t\t/* Connect to display server... */\n\t\tyctx = yutani_init();\n\t\tif (!yctx) {\n\t\t\tfprintf(stderr, \"%s: Failed to connect to compositor.\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t/* Open pex endpoint to receive notifications... */\n\t\tpex_endpoint = pex_bind(\"toast\");\n\t\tif (!pex_endpoint) {\n\t\t\tfprintf(stderr, \"%s: Failed to establish socket.\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t\t/* Set up our text rendering and sprite contexts... */\n\t\tmarkup_text_init();\n\t\tload_sprite(&background_sprite, \"/usr/share/ttk/toast/default.png\");\n\t\twindows = list_create();\n\t\tgarbage = list_create();\n\n\t\tint should_exit = 0;\n\t\twhile (!should_exit) {\n\t\t\tint fds[2] = {fileno(yctx->sock),fileno(pex_endpoint)};\n\t\t\tint index = fswait2(2,fds,windows->length ? 20 : -1);\n\t\t\tif (index == 0) {\n\t\t\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\t\t\twhile (m) {\n\t\t\t\t\tswitch (m->type) {\n\t\t\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tfree(m);\n\t\t\t\t\tm = yutani_poll_async(yctx);\n\t\t\t\t}\n\t\t\t} else if (index == 1) {\n\t\t\t\tpex_packet_t * p = calloc(PACKET_SIZE, 1);\n\t\t\t\tpex_listen(pex_endpoint, p);\n\n\t\t\t\tJSON_Value * msg = json_parse((char*)p->data);\n\n\t\t\t\tif (msg) {\n\t\t\t\t\thandle_msg(msg);\n\t\t\t\t\tjson_free(msg);\n\t\t\t\t}\n\n\t\t\t\tfree(p);\n\t\t\t}\n\n\t\t\tif (windows->length) {\n\t\t\t\t/* Check all the existing toasts for expired ones... */\n\t\t\t\tstruct timespec now;\n\t\t\t\tclock_gettime(CLOCK_MONOTONIC, &now);\n\n\t\t\t\tforeach(node, windows) {\n\t\t\t\t\tstruct ToastNotification * notification = node->value;\n\n\t\t\t\t\t/* How long has it been since this notification was created? */\n\t\t\t\t\tstruct timespec diff;\n\t\t\t\t\tdiff.tv_sec  = now.tv_sec  - notification->created.tv_sec;\n\t\t\t\t\tdiff.tv_nsec = now.tv_nsec - notification->created.tv_nsec;\n\t\t\t\t\tif (diff.tv_nsec < 0) { diff.tv_sec--; diff.tv_nsec += 1000000000L; }\n\n\t\t\t\t\tif (diff.tv_sec >= notification->duration) {\n\t\t\t\t\t\tif (notification->window) {\n\t\t\t\t\t\t\tyutani_close(yctx, notification->window);\n\t\t\t\t\t\t\tnotification->window = NULL;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (diff.tv_sec >= notification->duration + 1) {\n\t\t\t\t\t\t\tlist_insert(garbage, node);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Expunge garbage */\n\t\t\t\tif (garbage->length) {\n\t\t\t\t\twhile (garbage->length) {\n\t\t\t\t\t\tnode_t * n = list_pop(garbage);\n\t\t\t\t\t\tnode_t * node = n->value;\n\t\t\t\t\t\tfree(n);\n\t\t\t\t\t\tlist_delete(windows, node);\n\t\t\t\t\t\tfree(node->value);\n\t\t\t\t\t\tfree(node);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Figure out if we need to move anything */\n\t\t\t\tif (index == 2) {\n\t\t\t\t\tint index = 0;\n\t\t\t\t\tforeach(node, windows) {\n\t\t\t\t\t\tstruct ToastNotification * notification = node->value;\n\t\t\t\t\t\tif (notification->window && notification->window->y > PAD_TOP + background_sprite.height * index) {\n\t\t\t\t\t\t\tyutani_window_move(yctx, notification->window, notification->window->x, notification->window->y - 4);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tindex++;\n\t\t\t\t\t}\n\n\t\t\t\t\tsched_yield();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/toggle-abs-mouse.c",
    "content": "/**\n * @brief toggle-abs-mouse - Toggle mouse modes\n *\n * Set the mouse mode under VirtualBox, VMware, or QEMU to either\n * relative or absolute via ioctl to the relevant absolute mouse\n * device driver interface.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <string.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: argument (relative, absolute, get) expected\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint fd = open(\"/dev/absmouse\",O_WRONLY);\n\tif (fd < 0) {\n\t\t/* try vmmouse */\n\t\tfd = open(\"/dev/vmmouse\",O_WRONLY);\n\t\tif (fd < 0) {\n\t\t\tfprintf(stderr, \"%s: no valid mouse interface found.\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tint flag = 0;\n\tif (!strcmp(argv[1],\"relative\")) {\n\t\tflag = 1;\n\t}\n\tif (!strcmp(argv[1],\"absolute\")) {\n\t\tflag = 2;\n\t}\n\tif (!strcmp(argv[1],\"get\")) {\n\t\tflag = 3;\n\t}\n\n\tif (!flag) {\n\t\tfprintf(stderr, \"%s: invalid argument\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint result = ioctl(fd, flag, NULL);\n\n\tif (flag == 3) {\n\t\tif (result == 0) {\n\t\t\tfprintf(stdout, \"relative\\n\");\n\t\t} else {\n\t\t\tfprintf(stdout, \"absolute\\n\");\n\t\t}\n\t\treturn 0;\n\t}\n\n\treturn result;\n}\n"
  },
  {
    "path": "apps/top.c",
    "content": "/**\n * @brief Show processes sorted by resource usage.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <fcntl.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <pwd.h>\n#include <termios.h>\n#include <poll.h>\n#include <time.h>\n#include <signal.h>\n#include <sys/stat.h>\n#include <sys/ioctl.h>\n#include <sys/time.h>\n#include <sys/signal.h>\n\n#include <sys/sysfunc.h>\n#include <toaru/list.h>\n#include <toaru/hashmap.h>\n\n#define LINE_LEN 4096\n\nenum header_columns {\n\tCOLUMN_NONE,\n\tCOLUMN_PID,\n\tCOLUMN_TID,\n\tCOLUMN_USER,\n\tCOLUMN_VSZ,\n\tCOLUMN_SHM,\n\tCOLUMN_MEM,\n\tCOLUMN_CPUA,\n\tCOLUMN_CPU,\n\tCOLUMN_S\n};\n\nstatic hashmap_t * process_ents = NULL;\nstatic int cpu_count = 1;\nstatic int sort_column = COLUMN_CPU;\nstatic int show_help = 0;\n\nstatic const char * help_text[] = {\n\t\"q: quit\",\n\t\"w: switch sort column\",\n\t\"h: show this help text\",\n};\n\nenum {\n\tFORMATTER_DECIMAL,\n\tFORMATTER_PERCENT,\n\tFORMATTER_STRING\n};\n\nenum {\n\tSORT_ASC,\n\tSORT_DEC\n};\n\nstruct process {\n\tint uid, pid, tid, mem, vsz, shm, cpu, cpua;\n\tchar * user;\n\tchar * process;\n\tchar * command_line;\n\tchar * state;\n};\n\nstruct columns {\n\tconst char * title;\n\tintptr_t     member;\n\tint          formatter;\n\tint          width;\n\tint          sort_order;\n} ColumnDescriptions[] = {\n\t[COLUMN_NONE] = {\"\", 0, 0, 0, 0},\n\t[COLUMN_PID]  = {\"PID\",  offsetof(struct process, pid),  FORMATTER_DECIMAL, 0, SORT_ASC},\n\t[COLUMN_TID]  = {\"TID\",  offsetof(struct process, tid),  FORMATTER_DECIMAL, 0, SORT_ASC},\n\t[COLUMN_VSZ]  = {\"VSZ\",  offsetof(struct process, vsz),  FORMATTER_DECIMAL, 0, SORT_DEC},\n\t[COLUMN_SHM]  = {\"SHM\",  offsetof(struct process, shm),  FORMATTER_DECIMAL, 0, SORT_DEC},\n\t[COLUMN_MEM]  = {\"%MEM\", offsetof(struct process, mem),  FORMATTER_PERCENT, 0, SORT_DEC},\n\t[COLUMN_CPU]  = {\"%CPU\", offsetof(struct process, cpu),  FORMATTER_PERCENT, 0, SORT_DEC},\n\t[COLUMN_CPUA] = {\"CPUA\", offsetof(struct process, cpua), FORMATTER_PERCENT, 0, SORT_DEC},\n\t[COLUMN_USER] = {\"USER\", offsetof(struct process, user), FORMATTER_STRING,  0, SORT_ASC},\n\t[COLUMN_S]    = {\"S\",    offsetof(struct process, state),FORMATTER_STRING,  0, SORT_ASC},\n};\n\nstatic int columns[] = { COLUMN_PID, COLUMN_USER, COLUMN_VSZ, COLUMN_SHM, COLUMN_S, COLUMN_CPU, COLUMN_CPUA, COLUMN_MEM, COLUMN_NONE };\n\n/**\n * @brief Print a single column to stdout with the appropriate formatter.\n */\nstatic int print_column(struct process * proc, int column_id) {\n\tstruct columns * column = &ColumnDescriptions[column_id];\n\tswitch (column->formatter) {\n\t\tcase FORMATTER_DECIMAL: {\n\t\t\tint value = *(int*)((char *)proc + column->member);\n\t\t\treturn printf(\"%*d \", column->width, value);\n\t\t}\n\t\tcase FORMATTER_PERCENT: {\n\t\t\tint value = *(int*)((char *)proc + column->member);\n\t\t\tif (value >= 1000) {\n\t\t\t\treturn printf(\"%*d \", column->width, value / 10);\n\t\t\t} else {\n\t\t\t\treturn printf(\"%*d.%01d \", column->width - 2, value / 10, value % 10);\n\t\t\t}\n\t\t}\n\t\tcase FORMATTER_STRING: {\n\t\t\tchar * value = *(char**)((char *)proc + column->member);\n\t\t\treturn printf(\"%-*s \", column->width, value);\n\t\t}\n\t\tdefault: return 0;\n\t}\n}\n\n/**\n * @brief Calculate the size of a formatted column.\n */\nstatic int size_column(struct process * proc, int column_id) {\n\tchar garbage[100];\n\tstruct columns * column = &ColumnDescriptions[column_id];\n\tswitch (column->formatter) {\n\t\tcase FORMATTER_DECIMAL: {\n\t\t\tint value = *(int*)((char *)proc + column->member);\n\t\t\treturn snprintf(garbage, 100, \"%d\", value);\n\t\t}\n\t\tcase FORMATTER_PERCENT: {\n\t\t\tint value = *(int*)((char *)proc + column->member);\n\t\t\tif (value >= 1000) {\n\t\t\t\treturn 3;\n\t\t\t} else {\n\t\t\t\treturn snprintf(garbage, 100, \"%d.%01d\", value / 10, value % 10);\n\t\t\t}\n\t\t}\n\t\tcase FORMATTER_STRING: {\n\t\t\tchar * value = *(char**)((char *)proc + column->member);\n\t\t\treturn strlen(value);\n\t\t}\n\t\tdefault: return 0;\n\t}\n}\n\n/**\n * @brief Print the column headings.\n */\nvoid print_header(void) {\n\tprintf(\"\\033[44;30m\");\n\tfor (int * c = columns; *c; ++c) {\n\t\tif (*c == sort_column) printf(\"\\033[97m\");\n\t\tprintf(\"%*s \", ColumnDescriptions[*c].width, ColumnDescriptions[*c].title);\n\t\tif (*c == sort_column) printf(\"\\033[30m\");\n\t}\n\tif (sort_column == COLUMN_NONE) {\n\t\tprintf(\"\\033[1;97mCMD\\033[30m\");\n\t} else {\n\t\tprintf(\"CMD\");\n\t}\n\tprintf(\"\\033[K\\033[0m\\n\");\n}\n\n/**\n * @brief Reset column widths to the minimum required to fit their headings.\n */\nvoid reset_column_widths(void) {\n\tfor (size_t i = 0; i < sizeof(ColumnDescriptions) / sizeof(*ColumnDescriptions); ++i) {\n\t\tColumnDescriptions[i].width = strlen(ColumnDescriptions[i].title);\n\t}\n}\n\n/**\n * @brief Print one entry to stdout with the appropriate formatter.\n *\n * @p out Process entry to print.\n * @p width Total available screen width.\n */\nvoid print_entry(struct process * out, int width) {\n\tint used = 0;\n\tfor (int * c = columns; *c; ++c) {\n\t\tif (*c == sort_column) printf(\"\\033[1m\");\n\t\tused += print_column(out, *c);\n\t\tif (*c == sort_column) printf(\"\\033[0m\");\n\t}\n\tprintf(\"%.*s\\033[K\\n\", width - used, out->command_line ? out->command_line : out->process);\n}\n\n/**\n * @brief Given a process, expand any columns that need to be bigger to fit it.\n */\nvoid update_column_widths(struct process * out) {\n\tint len;\n\tfor (int * c = columns; *c; ++c) {\n\t\tif ((len = size_column(out, *c)) > ColumnDescriptions[*c].width) ColumnDescriptions[*c].width = len;\n\t}\n}\n\n/**\n * @brief Free resources used by a process entry.\n *\n * Frees any strings allocated for the process, as well\n * as the process struct itself.\n */\nvoid free_entry(struct process * out) {\n\tif (out->command_line) free(out->command_line);\n\tif (out->process) free(out->process);\n\tif (out->user) free(out->user);\n\tif (out->state) free(out->state);\n\tfree(out);\n}\n\n/**\n * @brief Given a UID, get the username.\n *\n * Always returns a string that must be freed. If the uid\n * could not be found in the passwd database, the uid itself\n * is formatted as a string for display.\n */\nchar * format_username(int uid) {\n\tstatic char tmp[100];\n\tstruct passwd * p = getpwuid(uid);\n\tif (p) {\n\t\tsnprintf(tmp, 100, \"%-8s\", p->pw_name);\n\t} else {\n\t\tsnprintf(tmp, 100, \"%-8d\", uid);\n\t}\n\tendpwent();\n\treturn strdup(tmp);\n}\n\n/**\n * @brief Find a process from its pid.\n *\n * Used for looking up the main thread's process entry when\n * collecting information for non-main threads, so we can\n * sum up CPU usage, which is reported in procfs per-thread.\n */\nstruct process * process_from_pid(pid_t pid) {\n\treturn hashmap_get(process_ents, (void*)(uintptr_t)pid);\n}\n\n/**\n * @brief Collect information for a process from its procfs entry.\n *\n * @p dent Directory entry from calling readdir on /proc.\n * @returns Process information that must be freed by the caller.\n */\nstruct process * process_entry(struct dirent *dent) {\n\tchar tmp[300];\n\tFILE * f;\n\tchar line[LINE_LEN];\n\n\tint pid = 0, uid = 0, tgid = 0, mem = 0, shm = 0, vsz = 0, cpu = 0, cpua = 0;\n\tchar name[100];\n\tchar state[10];\n\n\tsprintf(tmp, \"/proc/%s/status\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\n\tif (!f) {\n\t\treturn NULL;\n\t}\n\n\tline[0] = 0;\n\n\twhile (fgets(line, LINE_LEN, f) != NULL) {\n\t\tchar * n = strstr(line,\"\\n\");\n\t\tif (n) { *n = '\\0'; }\n\t\tchar * tab = strstr(line,\"\\t\");\n\t\tif (tab) {\n\t\t\t*tab = '\\0';\n\t\t\ttab++;\n\t\t}\n\t\tif (strstr(line, \"Pid:\") == line) {\n\t\t\tpid = atoi(tab);\n\t\t} else if (strstr(line, \"State:\") == line) {\n\t\t\tstrcpy(state, tab);\n\t\t} else if (strstr(line, \"Uid:\") == line) {\n\t\t\tuid = atoi(tab);\n\t\t} else if (strstr(line, \"Tgid:\") == line) {\n\t\t\ttgid = atoi(tab);\n\t\t} else if (strstr(line, \"Name:\") == line) {\n\t\t\tstrcpy(name, tab);\n\t\t} else if (strstr(line, \"VmSize:\") == line) {\n\t\t\tvsz = atoi(tab);\n\t\t} else if (strstr(line, \"RssShmem:\") == line) {\n\t\t\tshm = atoi(tab);\n\t\t} else if (strstr(line, \"MemPermille:\") == line) {\n\t\t\tmem = atoi(tab);\n\t\t} else if (strstr(line, \"CpuPermille:\") == line) {\n\t\t\tcpu = strtoul(tab, &tab, 10);\n\t\t\tcpua = cpu;\n\t\t\tcpua += strtoul(tab, &tab, 10);\n\t\t\tcpua += strtoul(tab, &tab, 10);\n\t\t\tcpua += strtoul(tab, &tab, 10);\n\t\t\tcpua /= 4;\n\t\t}\n\t}\n\n\tfclose(f);\n\n\tif (tgid != pid) {\n\t\t/* Add this thread's CPU usage to the parent */\n\t\tstruct process * parent = process_from_pid(tgid);\n\t\tif (parent) {\n\t\t\tparent->cpu += cpu;\n\t\t\tparent->cpua += cpua;\n\t\t}\n\t\treturn NULL;\n\t}\n\n\tstruct process * out = malloc(sizeof(struct process));\n\tout->uid = uid;\n\tout->pid = tgid;\n\tout->tid = pid;\n\tout->mem = mem;\n\tout->shm = shm;\n\tout->vsz = vsz;\n\tout->cpu = cpu;\n\tout->cpua = cpua;\n\tout->process = strdup(name);\n\tout->state = strdup(state);\n\tout->command_line = NULL;\n\tout->user = format_username(out->uid);\n\n\thashmap_set(process_ents, (void*)(uintptr_t)pid, out);\n\n\tsprintf(tmp, \"/proc/%s/cmdline\", dent->d_name);\n\tf = fopen(tmp, \"r\");\n\tchar foo[1024];\n\tint s = fread(foo, 1, 1024, f);\n\tif (s > 0) {\n\t\tout->command_line = malloc(s + 1);\n\t\tmemset(out->command_line, 0, s + 1);\n\t\tmemcpy(out->command_line, foo, s);\n\t\tfor (int i = 0; i < s; ++i) {\n\t\t\tif (out->command_line[i] == 30) {\n\t\t\t\tout->command_line[i] = ' ';\n\t\t\t}\n\t\t}\n\t}\n\tfclose(f);\n\n\tupdate_column_widths(out);\n\n\treturn out;\n}\n\n/**\n * @brief Sort an array of process struct pointers using the\n *        currently selected sort column.\n */\nstatic int sort_processes(const void * a, const void * b) {\n\tstruct process * left  = *(struct process **)a;\n\tstruct process * right = *(struct process **)b;\n\n\tstruct columns * column = &ColumnDescriptions[sort_column];\n\n\tif (sort_column == COLUMN_NONE) {\n\t\treturn strcmp(left->command_line, right->command_line);\n\t}\n\n\tswitch (column->formatter) {\n\t\tcase FORMATTER_DECIMAL:\n\t\tcase FORMATTER_PERCENT: {\n\t\t\tint a = *(int*)((char *)left + column->member);\n\t\t\tint b = *(int*)((char *)right + column->member);\n\t\t\treturn (column->sort_order == SORT_ASC) ? (a - b) : (b - a);\n\t\t}\n\t\tcase FORMATTER_STRING: {\n\t\t\tchar * a = *(char **)((char *)left + column->member);\n\t\t\tchar * b = *(char **)((char *)right + column->member);\n\t\t\treturn (column->sort_order == SORT_ASC) ? strcmp(a,b) : strcmp(b,a);\n\t\t}\n\t\tdefault: return 0;\n\t}\n}\n\n/**\n * @brief Collect memory usage information from /proc/meminfo\n *\n * @p total (out) Total memory available in KiB\n * @p used  (out) In-use memory in KiB\n */\nstatic void get_mem_info(int * total, int * used) {\n\tFILE * f = fopen(\"/proc/meminfo\", \"r\");\n\tif (!f) return;\n\tint free;\n\tchar buf[1024] = {0};\n\tfgets(buf, 1024, f);\n\n\tchar * a, * b;\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\t*total = atoi(a);\n\tfgets(buf, 1024, f);\n\ta = strchr(buf, ' ');\n\ta++;\n\tb = strchr(a, '\\n');\n\t*b = '\\0';\n\tfree = atoi(a);\n\t*used = *total - free;\n\n\tfclose(f);\n}\n\n/**\n * @brief Collect CPU usage information from /proc/idle\n *\n * @p cpus (out) Array of CPU usage in permilles.\n */\nstatic void get_cpu_info(int cpus[]) {\n\tFILE * f = fopen(\"/proc/idle\",\"r\");\n\tchar buf[4096];\n\tfread(buf, 4096, 1, f);\n\n\tchar * buffer = buf;\n\tfor (int i = 0; i < cpu_count; ++i) {\n\t\tchar * b = strchr(buffer, ':');\n\t\tb++;\n\t\tcpus[i] = 1000 - atoi(b);\n\t\tif (cpus[i] < 0) cpus[i] = 0;\n\t\tbuffer = strchr(b, '\\n');\n\t}\n\n\tfclose(f);\n}\n\n/**\n * @brief Obtain information on how much system memory is\n *        being used for tmpfs blocks.\n */\nstatic void get_tmpfs_info(size_t * size) {\n\tFILE * f = fopen(\"/proc/tmpfs\", \"r\");\n\tif (!f) return;\n\n\tchar buf[1024] = {0};\n\tfread(buf,1,1024,f);\n\tfclose(f);\n\n\t/* Should probably be looking for UsedBlocks: and advancing from there... */\n\tchar *b = strstr(buf, \":\");\n\tif (!b) return;\n\tb += 2;\n\n\t*size = strtoul(b,NULL,10) * 4; /* Expressed in pages, so * 4 for kilobytes */\n}\n\nstatic int fill_colors[] = {\n\t1, 3, 4, 5, 6\n};\n\n/**\n * @brief Display a progress-bar-style usage meter.\n *\n * @p title Label to apply to the meter, shown on left.\n * @p label Label to show inside of the meter, show on the right.\n * @p width Available width to display the meter in, including title and frame.\n * @p count Number of values to display.\n * @p filled Values to stack in the meter.\n * @p maximum Maximum value of the meter.\n */\nstatic void print_meter(const char * title, const char * label, int width, int count, int filled[], int maximum) {\n\tint available = width - strlen(title) - 4;\n\tint remaining = available;\n\tint fillSlots = 0;\n\n\t/* Count total fill slots */\n\tfor (int i = 0; i < count; ++i) {\n\t\tfilled[i] = filled[i] * available / maximum;\n\t\tif (filled[i] < 0) filled[i] = 0;\n\t\tif (filled[i] > remaining) filled[i] = remaining;\n\t\tfillSlots += filled[i];\n\t\tremaining = available - fillSlots;\n\t}\n\n\tint emptSlots = available - fillSlots;\n\n\tprintf(\"\\033[1m%s [\", title);\n\n\tchar * fill = malloc(available + 1);\n\tsize_t j = 0;\n\tfor (int i = 0; i < fillSlots; ++i, j++) fill[j] = '|';\n\tfor (int i = 0; i < emptSlots; ++i, j++) fill[j] = ' ';\n\n\tsize_t l = strlen(label);\n\tif (available > (int)l) {\n\t\tsprintf(fill + available - l, \"%s\", label);\n\t}\n\n\tj = 0;\n\n\tfor (int c = 0; c < count; ++c) {\n\t\tprintf(\"\\033[0;9%dm\", fill_colors[c % (sizeof(fill_colors) / sizeof(int))]);\n\t\tfor (int i = 0; i < filled[c]; ++i, j++) printf(\"%c\",fill[j]);\n\t}\n\tprintf(\"\\033[90m\");\n\tfor (int i = 0; i < emptSlots; ++i, j++) printf(\"%c\",fill[j]);\n\n\tprintf(\"\\033[0;1m]\\033[0m \");\n\tfree(fill);\n}\n\n/**\n * @brief Switch sorting to the next column.\n */\nstatic void next_sort_order(void) {\n\tsize_t column_count = sizeof(columns)/sizeof(*columns);\n\tfor (size_t i = 0; i < column_count; ++i) {\n\t\tif (columns[i] == sort_column) {\n\t\t\tsort_column = columns[(i + 1) % column_count];\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/**\n * @brief Switch sorting to the previous column.\n */\nstatic void prev_sort_order(void) {\n\tsize_t column_count = sizeof(columns)/sizeof(*columns);\n\tfor (size_t i = 0; i < column_count; ++i) {\n\t\tif (columns[i] == sort_column) {\n\t\t\tsort_column = columns[(i + column_count - 1) % column_count];\n\t\t\treturn;\n\t\t}\n\t}\n}\n\n/**\n * @brief Collect information on running processes.\n */\nstatic struct process ** read_processes(size_t * count) {\n\t/* Set minimum column widths to titles */\n\treset_column_widths();\n\n\t/* Read the entries in the directory */\n\tlist_t * ents_list = list_create();\n\tprocess_ents = hashmap_create_int(10);\n\n\t/* Scan /proc entries */\n\tDIR * dirp = opendir(\"/proc\");\n\tstruct dirent * dent = readdir(dirp);\n\twhile (dent != NULL) {\n\t\tif (dent->d_name[0] >= '0' && dent->d_name[0] <= '9') {\n\t\t\tstruct process * p = process_entry(dent);\n\t\t\tif (p) {\n\t\t\t\tlist_insert(ents_list, (void *)p);\n\t\t\t}\n\t\t}\n\n\t\tdent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n\n\thashmap_free(process_ents);\n\tfree(process_ents);\n\n\t/* Turn list into an array */\n\t*count = ents_list->length;\n\tstruct process ** processList = malloc(sizeof(struct process*) * *count);\n\tsize_t ent = 0;\n\twhile (ents_list->length) {\n\t\tnode_t * node = list_pop(ents_list);\n\t\tprocessList[ent] = node->value;\n\t\tfree(node);\n\t\tent++;\n\t}\n\tfree(ents_list);\n\n\t/* Sort processes with the current sort column */\n\tqsort(processList, *count, sizeof(struct process*), sort_processes);\n\n\treturn processList;\n}\n\n/**\n * @brief Gather system information and print one sample.\n */\nstatic int do_once(void) {\n\tsize_t count;\n\tstruct process ** processList = read_processes(&count);\n\n\t/* Gather total memory usage /proc/meminfo */\n\tint mem_total = 0, mem_used = 0;\n\tget_mem_info(&mem_total, &mem_used);\n\n\tsize_t mem_tmpfs = 0;\n\tget_tmpfs_info(&mem_tmpfs);\n\n\t/* Gather per-CPU usage from /proc/idle */\n\tint cpus[32];\n\tget_cpu_info(cpus);\n\n\t/* Gather screen size */\n\tstruct winsize w;\n\tioctl(STDOUT_FILENO, TIOCGWINSZ, &w);\n\n\t/* Figure out how we're going to lay out widgets */\n\tint top_rows = 1 + cpu_count;\n\tint meter_width = w.ws_col / 2;\n\tint current_row = 0;\n\tint left_side = 1;\n\n\t/* Generate info rows */\n\tint info_width = w.ws_col - meter_width;\n\tchar info_row[5][100] = {0};\n\tint info_rows = 0;\n\n\tif (info_width <= 30) {\n\t\tmeter_width = w.ws_col;\n\t\tinfo_width = 0;\n\t} else {\n#define T_T \"\\033[94m\"\n#define T_C \"\\033[0;1m\"\n#define T_E \"\\033[0m\"\n\t\tif (top_rows >= 1) {\n\t\t\tinfo_rows = 1;\n\t\t\tchar tmp[256] = {0};\n\t\t\tgethostname(tmp, 255);\n\t\t\tsnprintf(info_row[0], 99, T_T \"Hostname: \" T_C \"%.*s\" T_E, info_width - 10, tmp);\n\t\t}\n\t\tif (top_rows >= 2) {\n\t\t\tinfo_rows = 2;\n\t\t\tchar tmp[256] = {0};\n\t\t\tchar * format = \"%a %b %d %T %Y %Z\";\n\t\t\tstruct tm * timeinfo;\n\t\t\tstruct timeval now;\n\t\t\tgettimeofday(&now, NULL);\n\t\t\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\t\t\tstrftime(tmp,255,format,timeinfo);\n\t\t\tsnprintf(info_row[1], 99, T_T \"Time: \" T_C \"%.*s\" T_E, info_width - 6, tmp);\n\t\t}\n\t\tif (top_rows >= 3) {\n\t\t\tinfo_rows = 3;\n\t\t\tsnprintf(info_row[2], 99, T_T \"Tasks: \" T_C \"%lu\" T_E, count);\n\t\t}\n\t}\n\n\t/* Reset cursor to upper left */\n\tprintf(\"\\033[H\");\n\n\t/* Display CPU usage widgets */\n\tfor (int cpu = 0; cpu < cpu_count; ++cpu) {\n\t\tchar name[20], usage[30];\n\t\tsprintf(name, \"%3d\", cpu + 1);\n\t\tsprintf(usage, \"%d.%01d%%\", cpus[cpu] / 10, cpus[cpu] % 10);\n\t\tprint_meter(name, usage, left_side ? meter_width : info_width, 1, (int[]){cpus[cpu]}, 1000);\n\n\t\tif (current_row < info_rows) {\n\t\t\tprintf(\"%s\\033[K\\n\", info_row[current_row]);\n\t\t\tcurrent_row++;\n\t\t} else if (info_rows) {\n\t\t\tif (left_side) {\n\t\t\t\tleft_side = 0;\n\t\t\t} else {\n\t\t\t\tleft_side = 1;\n\t\t\t\tcurrent_row++;\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent_row++;\n\t\t}\n\t}\n\n\t/* Display memory usage widget */\n\tchar memUsed[30];\n\tsprintf(memUsed, \"%dM/%dM\", mem_used / 1024, mem_total / 1024);\n\tprint_meter(\"Mem\", memUsed, left_side ? meter_width : info_width, 2, (int[]){mem_used-mem_tmpfs,mem_tmpfs}, mem_total);\n\tif (left_side && current_row < info_rows) {\n\t\tprintf(\"%s\", info_row[current_row]);\n\t}\n\tcurrent_row++;\n\tprintf(\"\\033[K\\n\");\n\n\t/* Show column headers */\n\tprint_header();\n\tint i = 0;\n\tsize_t ent = 0;\n\n\t/* Print entries, or help text lines */\n\tif (show_help) {\n\t\tfor (ent = 0; ent < sizeof(help_text) / sizeof(*help_text); ++i, ++ent) {\n\t\t\tif (i >= w.ws_row - current_row - 2) break;\n\t\t\tprintf(\"%*s\\033[K\\n\", (int)w.ws_col, help_text[ent]);\n\t\t}\n\t} else {\n\t\tfor (ent = 0; ent < count; ++i, ++ent) {\n\t\t\tif (i >= w.ws_row - current_row - 2) break;\n\t\t\tprint_entry(processList[ent], w.ws_col);\n\t\t}\n\t}\n\n\t/* Clear remaining screen lines */\n\tfor (; i < w.ws_row - current_row - 2; ++i) {\n\t\tprintf(\"\\033[K\\n\");\n\t}\n\n\t/* Clean up process data from this round */\n\tfor (ent = 0; ent < count; ++ent) {\n\t\tfree_entry(processList[ent]);\n\t}\n\tfree(processList);\n\n\t/* Wait for command or 2 seconds for next refresh... */\n\tstruct pollfd fds[1];\n\tfds[0].fd = STDIN_FILENO;\n\tfds[0].events = POLLIN;\n\tint ret = poll(fds,1,2000);\n\tif (ret > 0 && fds[0].revents & POLLIN) {\n\t\tint c = fgetc(stdin);\n\t\tif (c == 'q') return 0;\n\t\tif (c == 'w') next_sort_order();\n\t\tif (c == 'W') prev_sort_order();\n\t\tif (c == 'h') show_help = !show_help;\n\t}\n\n\treturn 1;\n}\n\n/**\n * @brief Gather and print process information once.\n *\n * Prints only process information.\n */\nstatic int do_log(void) {\n\tsize_t count;\n\tstruct process ** processList = read_processes(&count);\n\n\tint mem_total = 0, mem_used = 0;\n\tget_mem_info(&mem_total, &mem_used);\n\n\tsize_t mem_tmpfs = 0;\n\tget_tmpfs_info(&mem_tmpfs);\n\n\t/* Gather per-CPU usage from /proc/idle */\n\tint cpus[32];\n\tget_cpu_info(cpus);\n\n\t/* Hostname */\n\t{\n\t\tchar tmp[256] = {0};\n\t\tgethostname(tmp, 255);\n\t\tprintf(\"Hostname: %s\\n\", tmp);\n\t}\n\n\t/* Current time */\n\t{\n\t\tchar tmp[256] = {0};\n\t\tchar * format = \"%a %b %d %T %Y %Z\";\n\t\tstruct tm * timeinfo;\n\t\tstruct timeval now;\n\t\tgettimeofday(&now, NULL);\n\t\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\t\tstrftime(tmp,255,format,timeinfo);\n\t\tprintf(\"Time: %s\\n\", tmp);\n\t}\n\n\t/* Task count and memory usage on one line */\n\tprintf(\"Tasks: %-7lu Mem: %dM/%dM (%ldM tmpfs)\\n\", count, mem_used / 1024, mem_total / 1024, mem_tmpfs/1024);\n\n\t/* CPU usage; all on one line; formatted best for small counts and <100% usage */\n\tfor (int cpu = 0; cpu < cpu_count; ++cpu) {\n\t\tprintf(\"CPU%2d:%3d.%01d%%%s\", cpu + 1, cpus[cpu] / 10, cpus[cpu] % 10,\n\t\t\t(cpu + 1 == cpu_count) ? \"\\n\" : \"   \");\n\t}\n\n\t/* Blank line to separator process table */\n\tprintf(\"\\n\");\n\n\tfor (int * c = columns; *c; ++c) {\n\t\tprintf(\"%*s \", ColumnDescriptions[*c].width, ColumnDescriptions[*c].title);\n\t}\n\tprintf(\"CMD\\n\");\n\n\tfor (size_t ent = 0; ent < count; ++ent) {\n\t\tstruct process * out = processList[ent];\n\t\tfor (int * c = columns; *c; ++c) {\n\t\t\tprint_column(out, *c);\n\t\t}\n\t\tprintf(\"%s\\n\", out->command_line ? out->command_line : out->process);\n\t\tfree(out);\n\t}\n\tfree(processList);\n\n\treturn 0;\n}\n\nstruct termios old;\nvoid get_initial_termios(void) {\n\ttcgetattr(STDOUT_FILENO, &old);\n}\n\n/**\n * @brief Switch to alt screen, turn on raw input.\n */\nvoid set_unbuffered(void) {\n\tstruct termios new = old;\n\tnew.c_iflag &= (~ICRNL) & (~IXON);\n\tnew.c_lflag &= (~ICANON) & (~ECHO);\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &new);\n\tprintf(\"\\033[?1049h\\033[?25l\\033[H\\033[2J\");\n}\n\n/**\n * @brief Switch to main screen, re-enable buffering.\n */\nvoid set_buffered(void) {\n\tprintf(\"\\033[H\\033[2J\\033[?25h\\033[?1049l\");\n\ttcsetattr(STDOUT_FILENO, TCSAFLUSH, &old);\n}\n\nvoid SIGWINCH_handler(int sig) {\n\t(void)sig;\n\tsignal(SIGWINCH, SIGWINCH_handler);\n}\n\nint main (int argc, char * argv[]) {\n\t/* Assume CPU count doesn't change... */\n\tcpu_count = sysfunc(TOARU_SYS_FUNC_NPROC, NULL);\n\n\t/*\n\t * If we are writing to a regular file, use the simple log format and\n\t * only output one sample of data before exiting.\n\t */\n\tif (!isatty(STDOUT_FILENO)) {\n\t\treturn do_log();\n\t}\n\n\t/* Initialize terminal for alt screen */\n\tget_initial_termios();\n\tset_unbuffered();\n\tsignal(SIGWINCH, SIGWINCH_handler);\n\n\t/* Loop */\n\twhile (do_once());\n\n\t/* Reset terminal */\n\tset_buffered();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/touch.c",
    "content": "/**\n * @brief touch - Create or update file timestamps\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <errno.h>\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr, \"%s: argument expected\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\n\tint out = 0;\n\tfor (int i = 1; i < argc; ++i) {\n\t\tFILE * f = fopen(argv[i], \"a\");\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tout = 1;\n\t\t\tcontinue;\n\t\t}\n\t\tfclose(f);\n\t}\n\n\treturn out;\n}\n"
  },
  {
    "path": "apps/true.c",
    "content": "/**\n * @brief true - Return success code\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\nint main() {\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/tty.c",
    "content": "/**\n * @brief tty - print terminal name\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n\nint main(int argc, char * argv[]) {\n\tif (!isatty(STDIN_FILENO)) {\n\t\tfprintf(stdout, \"not a tty\\n\");\n\t\treturn 1;\n\t}\n\tfprintf(stdout,\"%s\\n\",ttyname(STDIN_FILENO));\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/ttysize.c",
    "content": "/**\n * @brief ttysize - Magically divine terminal size\n *\n * This is called by getty to determine the size of foreign\n * terminals, such as ones attached over serial.\n *\n * It works by placing the cursor in the lower right of the\n * screen and requesting its position. Note that typing things\n * while this happens can cause problems. Maybe we can flush\n * stdin before doing this to try to avoid any conflicting data?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <getopt.h>\n#include <unistd.h>\n#include <sys/ioctl.h>\n#include <sys/fswait.h>\n\nstatic struct termios old;\n\nstatic void set_unbuffered() {\n\ttcgetattr(fileno(stdin), &old);\n\tstruct termios new = old;\n\tnew.c_lflag &= (~ICANON & ~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\nstatic void set_buffered() {\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &old);\n}\n\nstatic int getc_timeout(int timeout) {\n\tint fds[1] = {STDIN_FILENO};\n\tint index = fswait2(1,fds,timeout);\n\tif (index == 0) {\n\t\tunsigned char buf[1];\n\t\tread(STDIN_FILENO, buf, 1);\n\t\treturn buf[0];\n\t} else {\n\t\treturn -1;\n\t}\n}\n\nstatic void divine_size(int * width, int * height) {\n\tset_unbuffered();\n\n\t*width = 80;\n\t*height = 24;\n\n\tfprintf(stderr, \"\\033[s\\033[1000;1000H\\033[6n\\033[u\");\n\tfflush(stderr);\n\n\tchar buf[1024] = {0};\n\tsize_t i = 0;\n\twhile (1) {\n\t\tint c = getc_timeout(200);\n\t\tif (c == 'R') break;\n\t\tif (c == -1) goto _done;\n\t\tif (c == '\\033') continue;\n\t\tif (c == '[') continue;\n\t\tbuf[i++] = c;\n\t}\n\n\tchar * s = strstr(buf, \";\");\n\tif (s) {\n\t\t*(s++) = '\\0';\n\n\t\t*height = atoi(buf);\n\t\t*width = atoi(s);\n\t}\n\n_done:\n\tfflush(stderr);\n\tset_buffered();\n}\n\nint main(int argc, char * argv[]) {\n\tint width, height;\n\tint opt;\n\tint quiet = 0;\n\n\twhile ((opt = getopt(argc, argv, \"q\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'q':\n\t\t\t\tquiet = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (optind + 2 == argc) {\n\t\twidth = atoi(argv[optind]);\n\t\theight = atoi(argv[optind+1]);\n\t} else {\n\t\tdivine_size(&width, &height);\n\t}\n\n\tstruct winsize w;\n\tw.ws_col = width;\n\tw.ws_row = height;\n\tw.ws_xpixel = 0;\n\tw.ws_ypixel = 0;\n\tioctl(0, TIOCSWINSZ, &w);\n\n\tif (!quiet) {\n\t\tfprintf(stderr, \"%dx%d\\n\", width, height);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/tutorial.c",
    "content": "/**\n * @brief A recreation of the original wizard.py, explaining\n *        the functionality of ToaruOS and how to use the WM.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2019-2021 K. Lange\n */\n#include <time.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/button.h>\n#include <toaru/text.h>\n\n#include <sys/utsname.h>\n\n#define BUTTON_HEIGHT 28\n#define BUTTON_WIDTH 86\n#define BUTTON_PADDING 14\n\nstatic yutani_t * yctx;\n\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\n\nstatic yutani_window_t * background = NULL;\nstatic gfx_context_t * background_ctx = NULL;\n\nstatic int32_t width = 640;\nstatic int32_t height = 480;\n\nstatic char * title_str;\nstatic char * body_text[20] = {NULL};\n\nstatic sprite_t * icon = NULL;\nstatic sprite_t terminal;\nstatic sprite_t folder;\nstatic sprite_t package;\nstatic sprite_t logo;\nstatic sprite_t mouse_drag;\nstatic sprite_t cdicon;\n\nstatic struct TT_Font * _tt_font_thin = NULL;\nstatic struct TT_Font * _tt_font_bold = NULL;\n\nstatic int page = 0;\n\nstatic int center(int x, int width) {\n\treturn (width - x) / 2;\n}\n\nstatic void draw_string(int y, const char * string, struct TT_Font * font, uint32_t color, int size) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\ttt_set_size(font, size);\n\ttt_draw_string(ctx, font, bounds.left_width + center(tt_string_width(font, string), width), bounds.top_height + 30 + y + size, string, color);\n}\n\nstruct TTKButton _next_button = {0};\nstruct TTKButton _prev_button = {0};\n\nstatic int _prev_enabled = 0;\n\nstatic void redraw(void) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tdraw_fill(ctx, rgb(204,204,204));\n\tint offset = 0;\n\n\tif (icon) {\n\t\toffset = icon->height;\n\t\tdraw_sprite(ctx, icon, bounds.left_width + center(icon->width, width), bounds.top_height + 15);\n\t}\n\n\tfor (char ** copy_str = body_text; *copy_str; ++copy_str) {\n\t\tif (**copy_str == '-') {\n\t\t\toffset += 10;\n\t\t} else if (**copy_str == '%') {\n\t\t\tdraw_string(offset, *copy_str+1, _tt_font_thin, rgb(0,0,255), 13);\n\t\t\toffset += 20;\n\t\t} else if (**copy_str == '#') {\n\t\t\tdraw_string(offset, *copy_str+1, _tt_font_bold, rgb(0,0,0), 20);\n\t\t\toffset += 20;\n\t\t} else {\n\t\t\tdraw_string(offset, *copy_str, _tt_font_thin, rgb(0,0,0), 13);\n\t\t\toffset += 20;\n\t\t}\n\t}\n\n\tttk_button_draw(ctx, &_next_button);\n\tif (!_prev_enabled) {\n\t\tint _tmp = _prev_button.hilight;\n\t\t_prev_button.hilight = (1 << 8);\n\t\tttk_button_draw(ctx, &_prev_button);\n\t\t_prev_button.hilight = _tmp;\n\t} else {\n\t\tttk_button_draw(ctx, &_prev_button);\n\t}\n\n\trender_decorations(window, ctx, title_str);\n\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nstatic void reset_background(void) {\n\tdraw_fill(background_ctx, rgba(0,0,0,200));\n}\n\nstatic void invert_background_alpha(void) {\n\tfor (unsigned int y = 0; y < background->height; ++y) {\n\t\tfor (unsigned int x = 0; x < background->width; ++x) {\n\t\t\tuint32_t c = GFX(background_ctx, x, y);\n\t\t\tint r = _RED(c);\n\t\t\tint g = _GRE(c);\n\t\t\tint b = _BLU(c);\n\t\t\tint a = _ALP(c);\n\t\t\ta = 255 - a;\n\t\t\tGFX(background_ctx, x, y) = rgba(r,g,b,a);\n\t\t}\n\t}\n}\n\nstatic void circle(int x, int y, int r) {\n\tdraw_fill(background_ctx, rgba(0,0,0,255-200));\n\tdraw_rounded_rectangle(background_ctx, x - r, y - r, r * 2, r * 2, r, rgb(0,0,0));\n\tinvert_background_alpha();\n}\n\nstatic char * randomly_select_begging(void) {\n\tchar * options[] = {\n\t\t\"You can help support ToaruOS by donating:\",\n\t\t\"Your donation helps us continue developing ToaruOS:\",\n\t\t\"You can sponsor ToaruOS development on Github:\",\n\t\t\"Please give me money:\",\n\t};\n\n\treturn options[rand() % (sizeof(options) / sizeof(*options))];\n\n}\n\nstatic void load_page(int page) {\n\n\tint i = 0;\n\t_prev_enabled = 1;\n\t_next_button.title = \"Next\";\n\treset_background();\n\n\tswitch (page) {\n\t\tcase 0:\n\t\t\t_prev_enabled = 0;\n\t\t\ttitle_str = \"Welcome to ToaruOS!\";\n\t\t\ticon = &logo;\n\t\t\tbody_text[i++] = \"#Welcome to ToaruOS!\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"This tutorial will guide you through the features of the operating\";\n\t\t\tbody_text[i++] = \"system, as well as give you a feel for the UI and design principles.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"When you're ready to continue, press \\\"Next\\\".\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"%https://github.com/klange/toaruos - https://toaruos.org\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"ToaruOS is free software, released under the terms of the\";\n\t\t\tbody_text[i++] = \"NCSA/University of Illinois license.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = randomly_select_begging();\n\t\t\tbody_text[i++] = \"%https://github.com/sponsors/klange\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\ticon = &logo;\n\t\t\tbody_text[i++] = \"ToaruOS is a hobby project. The entire contents of this Live CD\";\n\t\t\tbody_text[i++] = \"were written by the ToaruOS development team over the course of\";\n\t\t\tbody_text[i++] = \"many years, but that development team is very small. Some features\";\n\t\t\tbody_text[i++] = \"may be missing, incomplete, or unstable. Contributions in the form\";\n\t\t\tbody_text[i++] = \"of bug reports and new ports are welcome. You can join our community\";\n\t\t\tbody_text[i++] = \"through IRC by joining the #toaruos channel on Libera.chat.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"You can help support ToaruOS by donating:\";\n\t\t\tbody_text[i++] = \"%https://github.com/sponsors/klange\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\ticon = &cdicon;\n\t\t\tbody_text[i++] = \"This is a \\\"live CD\\\". You can make changes to the file system, including\";\n\t\t\tbody_text[i++] = \"installing applications, but those changes will not persist between reboots.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"If you need to enter a password, such as for the \\\"sudo\\\" utility or when\";\n\t\t\tbody_text[i++] = \"using the package manager, the default user account is \\\"local\\\" with the\";\n\t\t\tbody_text[i++] = \"password \\\"local\\\". There is also a \\\"guest\\\" account available with limited\";\n\t\t\tbody_text[i++] = \"privileges (password \\\"guest\\\"), and a \\\"root\\\" account (password \\\"toor\\\").\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\ticon = &folder;\n\t\t\tcircle(70, 90, 60);\n\t\t\tbody_text[i++] = \"You can explore the file system using the File Browser.\";\n\t\t\tbody_text[i++] = \"Application shortcuts on the desktop, as well as files in the file browser\";\n\t\t\tbody_text[i++] = \"are opened with a double click. You can also find more applications in\";\n\t\t\tbody_text[i++] = \"the Applications menu in the upper left.\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\ticon = &terminal;\n\t\t\tcircle(70, 170, 60);\n\t\t\tbody_text[i++] = \"ToaruOS aims to provide a Unix-like environment. You can find\";\n\t\t\tbody_text[i++] = \"familiar command-line tools by opening a terminal. ToaruOS's\";\n\t\t\tbody_text[i++] = \"shell provides command history, syntax highlighting, and tab\";\n\t\t\tbody_text[i++] = \"completion. There is also a growing suite of Unix utilities\";\n\t\t\tbody_text[i++] = \"and a featureful text editor (bim).\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\ticon = &package;\n\t\t\tcircle(70, 250, 60);\n\t\t\tbody_text[i++] = \"Many third-party software packages have been ported to ToaruOS\";\n\t\t\tbody_text[i++] = \"and are available from our package repositories. You can use the\";\n\t\t\tbody_text[i++] = \"Package Manager to install GCC, Doom, Quake, and more.\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\ticon = &mouse_drag;\n\t\t\tbody_text[i++] = \"With ToaruOS's window manager, you can drag most windows by\";\n\t\t\tbody_text[i++] = \"holding Alt, or by using the title bar. You can also resize\";\n\t\t\tbody_text[i++] = \"windows by dragging from their edges or using Alt + Middle Click.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"Note that if you are running ToaruOS in a virtual machine, your\";\n\t\t\tbody_text[i++] = \"host operating system configuration may conflict with modifier\";\n\t\t\tbody_text[i++] = \"keys in ToaruOS.\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tcase 7:\n\t\t\ticon = NULL;\n\t\t\t_next_button.title = \"Exit\";\n\t\t\tbody_text[i++] = \"#That's it!\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"The tutorial is over.\";\n\t\t\tbody_text[i++] = \"\";\n\t\t\tbody_text[i++] = \"Press \\\"Exit\\\" to close this window and start exploring ToaruOS.\";\n\t\t\tbody_text[i++] = NULL;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\texit(0);\n\t\t\tbreak;\n\t}\n\n\tflip(background_ctx);\n\tyutani_flip(yctx, background);\n}\n\nint in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {\n\tif (me->new_y >= button->y && me->new_y < button->y  + button->height) {\n\t\tif (me->new_x >= button->x && me->new_x < button->x + button->width) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid setup_buttons(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\t_next_button.title = \"Next\";\n\t_next_button.width = BUTTON_WIDTH;\n\t_next_button.height = BUTTON_HEIGHT;\n\t_next_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING;\n\t_next_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n\n\t_prev_button.title = \"Back\";\n\t_prev_button.width = BUTTON_WIDTH;\n\t_prev_button.height = BUTTON_HEIGHT;\n\t_prev_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 2;\n\t_prev_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n}\n\nstatic void update_size(int w, int h) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\twidth  = w - bounds.width;\n\theight = h - bounds.height;\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\tupdate_size(w, h);\n\tsetup_buttons();\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n}\n\nvoid resize_finish_bg(int w, int h) {\n\tyutani_window_resize_accept(yctx, background, w, h);\n\treinit_graphics_yutani(background_ctx, background);\n\tload_page(page);\n\tyutani_window_resize_done(yctx, background);\n}\n\nvoid set_hilight(struct TTKButton * button, int hilight) {\n\tif (!button && (_next_button.hilight || _prev_button.hilight)) {\n\t\t_next_button.hilight = 0;\n\t\t_prev_button.hilight = 0;\n\t\tredraw();\n\t} else if (button && (button->hilight != hilight)) {\n\t\t_next_button.hilight = 0;\n\t\t_prev_button.hilight = 0;\n\t\tbutton->hilight = hilight;\n\t\tredraw();\n\t}\n}\n\n\nint main(int argc, char * argv[]) {\n\tsrand(time(NULL));\n\tint req_center_x, req_center_y;\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\t_tt_font_thin = tt_font_from_shm(\"sans-serif\");\n\t_tt_font_bold = tt_font_from_shm(\"sans-serif.bold\");\n\n\tbackground = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height,\n\t\t\tYUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG |\n\t\t\tYUTANI_WINDOW_FLAG_ALT_ANIMATION | YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS);\n\tyutani_window_move(yctx, background, 0, 0);\n\tyutani_window_update_shape(yctx, background, 2);\n\tbackground_ctx = init_graphics_yutani_double_buffer(background);\n\treset_background();\n\tflip(background_ctx);\n\tyutani_flip(yctx, background);\n\n\tupdate_size(width, height);\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\twindow = yutani_window_create(yctx, width + bounds.width, height + bounds.height);\n\twindow->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE;\n\treq_center_x = yctx->display_width / 2;\n\treq_center_y = yctx->display_height / 2;\n\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\n\t/* Load icons */\n\tload_sprite(&logo, \"/usr/share/logo_login.png\");\n\tload_sprite(&terminal, \"/usr/share/icons/48/utilities-terminal.png\");\n\tload_sprite(&folder, \"/usr/share/icons/48/folder.png\");\n\tload_sprite(&package, \"/usr/share/icons/48/package.png\");\n\tload_sprite(&mouse_drag, \"/usr/share/cursor/drag.png\");\n\tload_sprite(&cdicon, \"/usr/share/icons/48/cd.png\");\n\n\tload_page(0);\n\n\tyutani_window_advertise_icon(yctx, window, title_str, \"star\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\tsetup_buttons();\n\tredraw();\n\n\tstruct TTKButton * _down_button = NULL;\n\n\tint playing = 1;\n\tint status = 0;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == '\\n') {\n\t\t\t\t\t\t\tpage++;\n\t\t\t\t\t\t\tload_page(page);\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t} else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\tstatus = 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (wf->wid == background->wid) {\n\t\t\t\t\t\t\tyutani_focus_window(yctx, window->wid);\n\t\t\t\t\t\t} else if (win) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WELCOME:\n\t\t\t\t\tyutani_window_resize_offer(yctx, background, yctx->display_width, yctx->display_height);\n\t\t\t\t\treq_center_x = yctx->display_width / 2;\n\t\t\t\t\treq_center_y = yctx->display_height / 2;\n\t\t\t\t\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tif (wr->wid == window->wid) {\n\t\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t\t} else if (wr->wid == background->wid) {\n\t\t\t\t\t\t\tresize_finish_bg(wr->width, wr->height);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\tstatus = 2;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t\tdecor_get_bounds(window, &bounds);\n\t\t\t\t\t\t\tif (me->new_y > bounds.top_height) {\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_next_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_next_button, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_next_button;\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_prev_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_prev_button, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_prev_button;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\t\t\tif (_down_button) {\n\t\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\t\tif (_down_button == &_prev_button) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (page > 0) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tpage--;\n\t\t\t\t\t\t\t\t\t\t\t\t\tload_page(page);\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button == &_next_button) {\n\t\t\t\t\t\t\t\t\t\t\t\tpage++;\n\t\t\t\t\t\t\t\t\t\t\t\tload_page(page);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t_down_button->hilight = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t_down_button = NULL;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_next_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_next_button, 1);\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_prev_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_prev_button, 1);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL,0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (_down_button) {\n\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(_down_button, 2);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL, 0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tstatus = 2;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn status;\n}\n"
  },
  {
    "path": "apps/uname.c",
    "content": "/**\n * @brief uname - Print kernel version information\n *\n * Supports all the usual options (a,s,n,r,v,m,o)\n *\n * Note that o is hardcoded, which is also the situation in\n * the coreutils implementation, so I don't see that being\n * a problem. If you want to build this uname for Linux or\n * something... you'll have to change that.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/utsname.h>\n\n#define FLAG_SYSNAME  0x01\n#define FLAG_NODENAME 0x02\n#define FLAG_RELEASE  0x04\n#define FLAG_VERSION  0x08\n#define FLAG_MACHINE  0x10\n#define FLAG_OSNAME   0x20\n\n#define FLAG_ALL (FLAG_SYSNAME|FLAG_NODENAME|FLAG_RELEASE|FLAG_VERSION|FLAG_MACHINE|FLAG_OSNAME)\n\n#define _ITALIC \"\\033[3m\"\n#define _END    \"\\033[0m\\n\"\n\nvoid show_usage(int argc, char * argv[]) {\n\tfprintf(stderr,\n\t\t\t\"uname - Print system version information.\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-asnrvmop]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -a     \" _ITALIC \"Print the standard uname string we all love\" _END\n\t\t\t\" -s     \" _ITALIC \"Print kernel name\" _END\n\t\t\t\" -n     \" _ITALIC \"Print system name\" _END\n\t\t\t\" -r     \" _ITALIC \"Print kernel version number\" _END\n\t\t\t\" -v     \" _ITALIC \"Print the extra kernel version information\" _END\n\t\t\t\" -m     \" _ITALIC \"Print the architecture name\" _END\n\t\t\t\" -o     \" _ITALIC \"Print operating system name\" _END\n\t\t\t\" -p     \" _ITALIC \"Alias to -m\" _END\n\t\t\t\"\\n\", argv[0]);\n\texit(1);\n}\n\nint main(int argc, char * argv[]) {\n\tstruct utsname u;\n\n\tint flags = 0;\n\tint space = 0;\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (argv[i][0] == '-') {\n\t\t\tchar *c = &argv[i][1];\n\t\t\twhile (*c) {\n\t\t\t\tswitch (*c) {\n\t\t\t\t\tcase 'a':\n\t\t\t\t\t\tflags |= FLAG_ALL;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 's':\n\t\t\t\t\t\tflags |= FLAG_SYSNAME;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'n':\n\t\t\t\t\t\tflags |= FLAG_NODENAME;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'r':\n\t\t\t\t\t\tflags |= FLAG_RELEASE;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'v':\n\t\t\t\t\t\tflags |= FLAG_VERSION;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'm':\n\t\t\t\t\tcase 'p':\n\t\t\t\t\t\tflags |= FLAG_MACHINE;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'o':\n\t\t\t\t\t\tflags |= FLAG_OSNAME;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'h':\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tshow_usage(argc, argv);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tc++;\n\t\t\t}\n\t\t}\n\t}\n\n\tuname(&u);\n\n\tif (!flags) {\n\t\t/* By default, we just print the kernel name */\n\t\tflags = FLAG_SYSNAME;\n\t}\n\n\tif (flags & FLAG_SYSNAME) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", u.sysname);\n\t}\n\n\tif (flags & FLAG_NODENAME) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", u.nodename);\n\t}\n\n\tif (flags & FLAG_RELEASE) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", u.release);\n\t}\n\n\tif (flags & FLAG_VERSION) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", u.version);\n\t}\n\n\tif (flags & FLAG_MACHINE) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", u.machine);\n\t}\n\n\tif (flags & FLAG_OSNAME) {\n\t\tif (space++) printf(\" \");\n\t\tprintf(\"%s\", \"ToaruOS\");\n\t}\n\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/ununicode.h",
    "content": "/* Commonly used by a few different things. */\nstatic uint32_t ununicode(uint32_t c) {\n\tswitch (c) {\n\t\tcase L'☺': return 1;\n\t\tcase L'☻': return 2;\n\t\tcase L'♥': return 3;\n\t\tcase L'♦': return 4;\n\t\tcase L'♣': return 5;\n\t\tcase L'♠': return 6;\n\t\tcase L'•': return 7;\n\t\tcase L'◘': return 8;\n\t\tcase L'○': return 9;\n\t\tcase L'◙': return 10;\n\t\tcase L'♂': return 11;\n\t\tcase L'♀': return 12;\n\t\tcase L'♪': return 13;\n\t\tcase L'♫': return 14;\n\t\tcase L'☼': return 15;\n\t\tcase L'►': return 16;\n\t\tcase L'◄': return 17;\n\t\tcase L'↕': return 18;\n\t\tcase L'‼': return 19;\n\t\tcase L'¶': return 20;\n\t\tcase L'§': return 21;\n\t\tcase L'▬': return 22;\n\t\tcase L'↨': return 23;\n\t\tcase L'↑': return 24;\n\t\tcase L'↓': return 25;\n\t\tcase L'→': return 26;\n\t\tcase L'←': return 27;\n\t\tcase L'∟': return 28;\n\t\tcase L'↔': return 29;\n\t\tcase L'▲': return 30;\n\t\tcase L'▼': return 31;\n\t\t/* ASCII text */\n\t\tcase L'⌂': return 127;\n\t\tcase L'Ç': return 128;\n\t\tcase L'ü': return 129;\n\t\tcase L'é': return 130;\n\t\tcase L'â': return 131;\n\t\tcase L'ä': return 132;\n\t\tcase L'à': return 133;\n\t\tcase L'å': return 134;\n\t\tcase L'ç': return 135;\n\t\tcase L'ê': return 136;\n\t\tcase L'ë': return 137;\n\t\tcase L'è': return 138;\n\t\tcase L'ï': return 139;\n\t\tcase L'î': return 140;\n\t\tcase L'ì': return 141;\n\t\tcase L'Ä': return 142;\n\t\tcase L'Å': return 143;\n\t\tcase L'É': return 144;\n\t\tcase L'æ': return 145;\n\t\tcase L'Æ': return 146;\n\t\tcase L'ô': return 147;\n\t\tcase L'ö': return 148;\n\t\tcase L'ò': return 149;\n\t\tcase L'û': return 150;\n\t\tcase L'ù': return 151;\n\t\tcase L'ÿ': return 152;\n\t\tcase L'Ö': return 153;\n\t\tcase L'Ü': return 154;\n\t\tcase L'¢': return 155;\n\t\tcase L'£': return 156;\n\t\tcase L'¥': return 157;\n\t\tcase L'₧': return 158;\n\t\tcase L'ƒ': return 159;\n\t\tcase L'á': return 160;\n\t\tcase L'í': return 161;\n\t\tcase L'ó': return 162;\n\t\tcase L'ú': return 163;\n\t\tcase L'ñ': return 164;\n\t\tcase L'Ñ': return 165;\n\t\tcase L'ª': return 166;\n\t\tcase L'º': return 167;\n\t\tcase L'¿': return 168;\n\t\tcase L'⌐': return 169;\n\t\tcase L'¬': return 170;\n\t\tcase L'½': return 171;\n\t\tcase L'¼': return 172;\n\t\tcase L'¡': return 173;\n\t\tcase L'«': return 174;\n\t\tcase L'»': return 175;\n\t\tcase L'░': return 176;\n\t\tcase L'▒': return 177;\n\t\tcase L'▓': return 178;\n\t\tcase L'│': return 179;\n\t\tcase L'┤': return 180;\n\t\tcase L'╡': return 181;\n\t\tcase L'╢': return 182;\n\t\tcase L'╖': return 183;\n\t\tcase L'╕': return 184;\n\t\tcase L'╣': return 185;\n\t\tcase L'║': return 186;\n\t\tcase L'╗': return 187;\n\t\tcase L'╝': return 188;\n\t\tcase L'╜': return 189;\n\t\tcase L'╛': return 190;\n\t\tcase L'┐': return 191;\n\t\tcase L'└': return 192;\n\t\tcase L'┴': return 193;\n\t\tcase L'┬': return 194;\n\t\tcase L'├': return 195;\n\t\tcase L'─': return 196;\n\t\tcase L'┼': return 197;\n\t\tcase L'╞': return 198;\n\t\tcase L'╟': return 199;\n\t\tcase L'╚': return 200;\n\t\tcase L'╔': return 201;\n\t\tcase L'╩': return 202;\n\t\tcase L'╦': return 203;\n\t\tcase L'╠': return 204;\n\t\tcase L'═': return 205;\n\t\tcase L'╬': return 206;\n\t\tcase L'╧': return 207;\n\t\tcase L'╨': return 208;\n\t\tcase L'╤': return 209;\n\t\tcase L'╥': return 210;\n\t\tcase L'╙': return 211;\n\t\tcase L'╘': return 212;\n\t\tcase L'╒': return 213;\n\t\tcase L'╓': return 214;\n\t\tcase L'╫': return 215;\n\t\tcase L'╪': return 216;\n\t\tcase L'┘': return 217;\n\t\tcase L'┌': return 218;\n\t\tcase L'█': return 219;\n\t\tcase L'▄': return 220;\n\t\tcase L'▌': return 221;\n\t\tcase L'▐': return 222;\n\t\tcase L'▀': return 223;\n\t\tcase L'α': return 224;\n\t\tcase L'ß': return 225;\n\t\tcase L'Γ': return 226;\n\t\tcase L'π': return 227;\n\t\tcase L'Σ': return 228;\n\t\tcase L'σ': return 229;\n\t\tcase L'µ': return 230;\n\t\tcase L'τ': return 231;\n\t\tcase L'Φ': return 232;\n\t\tcase L'Θ': return 233;\n\t\tcase L'Ω': return 234;\n\t\tcase L'δ': return 235;\n\t\tcase L'∞': return 236;\n\t\tcase L'φ': return 237;\n\t\tcase L'ε': return 238;\n\t\tcase L'∩': return 239;\n\t\tcase L'≡': return 240;\n\t\tcase L'±': return 241;\n\t\tcase L'≥': return 242;\n\t\tcase L'≤': return 243;\n\t\tcase L'⌠': return 244;\n\t\tcase L'⌡': return 245;\n\t\tcase L'÷': return 246;\n\t\tcase L'≈': return 247;\n\t\tcase L'°': return 248;\n\t\tcase L'∙': return 249;\n\t\tcase L'·': return 250;\n\t\tcase L'√': return 251;\n\t\tcase L'ⁿ': return 252;\n\t\tcase L'²': return 253;\n\t\tcase L'■': return 254;\n\t}\n\treturn 4;\n}\n\n"
  },
  {
    "path": "apps/upload.krk",
    "content": "#!/bin/kuroko\n'''\nTest tool to send file contents over a socket.\n\n$ upload.krk file address:port\n'''\nimport socket\nimport fileio\nimport kuroko\n\n\ndef usage():\n    print(f'''usage: {kuroko.argv[0]} file address:port\n\nSends files over a TCP socket to a remote address.\n''', file=fileio.stderr)\n    return 1\n\nif __name__ == '__main__':\n    if len(kuroko.argv) < 3:\n        return usage()\n\n    let path = kuroko.argv[1]\n    let endpoint = kuroko.argv[2]\n\n    if ':' not in endpoint:\n        return usage()\n\n    let x = endpoint.split(':')\n    endpoint = (x[0], int(x[1]))\n\n    let b\n    with fileio.open(path,'rb') as f:\n        b = f.read()\n    print(\"Uploading\",len(b),\"byte(s) to\",endpoint)\n    let s = socket.socket()\n    s.connect(endpoint)\n    print(\"Connected...\")\n    s.send(b)\n    print(\"Done.\")\n\n"
  },
  {
    "path": "apps/uptime.c",
    "content": "/**\n * @brief Print system uptime\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <time.h>\n#include <sys/time.h>\n\nvoid print_time(void) {\n\tstruct timeval now;\n\tstruct tm * timeinfo;\n\tchar clocktime[10];\n\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tstrftime(clocktime, 80, \"%H:%M:%S\", timeinfo);\n\n\tprintf(\" %s \", clocktime);\n}\n\n#define MINUTE (60)\n#define HOUR (60 * MINUTE)\n#define DAY (24 * HOUR)\nvoid print_seconds(int seconds) {\n\tif (seconds > DAY) {\n\t\tint days = seconds / DAY;\n\t\tseconds -= DAY * days;\n\t\tprintf(\"%d day%s, \", days, days != 1 ? \"s\" : \"\");\n\t}\n\tif (seconds > HOUR) {\n\t\tint hours = seconds / HOUR;\n\t\tseconds -= HOUR * hours;\n\t\tint minutes = seconds / MINUTE;\n\t\tprintf(\"%2d:%02d\", hours, minutes);\n\t\treturn;\n\t} else if (seconds > MINUTE) {\n\t\tint minutes = seconds / MINUTE;\n\t\tprintf(\"%d minute%s,  \", minutes, minutes != 1 ? \"s\" : \"\");\n\t\tseconds -= MINUTE * minutes;\n\t}\n\n\tprintf(\"%2d second%s\", seconds, seconds != 1 ? \"s\" : \"\");\n}\n\nvoid print_uptime(void) {\n\tFILE * f = fopen(\"/proc/uptime\", \"r\");\n\tif (!f) return;\n\n\tint seconds;\n\n\tchar buf[1024] = {0};\n\tfgets(buf, 1024, f);\n\tchar * dot = strchr(buf, '.');\n\t*dot = '\\0';\n\tdot++;\n\tdot[3] = '\\0';\n\n\tseconds = atoi(buf);\n\n\tprintf(\"up \");\n\n\tprint_seconds(seconds);\n}\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"uptime - display system uptime information\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-p]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -p     \\033[3mshow just the uptime info\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nint main(int argc, char * argv[]) {\n\tint just_pretty_uptime = 0;\n\tint opt;\n\n\twhile ((opt = getopt(argc, argv, \"?p\")) != -1 ) {\n\t\tswitch (opt) {\n\t\t\tcase 'p':\n\t\t\t\tjust_pretty_uptime = 1;\n\t\t\t\tbreak;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc, argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\n\tif (!just_pretty_uptime)\n\t\tprint_time();\n\tprint_uptime();\n\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n\n\n"
  },
  {
    "path": "apps/vga-palette.h",
    "content": "/**\n * @brief VGA palette conversion\n *\n * Converts 256-color index values to closest matching 16-color\n * value for the VGA terminal. Note that values here are terminal\n * color codes, not the VGA color codes - the terminal converts\n * them to VGA color codes later. This was automatically generated\n * from a script, but I don't know where that script went.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n#define PALETTE_COLORS 256\nuint32_t vga_colors[PALETTE_COLORS] = {\n\t0x0,\n\t0x1,\n\t0x2,\n\t0x3,\n\t0x4,\n\t0x5,\n\t0x6,\n\t0x7,\n\t0x8,\n\t0x9,\n\t0xa,\n\t0xb,\n\t0xc,\n\t0xd,\n\t0xe,\n\t0xf,\n\t0x0, /* #000000 -> #000000 */\n\t0x4, /* #00005f -> #0000aa */\n\t0x4, /* #000087 -> #0000aa */\n\t0x4, /* #0000af -> #0000aa */\n\t0x4, /* #0000d7 -> #0000aa */\n\t0xc, /* #0000ff -> #5555ff */\n\t0x2, /* #005f00 -> #00aa00 */\n\t0x8, /* #005f5f -> #555555 */\n\t0x6, /* #005f87 -> #00aaaa */\n\t0x6, /* #005faf -> #00aaaa */\n\t0xc, /* #005fd7 -> #5555ff */\n\t0xc, /* #005fff -> #5555ff */\n\t0x2, /* #008700 -> #00aa00 */\n\t0xa, /* #00875f -> #55aa55 */\n\t0x6, /* #008787 -> #00aaaa */\n\t0x6, /* #0087af -> #00aaaa */\n\t0x6, /* #0087d7 -> #00aaaa */\n\t0xc, /* #0087ff -> #5555ff */\n\t0x2, /* #00af00 -> #00aa00 */\n\t0xa, /* #00af5f -> #55aa55 */\n\t0x6, /* #00af87 -> #00aaaa */\n\t0x6, /* #00afaf -> #00aaaa */\n\t0x6, /* #00afd7 -> #00aaaa */\n\t0xe, /* #00afff -> #55ffff */\n\t0x2, /* #00d700 -> #00aa00 */\n\t0xa, /* #00d75f -> #55aa55 */\n\t0x6, /* #00d787 -> #00aaaa */\n\t0x6, /* #00d7af -> #00aaaa */\n\t0x6, /* #00d7d7 -> #00aaaa */\n\t0xe, /* #00d7ff -> #55ffff */\n\t0x2, /* #00ff00 -> #00aa00 */\n\t0xa, /* #00ff5f -> #55aa55 */\n\t0x6, /* #00ff87 -> #00aaaa */\n\t0x6, /* #00ffaf -> #00aaaa */\n\t0xe, /* #00ffd7 -> #55ffff */\n\t0xe, /* #00ffff -> #55ffff */\n\t0x1, /* #5f0000 -> #aa0000 */\n\t0x8, /* #5f005f -> #555555 */\n\t0x5, /* #5f0087 -> #aa00aa */\n\t0x5, /* #5f00af -> #aa00aa */\n\t0x5, /* #5f00d7 -> #aa00aa */\n\t0xc, /* #5f00ff -> #5555ff */\n\t0x3, /* #5f5f00 -> #aa5500 */\n\t0x8, /* #5f5f5f -> #555555 */\n\t0x8, /* #5f5f87 -> #555555 */\n\t0x7, /* #5f5faf -> #aaaaaa */\n\t0xc, /* #5f5fd7 -> #5555ff */\n\t0xc, /* #5f5fff -> #5555ff */\n\t0x2, /* #5f8700 -> #00aa00 */\n\t0xa, /* #5f875f -> #55aa55 */\n\t0xa, /* #5f8787 -> #55aa55 */\n\t0x7, /* #5f87af -> #aaaaaa */\n\t0xc, /* #5f87d7 -> #5555ff */\n\t0xc, /* #5f87ff -> #5555ff */\n\t0x2, /* #5faf00 -> #00aa00 */\n\t0xa, /* #5faf5f -> #55aa55 */\n\t0xa, /* #5faf87 -> #55aa55 */\n\t0x7, /* #5fafaf -> #aaaaaa */\n\t0x7, /* #5fafd7 -> #aaaaaa */\n\t0xe, /* #5fafff -> #55ffff */\n\t0x2, /* #5fd700 -> #00aa00 */\n\t0xa, /* #5fd75f -> #55aa55 */\n\t0xa, /* #5fd787 -> #55aa55 */\n\t0x7, /* #5fd7af -> #aaaaaa */\n\t0xe, /* #5fd7d7 -> #55ffff */\n\t0xe, /* #5fd7ff -> #55ffff */\n\t0x2, /* #5fff00 -> #00aa00 */\n\t0xb, /* #5fff5f -> #ffff55 */\n\t0xb, /* #5fff87 -> #ffff55 */\n\t0x7, /* #5fffaf -> #aaaaaa */\n\t0xe, /* #5fffd7 -> #55ffff */\n\t0xe, /* #5fffff -> #55ffff */\n\t0x1, /* #870000 -> #aa0000 */\n\t0x8, /* #87005f -> #555555 */\n\t0x5, /* #870087 -> #aa00aa */\n\t0x5, /* #8700af -> #aa00aa */\n\t0x5, /* #8700d7 -> #aa00aa */\n\t0xc, /* #8700ff -> #5555ff */\n\t0x3, /* #875f00 -> #aa5500 */\n\t0x8, /* #875f5f -> #555555 */\n\t0x8, /* #875f87 -> #555555 */\n\t0x7, /* #875faf -> #aaaaaa */\n\t0xc, /* #875fd7 -> #5555ff */\n\t0xc, /* #875fff -> #5555ff */\n\t0x3, /* #878700 -> #aa5500 */\n\t0xa, /* #87875f -> #55aa55 */\n\t0x7, /* #878787 -> #aaaaaa */\n\t0x7, /* #8787af -> #aaaaaa */\n\t0x7, /* #8787d7 -> #aaaaaa */\n\t0xc, /* #8787ff -> #5555ff */\n\t0x2, /* #87af00 -> #00aa00 */\n\t0xa, /* #87af5f -> #55aa55 */\n\t0x7, /* #87af87 -> #aaaaaa */\n\t0x7, /* #87afaf -> #aaaaaa */\n\t0x7, /* #87afd7 -> #aaaaaa */\n\t0xe, /* #87afff -> #55ffff */\n\t0x2, /* #87d700 -> #00aa00 */\n\t0xa, /* #87d75f -> #55aa55 */\n\t0x7, /* #87d787 -> #aaaaaa */\n\t0x7, /* #87d7af -> #aaaaaa */\n\t0xe, /* #87d7d7 -> #55ffff */\n\t0xe, /* #87d7ff -> #55ffff */\n\t0x2, /* #87ff00 -> #00aa00 */\n\t0xb, /* #87ff5f -> #ffff55 */\n\t0xb, /* #87ff87 -> #ffff55 */\n\t0x7, /* #87ffaf -> #aaaaaa */\n\t0xe, /* #87ffd7 -> #55ffff */\n\t0xe, /* #87ffff -> #55ffff */\n\t0x1, /* #af0000 -> #aa0000 */\n\t0x5, /* #af005f -> #aa00aa */\n\t0x5, /* #af0087 -> #aa00aa */\n\t0x5, /* #af00af -> #aa00aa */\n\t0x5, /* #af00d7 -> #aa00aa */\n\t0xd, /* #af00ff -> #ff55ff */\n\t0x3, /* #af5f00 -> #aa5500 */\n\t0x9, /* #af5f5f -> #ff5555 */\n\t0x9, /* #af5f87 -> #ff5555 */\n\t0x7, /* #af5faf -> #aaaaaa */\n\t0xd, /* #af5fd7 -> #ff55ff */\n\t0xd, /* #af5fff -> #ff55ff */\n\t0x3, /* #af8700 -> #aa5500 */\n\t0xa, /* #af875f -> #55aa55 */\n\t0x7, /* #af8787 -> #aaaaaa */\n\t0x7, /* #af87af -> #aaaaaa */\n\t0x7, /* #af87d7 -> #aaaaaa */\n\t0xd, /* #af87ff -> #ff55ff */\n\t0x2, /* #afaf00 -> #00aa00 */\n\t0xa, /* #afaf5f -> #55aa55 */\n\t0x7, /* #afaf87 -> #aaaaaa */\n\t0x7, /* #afafaf -> #aaaaaa */\n\t0x7, /* #afafd7 -> #aaaaaa */\n\t0xf, /* #afafff -> #ffffff */\n\t0x2, /* #afd700 -> #00aa00 */\n\t0xb, /* #afd75f -> #ffff55 */\n\t0x7, /* #afd787 -> #aaaaaa */\n\t0x7, /* #afd7af -> #aaaaaa */\n\t0x7, /* #afd7d7 -> #aaaaaa */\n\t0xf, /* #afd7ff -> #ffffff */\n\t0x2, /* #afff00 -> #00aa00 */\n\t0xb, /* #afff5f -> #ffff55 */\n\t0xb, /* #afff87 -> #ffff55 */\n\t0x7, /* #afffaf -> #aaaaaa */\n\t0xf, /* #afffd7 -> #ffffff */\n\t0xf, /* #afffff -> #ffffff */\n\t0x1, /* #d70000 -> #aa0000 */\n\t0x9, /* #d7005f -> #ff5555 */\n\t0x5, /* #d70087 -> #aa00aa */\n\t0x5, /* #d700af -> #aa00aa */\n\t0x5, /* #d700d7 -> #aa00aa */\n\t0xd, /* #d700ff -> #ff55ff */\n\t0x3, /* #d75f00 -> #aa5500 */\n\t0x9, /* #d75f5f -> #ff5555 */\n\t0x9, /* #d75f87 -> #ff5555 */\n\t0x7, /* #d75faf -> #aaaaaa */\n\t0xd, /* #d75fd7 -> #ff55ff */\n\t0xd, /* #d75fff -> #ff55ff */\n\t0x3, /* #d78700 -> #aa5500 */\n\t0x9, /* #d7875f -> #ff5555 */\n\t0x7, /* #d78787 -> #aaaaaa */\n\t0x7, /* #d787af -> #aaaaaa */\n\t0x7, /* #d787d7 -> #aaaaaa */\n\t0xd, /* #d787ff -> #ff55ff */\n\t0x2, /* #d7af00 -> #00aa00 */\n\t0xa, /* #d7af5f -> #55aa55 */\n\t0x7, /* #d7af87 -> #aaaaaa */\n\t0x7, /* #d7afaf -> #aaaaaa */\n\t0x7, /* #d7afd7 -> #aaaaaa */\n\t0xf, /* #d7afff -> #ffffff */\n\t0x2, /* #d7d700 -> #00aa00 */\n\t0xb, /* #d7d75f -> #ffff55 */\n\t0x7, /* #d7d787 -> #aaaaaa */\n\t0x7, /* #d7d7af -> #aaaaaa */\n\t0xf, /* #d7d7d7 -> #ffffff */\n\t0xf, /* #d7d7ff -> #ffffff */\n\t0xb, /* #d7ff00 -> #ffff55 */\n\t0xb, /* #d7ff5f -> #ffff55 */\n\t0xb, /* #d7ff87 -> #ffff55 */\n\t0x7, /* #d7ffaf -> #aaaaaa */\n\t0xf, /* #d7ffd7 -> #ffffff */\n\t0xf, /* #d7ffff -> #ffffff */\n\t0x1, /* #ff0000 -> #aa0000 */\n\t0x9, /* #ff005f -> #ff5555 */\n\t0x5, /* #ff0087 -> #aa00aa */\n\t0x5, /* #ff00af -> #aa00aa */\n\t0x5, /* #ff00d7 -> #aa00aa */\n\t0xd, /* #ff00ff -> #ff55ff */\n\t0x3, /* #ff5f00 -> #aa5500 */\n\t0x9, /* #ff5f5f -> #ff5555 */\n\t0x9, /* #ff5f87 -> #ff5555 */\n\t0x7, /* #ff5faf -> #aaaaaa */\n\t0xd, /* #ff5fd7 -> #ff55ff */\n\t0xd, /* #ff5fff -> #ff55ff */\n\t0x3, /* #ff8700 -> #aa5500 */\n\t0x9, /* #ff875f -> #ff5555 */\n\t0x9, /* #ff8787 -> #ff5555 */\n\t0x7, /* #ff87af -> #aaaaaa */\n\t0xd, /* #ff87d7 -> #ff55ff */\n\t0xd, /* #ff87ff -> #ff55ff */\n\t0x2, /* #ffaf00 -> #00aa00 */\n\t0xb, /* #ffaf5f -> #ffff55 */\n\t0x7, /* #ffaf87 -> #aaaaaa */\n\t0x7, /* #ffafaf -> #aaaaaa */\n\t0x7, /* #ffafd7 -> #aaaaaa */\n\t0xf, /* #ffafff -> #ffffff */\n\t0x2, /* #ffd700 -> #00aa00 */\n\t0xb, /* #ffd75f -> #ffff55 */\n\t0xb, /* #ffd787 -> #ffff55 */\n\t0x7, /* #ffd7af -> #aaaaaa */\n\t0xf, /* #ffd7d7 -> #ffffff */\n\t0xf, /* #ffd7ff -> #ffffff */\n\t0xb, /* #ffff00 -> #ffff55 */\n\t0xb, /* #ffff5f -> #ffff55 */\n\t0xb, /* #ffff87 -> #ffff55 */\n\t0xf, /* #ffffaf -> #ffffff */\n\t0xf, /* #ffffd7 -> #ffffff */\n\t0xf, /* #ffffff -> #ffffff */\n\t0x0, /* #080808 -> #000000 */\n\t0x0, /* #121212 -> #000000 */\n\t0x0, /* #1c1c1c -> #000000 */\n\t0x0, /* #262626 -> #000000 */\n\t0x8, /* #303030 -> #555555 */\n\t0x8, /* #3a3a3a -> #555555 */\n\t0x8, /* #444444 -> #555555 */\n\t0x8, /* #4e4e4e -> #555555 */\n\t0x8, /* #585858 -> #555555 */\n\t0x8, /* #626262 -> #555555 */\n\t0x8, /* #6c6c6c -> #555555 */\n\t0x8, /* #767676 -> #555555 */\n\t0x7, /* #808080 -> #aaaaaa */\n\t0x7, /* #8a8a8a -> #aaaaaa */\n\t0x7, /* #949494 -> #aaaaaa */\n\t0x7, /* #9e9e9e -> #aaaaaa */\n\t0x7, /* #a8a8a8 -> #aaaaaa */\n\t0x7, /* #b2b2b2 -> #aaaaaa */\n\t0x7, /* #bcbcbc -> #aaaaaa */\n\t0x7, /* #c6c6c6 -> #aaaaaa */\n\t0x7, /* #d0d0d0 -> #aaaaaa */\n\t0xf, /* #dadada -> #ffffff */\n\t0xf, /* #e4e4e4 -> #ffffff */\n\t0xf, /* #eeeeee -> #ffffff */\n};\n"
  },
  {
    "path": "apps/wallpaper-picker.c",
    "content": "/**\n * @brief Graphical wallpaper picker.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2019 K. Lange\n */\n#include <signal.h>\n#include <dirent.h>\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/text.h>\n#include <toaru/menu.h>\n#include <toaru/button.h>\n#include <toaru/list.h>\n\n#include <sys/utsname.h>\n\n#define BUTTON_HEIGHT 28\n#define BUTTON_WIDTH 86\n#define BUTTON_PADDING 14\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * window = NULL;\nstatic gfx_context_t * ctx = NULL;\nstatic sprite_t wallpaper = { 0 };\nstatic struct TT_Font * tt_font = NULL;\n\nstatic int32_t width = 640;\nstatic int32_t height = 300;\nstatic char * title_str = \"Wallpaper Picker\";\n#define DEFAULT_PATH \"/usr/share/wallpaper.jpg\"\n#define WALLPAPERS_PATH \"/usr/share/wallpapers\"\n\nstatic char * wallpaper_path;\n\nstatic struct TTKButton _set = {0};\nstatic struct TTKButton _close = {0};\nstatic struct TTKButton _left = {0};\nstatic struct TTKButton _right = {0};\n\nstatic list_t * wallpapers = NULL;\nstatic node_t * current_wallpaper = NULL;\n\nstatic void redraw(void) {\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\t/* Clear to black */\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\t/* Calculate fit */\n\tint max_width = window->width - bounds.width;\n\tint max_height = window->height - bounds.height;\n\t/* Calculate the appropriate scaled size to fit the screen. */\n\tfloat x = (float)max_width / (float)wallpaper.width;\n\tfloat y = (float)max_height / (float)wallpaper.height;\n\n\tint nh = (int)(x * (float)wallpaper.height);\n\tint nw = (int)(y * (float)wallpaper.width);\n\n\t/* Scale the wallpaper into the buffer. */\n\tif (nw <= width) {\n\t\t/* Scaled wallpaper is wider, height should match. */\n\t\tdraw_sprite_scaled(ctx, &wallpaper, bounds.left_width + ((int)max_width - nw) / 2, bounds.top_height, nw+2, max_height);\n\t} else {\n\t\t/* Scaled wallpaper is taller, width should match. */\n\t\tdraw_sprite_scaled(ctx, &wallpaper, bounds.left_width, bounds.top_height + ((int)max_height - nh) / 2, max_width+2, nh);\n\t}\n\n\t/* Draws the path for the selected wallpaper in white, centered, with a drop shadow */\n\ttt_set_size(tt_font, 13);\n\tint str_width = tt_string_width(tt_font, wallpaper_path);\n\tint center_x_text = (window->width - bounds.width - str_width) / 2;\n\ttt_draw_string_shadow(ctx, tt_font, wallpaper_path, 13, center_x_text + 1, bounds.top_height + 10 + 1, rgba(0,0,0,0), rgb(0,0,0), 4);\n\ttt_draw_string_shadow(ctx, tt_font, wallpaper_path, 13, center_x_text + 1, bounds.top_height + 10 + 1, rgb(255,255,255), rgb(0,0,0), 4);\n\n\t/* Draw the buttons */\n\tttk_button_draw(ctx, &_set);\n\tttk_button_draw(ctx, &_close);\n\tttk_button_draw(ctx, &_left);\n\tttk_button_draw(ctx, &_right);\n\n\t/* Draw window decorations */\n\trender_decorations(window, ctx, title_str);\n\n\tflip(ctx);\n\tyutani_flip(yctx, window);\n}\n\nint in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) {\n\tif (me->new_y >= button->y && me->new_y < button->y  + button->height) {\n\t\tif (me->new_x >= button->x && me->new_x < button->x + button->width) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid setup_buttons(void) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\t_set.title = \"Set\";\n\t_set.width = BUTTON_WIDTH;\n\t_set.height = BUTTON_HEIGHT;\n\t_set.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING * 2 - BUTTON_HEIGHT;\n\t_set.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n\n\t_close.title = \"Close\";\n\t_close.width = BUTTON_WIDTH;\n\t_close.height = BUTTON_HEIGHT;\n\t_close.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 3 - BUTTON_HEIGHT;\n\t_close.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING;\n\n\t_left.title = \"<\";\n\t_left.width = BUTTON_HEIGHT;\n\t_left.height = BUTTON_WIDTH;\n\t_left.x = bounds.left_width + BUTTON_PADDING;\n\t_left.y = bounds.top_height + (ctx->height - BUTTON_WIDTH) / 2;\n\n\t_right.title = \">\";\n\t_right.width = BUTTON_HEIGHT;\n\t_right.height = BUTTON_WIDTH;\n\t_right.x = ctx->width - bounds.right_width - BUTTON_HEIGHT - BUTTON_PADDING;\n\t_right.y = bounds.top_height + (ctx->height - BUTTON_WIDTH) / 2;\n}\n\nvoid resize_finish(int w, int h) {\n\tyutani_window_resize_accept(yctx, window, w, h);\n\treinit_graphics_yutani(ctx, window);\n\twidth  = w;\n\theight = h;\n\tsetup_buttons();\n\tredraw();\n\tyutani_window_resize_done(yctx, window);\n}\n\nvoid set_hilight(struct TTKButton * button, int hilight) {\n\tif (!button && (_set.hilight || _close.hilight || _left.hilight || _right.hilight)) {\n\t\t_set.hilight = 0;\n\t\t_close.hilight = 0;\n\t\t_left.hilight = 0;\n\t\t_right.hilight = 0;\n\t\tredraw();\n\t} else if (button && (button->hilight != hilight)) {\n\t\t_set.hilight = 0;\n\t\t_close.hilight = 0;\n\t\t_left.hilight = 0;\n\t\t_right.hilight = 0;\n\t\tbutton->hilight = hilight;\n\t\tredraw();\n\t}\n}\n\nvoid load_wallpaper(void) {\n\tif (wallpaper.bitmap) free(wallpaper.bitmap);\n\twallpaper.bitmap = NULL;\n\t/* load wallpaper */\n\tload_sprite(&wallpaper, wallpaper_path);\n\t/* Ensures we render correctly when scaling */\n\twallpaper.alpha = ALPHA_EMBEDDED;\n}\n\nvoid get_default_wallpaper(void) {\n\tchar * home = getenv(\"HOME\");\n\tif (!home) {\n\t\t/* That should not happen... */\n\t\twallpaper_path = strdup(DEFAULT_PATH);\n\t\treturn;\n\t}\n\n\tchar path[512];\n\tsprintf(path, \"%s/.wallpaper.conf\", home);\n\tFILE * conf = fopen(path,\"r\");\n\tif (!conf) {\n\t\twallpaper_path = strdup(DEFAULT_PATH);\n\t\treturn;\n\t}\n\n\tif (conf) {\n\t\tchar line[1024];\n\t\twhile (!feof(conf)) {\n\t\t\tfgets(line, 1024, conf);\n\t\t\tchar * nl = strchr(line, '\\n');\n\t\t\tif (nl) *nl = '\\0';\n\t\t\tif (line[0] == ';') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (strstr(line, \"wallpaper=\") == line) {\n\t\t\t\twallpaper_path = strdup(line+strlen(\"wallpaper=\"));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfclose(conf);\n\t}\n\n}\n\nvoid set_wallpaper(void) {\n\t/* get the PID of the destkop file-browser */\n\tFILE * f = fopen(\"/var/run/.wallpaper.pid\",\"r\");\n\tif (!f) {\n\t\t/* TODO show an error dialog */\n\t\tfprintf(stderr, \"Failed to read wallpaper PID\\n\");\n\t\treturn;\n\t}\n\tchar data[30];\n\tfgets(data, 30, f);\n\tfclose(f);\n\tint pid = atoi(data);\n\n\t/* write the config file */\n\tchar * home = getenv(\"HOME\");\n\tif (!home) {\n\t\t/* That should not happen... */\n\t\tfprintf(stderr, \"Failed to read HOME envvar\\n\");\n\t\treturn;\n\t}\n\tchar path[512];\n\tsprintf(path, \"%s/.wallpaper.conf\", home);\n\tFILE * conf = fopen(path,\"w\");\n\tfprintf(conf,\"wallpaper=%s\\n\", wallpaper_path);\n\tfprintf(stderr, \"Setting wallpaper to %s\\n\", wallpaper_path);\n\tfclose(conf);\n\n\t/* signal the desktop */\n\tkill(pid, SIGUSR1);\n}\n\nvoid read_wallpapers(void) {\n\twallpapers = list_create();\n\n\t/* Open wallpapers directory */\n\tDIR * dirp = opendir(WALLPAPERS_PATH);\n\n\tif (!dirp) {\n\t\treturn; /* No wallpapers? */\n\t}\n\n\tstruct dirent * ent = readdir(dirp);\n\twhile (ent != NULL) {\n\t\tif (!strcmp(ent->d_name,\".\") || !strcmp(ent->d_name,\"..\")) {\n\t\t\tent = readdir(dirp);\n\t\t\tcontinue;\n\t\t}\n\t\tchar tmp[strlen(WALLPAPERS_PATH)+strlen(ent->d_name)+2];\n\t\tsprintf(tmp, \"%s/%s\", WALLPAPERS_PATH, ent->d_name);\n\n\t\tlist_insert(wallpapers, strdup(tmp));\n\n\t\tent = readdir(dirp);\n\t}\n\tclosedir(dirp);\n}\n\nvoid pick_wallpaper(int dir) {\n\tif (current_wallpaper) {\n\t\tif (dir == 1) {\n\t\t\tcurrent_wallpaper = current_wallpaper->next;\n\t\t} else {\n\t\t\tcurrent_wallpaper = current_wallpaper->prev;\n\t\t}\n\t}\n\tif (!current_wallpaper) {\n\t\tif (dir == 1) {\n\t\t\tcurrent_wallpaper = wallpapers->head;\n\t\t} else {\n\t\t\tcurrent_wallpaper = wallpapers->tail;\n\t\t}\n\t\tif (!current_wallpaper) return; /* No wallpapers */\n\t}\n\n\tfree(wallpaper_path);\n\twallpaper_path = strdup(current_wallpaper->value);\n\tload_wallpaper();\n}\n\nint main(int argc, char * argv[]) {\n\tint req_center_x, req_center_y;\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tinit_decorations();\n\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(NULL, &bounds);\n\n\twindow = yutani_window_create(yctx, width + bounds.width, height + bounds.height);\n\treq_center_x = yctx->display_width / 2;\n\treq_center_y = yctx->display_height / 2;\n\n\ttt_font = tt_font_from_shm(\"sans-serif\");\n\n\tget_default_wallpaper();\n\tread_wallpapers();\n\n\tyutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);\n\n\tyutani_window_advertise_icon(yctx, window, title_str, \"wallpaper-picker\");\n\n\tctx = init_graphics_yutani_double_buffer(window);\n\tsetup_buttons();\n\tload_wallpaper();\n\tredraw();\n\n\tstruct TTKButton * _down_button = NULL;\n\n\tint playing = 1;\n\twhile (playing) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\twhile (m) {\n\t\t\tif (menu_process_event(yctx, m)) {\n\t\t\t\tredraw();\n\t\t\t}\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == '\\n') {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t} else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) {\n\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * wf = (void*)m->data;\n\t\t\t\t\t\tyutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid);\n\t\t\t\t\t\tif (win) {\n\t\t\t\t\t\t\twin->focused = wf->focused;\n\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tresize_finish(wr->width, wr->height);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tif (me->wid == window->wid) {\n\t\t\t\t\t\t\tint result = decor_handle_event(yctx, m);\n\t\t\t\t\t\t\tswitch (result) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RIGHT:\n\t\t\t\t\t\t\t\t\t/* right click in decoration, show appropriate menu */\n\t\t\t\t\t\t\t\t\tdecor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t/* Other actions */\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\t\t\tdecor_get_bounds(window, &bounds);\n\t\t\t\t\t\t\tif (me->new_y > bounds.top_height) {\n\n\t\t\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_set, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_set, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_set;\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_close, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_close, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_close;\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_left, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_left, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_left;\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_right, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_right, 2);\n\t\t\t\t\t\t\t\t\t\t_down_button = &_right;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) {\n\t\t\t\t\t\t\t\t\tif (_down_button) {\n\t\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\t\tif (_down_button == &_close) {\n\t\t\t\t\t\t\t\t\t\t\t\tplaying = 0;\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button == &_set) {\n\t\t\t\t\t\t\t\t\t\t\t\t/* Set wallpaper */\n\t\t\t\t\t\t\t\t\t\t\t\tset_wallpaper();\n\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button == &_left) {\n\t\t\t\t\t\t\t\t\t\t\t\t/* Previous wallpaper */\n\t\t\t\t\t\t\t\t\t\t\t\tpick_wallpaper(-1);\n\t\t\t\t\t\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\t\t\t\t\t} else if (_down_button == &_right) {\n\t\t\t\t\t\t\t\t\t\t\t\t/* Next wallpaper */\n\t\t\t\t\t\t\t\t\t\t\t\tpick_wallpaper(1);\n\t\t\t\t\t\t\t\t\t\t\t\tredraw();\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t_down_button->hilight = 0;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t_down_button = NULL;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\t\t\tif (in_button(&_set, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_set, 1);\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_close, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_close, 1);\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_left, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_left, 1);\n\t\t\t\t\t\t\t\t\t} else if (in_button(&_right, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(&_right, 1);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL,0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (_down_button) {\n\t\t\t\t\t\t\t\t\tif (in_button(_down_button, me)) {\n\t\t\t\t\t\t\t\t\t\tset_hilight(_down_button, 2);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tset_hilight(NULL, 0);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tplaying = 0;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(m);\n\t\t\tm = yutani_poll_async(yctx);\n\t\t}\n\t}\n\n\tyutani_close(yctx, window);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/wc.c",
    "content": "/**\n * @brief wc - count bytes, characters, words, lines...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <getopt.h>\n#include <errno.h>\n#include <toaru/decodeutf8.h>\n\nint main(int argc, char * argv[]) {\n\tint show_lines = 0;\n\tint show_words = 0;\n\tint show_chars = 0;\n\tint show_bytes = 0;\n\n\tint opt;\n\n\twhile ((opt = getopt(argc,argv,\"cmlw\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'c':\n\t\t\t\tshow_bytes = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'm':\n\t\t\t\tshow_chars = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tshow_lines = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\tshow_words = 1;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tint retval = 0;\n\tint total_lines = 0;\n\tint total_chars = 0;\n\tint total_words = 0;\n\tint just_stdin = 0;\n\n\tif (optind == argc) {\n\t\targv[optind] = \"\";\n\t\targc++;\n\t\tjust_stdin = 1;\n\t}\n\n\tfor (int i = optind; i < argc; ++i) {\n\t\tif (!*argv[i] && !just_stdin) {\n\t\t\tfprintf(stderr, \"%s: invalid zero-length file name\\n\", argv[0]);\n\t\t\tretval = 1;\n\t\t\tcontinue;\n\t\t}\n\t\tFILE * f = (!strcmp(argv[i], \"-\") || just_stdin) ? stdin : fopen(argv[i], \"r\");\n\t\tif (!f) {\n\t\t\tfprintf(stderr, \"%s: %s: %s\\n\", argv[0], argv[i], strerror(errno));\n\t\t\tretval = 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tint lines = 0;\n\t\tint chars = 0;\n\t\tint words = 0;\n\t\tint ch;\n\t\tuint32_t state, c;\n\t\tint last_was_whitespace = 0;\n\n\t\twhile (!feof(f)) {\n\t\t\tch = getc(f);\n\t\t\tif (ch < 0) break;\n\n\t\t\tif (show_chars) {\n\t\t\t\tif (!decode(&state, &c, ch)) {\n\t\t\t\t} else if (state == UTF8_REJECT) {\n\t\t\t\t\tstate = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tc = ch;\n\t\t\t}\n\n\t\t\tchars++;\n\t\t\tif (c == '\\n') {\n\t\t\t\tlast_was_whitespace = 1;\n\t\t\t\tlines++;\n\t\t\t\twords++;\n\t\t\t} else if (c == ' ') {\n\t\t\t\tif (last_was_whitespace) continue;\n\t\t\t\tlast_was_whitespace = 1;\n\t\t\t\twords++;\n\t\t\t} else {\n\t\t\t\tlast_was_whitespace = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (!last_was_whitespace && chars > 0) words++;\n\n\t\tif (!show_words && !show_chars && !show_bytes && !show_lines) {\n\t\t\tfprintf(stdout, \"%d %d %d %s\\n\", lines, words, chars, argv[i]);\n\t\t} else {\n\t\t\tif (show_lines) fprintf(stdout, \"%d \", lines);\n\t\t\tif (show_words) fprintf(stdout, \"%d \", words);\n\t\t\tif (show_bytes|show_chars) fprintf(stdout, \"%d \", chars);\n\t\t\tfprintf(stdout, \"%s\\n\", argv[i]);\n\t\t}\n\n\t\ttotal_lines += lines;\n\t\ttotal_words += words;\n\t\ttotal_chars += chars;\n\n\t\tif (f != stdin) fclose(f);\n\t\tif (just_stdin) return 0;\n\t}\n\n\tif (optind + 1 < argc) {\n\t\tif (!show_words && !show_chars && !show_bytes && !show_lines) {\n\t\t\tfprintf(stdout, \"%d %d %d %s\\n\", total_lines, total_words, total_chars, \"total\");\n\t\t} else {\n\t\t\tif (show_lines) fprintf(stdout, \"%d \", total_lines);\n\t\t\tif (show_words) fprintf(stdout, \"%d \", total_words);\n\t\t\tif (show_bytes|show_chars) fprintf(stdout, \"%d \", total_chars);\n\t\t\tfprintf(stdout, \"%s\\n\", \"total\");\n\t\t}\n\t}\n\n\treturn retval;\n}\n\n"
  },
  {
    "path": "apps/weather-configurator.c",
    "content": "/**\n * @brief Configure the weather information 'daemon'\n *\n * Messes with /etc/weather.json so it needs root for now...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <toaru/json.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n\ntypedef struct JSON_Value Value;\n\nint main(int argc, char * argv[]) {\n\n\tValue * config = json_parse_file(\"/etc/weather.json\");\n\tif (config) {\n\t\tchar * city = JSON_KEY(config, \"city\")->string;\n\t\tchar * key = JSON_KEY(config, \"key\")->string;\n\t\tchar * __comment = JSON_KEY(config, \"--comment\")->string;\n\t\tchar * units = JSON_KEY(config, \"units\")->string;\n\n\t\tfprintf(stdout, \"City? [%s] \", city);\n\t\tfflush(stdout);\n\n\t\tchar ncity[100];\n\t\tfgets(ncity, 100, stdin);\n\n\t\tif (ncity[0] != '\\n') {\n\t\t\tchar * n = strstr(ncity, \"\\n\");\n\t\t\tif (n) *n = '\\0';\n\t\t\tcity = ncity;\n\t\t}\n\n\t\tfprintf(stdout, \"Units? [%s] \", units);\n\t\tfflush(stdout);\n\n\t\tchar nunits[100];\n\t\tfgets(nunits, 100, stdin);\n\n\t\tif (nunits[0] != '\\n') {\n\t\t\tchar * n = strstr(nunits, \"\\n\");\n\t\t\tif (n) *n = '\\0';\n\t\t\tunits = nunits;\n\t\t}\n\n\t\tFILE * f = fopen(\"/etc/weather.json\", \"w\");\n\t\tfprintf(f, \"{\\n\");\n\t\tfprintf(f, \"    \\\"city\\\": \\\"%s\\\",\\n\", city);\n\t\tfprintf(f, \"    \\\"units\\\": \\\"%s\\\",\\n\", units);\n\t\tfprintf(f, \"\\n\");\n\t\tfprintf(f, \"    \\\"--comment\\\": \\\"%s\\\",\\n\", __comment);\n\t\tfprintf(f, \"    \\\"key\\\": \\\"%s\\\"\\n\", key);\n\t\tfprintf(f, \"}\\n\");\n\t\tfclose(f);\n\n\t} else {\n\t\tfprintf(stderr, \"Configuration is not set. A key is required. Please create the file manually.\\n\");\n\t\tfprintf(stderr, \"(Press ENTER to exit.)\\n\");\n\t\tgetchar();\n\t\treturn 0;\n\t}\n}\n"
  },
  {
    "path": "apps/weather-tool.c",
    "content": "/**\n * @brief Ask OpenWeather for forecast data.\n *\n * Fetches weather forecast data from OpenWeather and converts the JSON\n * output to a simpler parsed format we read in the panel.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/time.h>\n#include <toaru/json.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n\ntypedef struct JSON_Value Value;\n\n#define WEATHER_CONF_PATH  \"/etc/weather.json\"\n#define WEATHER_DATA_PATH  \"/tmp/weather-data.json\"\n#define WEATHER_OUT_PATH   \"/tmp/weather-parsed.conf\"\n#define LOCATION_DATA_PATH \"/tmp/location-data.json\"\n\nint main(int argc, char * argv[]) {\n\tValue * config = json_parse_file(WEATHER_CONF_PATH);\n\tif (!config) {\n\t\tfprintf(stderr, \"No weather config data\\n\");\n\t\treturn 1;\n\t}\n\n\tchar * city = JSON_KEY(config, \"city\")->string;\n\tchar * key = JSON_KEY(config, \"key\")->string;\n\tchar * units = JSON_KEY(config, \"units\")->string;\n\tchar cmdline[1024];\n\n\t/* If the city is 'guess', we'll make a single query to a separate service to\n\t * get a location from the user's external IP... */\n\tif (!strcmp(city, \"guess\")) {\n\t\t/* See if the location data already exists... */\n\t\tValue * locationData = json_parse_file(LOCATION_DATA_PATH);\n\t\tif (!locationData) {\n\t\t\tsprintf(cmdline, \"fetch -o \\\"\" LOCATION_DATA_PATH \"\\\" \\\"http://ip-api.com/json/?fields=lat,lon,city,offset\\\"\");\n\t\t\tsystem(cmdline);\n\t\t\tlocationData = json_parse_file(LOCATION_DATA_PATH);\n\t\t}\n\t\t/* If we still failed to load it, then bail. */\n\t\tif (!locationData) {\n\t\t\tfprintf(stderr, \"%s: city field was set to 'guess' but failed to acquire data from IP geolocation service\\n\", argv[0]);\n\t\t\treturn 1;\n\t\t}\n\n\t\tcity = JSON_KEY(locationData, \"city\")->string;\n\t\tdouble lat = JSON_KEY(locationData, \"lat\")->number;\n\t\tdouble lon = JSON_KEY(locationData, \"lon\")->number;\n\n\t\tsprintf(cmdline, \"fetch -o \\\"\" WEATHER_DATA_PATH \"\\\" \\\"http://api.openweathermap.org/data/2.5/weather?lat=%.5lf&lon=%.5lf&appid=%s&units=%s\\\"\", lat, lon, key, units);\n\t\tsystem(cmdline);\n\t} else {\n\t\tsprintf(cmdline, \"fetch -o \\\"\" WEATHER_DATA_PATH \"\\\" \\\"http://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s\\\"\", city, key, units);\n\t\tsystem(cmdline);\n\t}\n\n\tValue * result = json_parse_file(WEATHER_DATA_PATH);\n\tassert(result && result->type == JSON_TYPE_OBJECT);\n\n\tValue * _main = JSON_KEY(result,\"main\");\n\tValue * conditions = (JSON_KEY(result,\"weather\") && JSON_KEY(result,\"weather\")->array->length > 0) ?\n\t\tJSON_IND(JSON_KEY(result,\"weather\"),0) : NULL;\n\n\tFILE * out = fopen(WEATHER_OUT_PATH, \"w\");\n\n\t/**\n\t * The format for a parsed weather payload is a series of line-separated entries:\n\t * - Formatted temperature, with decimal.\n\t * - Integral temperature, eg. for the panel widget.\n\t * - Main weather conditions string, eg. \"Clouds\"\n\t * - Icon identifier, eg. 02d is \"cloudy, daytime\".\n\t * - Humidity (integer, percentage)\n\t * - Cloud coverage (integer, percentage)\n\t * - City name (we're using the guessed location, not the one from the weather provider...)\n\t * - Date string of last update\n\t */\n\tfprintf(out, \"%.2lf\\n\", JSON_KEY(_main,\"temp\")->number);\n\tfprintf(out, \"%d\\n\", (int)JSON_KEY(_main,\"temp\")->number);\n\tfprintf(out, \"%s\\n\", conditions ? JSON_KEY(conditions,\"main\")->string : \"\");\n\tfprintf(out, \"%s\\n\", conditions ? JSON_KEY(conditions,\"icon\")->string : \"\");\n\tfprintf(out, \"%d\\n\", (int)JSON_KEY(_main,\"humidity\")->number);\n\tfprintf(out, \"%d\\n\", JSON_KEY(JSON_KEY(result,\"clouds\"),\"all\") ? (int)JSON_KEY(JSON_KEY(result,\"clouds\"),\"all\")->number : 0);\n\tfprintf(out, \"%s\\n\", city);\n\tchar * format = \"%a, %d %b %Y %H:%M:%S\\n\";\n\tstruct tm * timeinfo;\n\tstruct timeval now;\n\tchar buf[BUFSIZ] = {0};\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tstrftime(buf,BUFSIZ,format,timeinfo);\n\tfprintf(out, buf);\n\n\tfprintf(out, \"%d\\n\", (int)JSON_KEY(_main,\"pressure\")->number);\n\n\tfclose(out);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/which.c",
    "content": "/**\n * @brief which - Figure out which binary will be used\n *\n * Searches through $PATH to find a matching binary, just like\n * how execp* family does it. (Except does our execp actually\n * bother checking permissions? Look into this...)\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2014 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/stat.h>\n\n#define DEFAULT_PATH \"/bin:/usr/bin\"\n\nint main(int argc, char * argv[]) {\n\n\tint ret_val = 0;\n\tint i = 1;\n\tint print_all = 0;\n\n\tif (i < argc && !strcmp(argv[i],\"-a\")) {\n\t\tprint_all = 1;\n\t\ti++;\n\t}\n\n\tif (i == argc) {\n\t\treturn 1;\n\t}\n\n\tfor (; i < argc; ++i) {\n\n\t\tif (strstr(argv[i], \"/\")) {\n\t\t\tstruct stat t;\n\t\t\tif (!stat(argv[i], &t)) {\n\t\t\t\tif ((t.st_mode & 0111)) {\n\t\t\t\t\tprintf(\"%s\\n\", argv[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tchar * file = argv[i];\n\t\t\tchar * path = getenv(\"PATH\");\n\t\t\tif (!path) {\n\t\t\t\tpath = DEFAULT_PATH;\n\t\t\t}\n\n\t\t\tchar * xpath = strdup(path);\n\t\t\tchar * p, * last;\n\t\t\tint found = 0;\n\t\t\tfor ((p = strtok_r(xpath, \":\", &last)); p; p = strtok_r(NULL, \":\", &last)) {\n\t\t\t\tint r;\n\t\t\t\tstruct stat stat_buf;\n\t\t\t\tchar * exe = malloc(strlen(p) + strlen(file) + 2);\n\t\t\t\tstrcpy(exe, p);\n\t\t\t\tstrcat(exe, \"/\");\n\t\t\t\tstrcat(exe, file);\n\n\t\t\t\tr = stat(exe, &stat_buf);\n\t\t\t\tif (r != 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!(stat_buf.st_mode & 0111)) {\n\t\t\t\t\tcontinue; /* XXX not technically correct; need to test perms */\n\t\t\t\t}\n\t\t\t\tfound = 1;\n\t\t\t\tprintf(\"%s\\n\", exe);\n\t\t\t\tif (print_all) continue;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tfree(xpath);\n\t\t\tif (!found) ret_val = 1;\n\t\t}\n\t}\n\treturn ret_val;\n}\n"
  },
  {
    "path": "apps/whoami.c",
    "content": "/**\n * @brief uses getpwuid and geteuid to retrieve the current user's name.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <pwd.h>\n\nint main(int argc, char ** argv) {\n\tstruct passwd * p = getpwuid(geteuid());\n\tif (!p) return 0;\n\n\tfprintf(stdout, \"%s\\n\", p->pw_name);\n\n\tendpwent();\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/yes.c",
    "content": "/**\n * @brief yes - Continually print stuff\n *\n * Continually prints its first argument, followed by a newline.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013 K Lange\n */\n#include <stdio.h>\n\nint main(int argc, char * argv[]) {\n\tchar * yes_string = \"y\";\n\tif (argc > 1) {\n\t\tyes_string = argv[1];\n\t}\n\twhile (1) {\n\t\tprintf(\"%s\\n\", yes_string);\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/yutani-clipboard.c",
    "content": "/**\n * @brief yutani-clipboard - Manipulate the Yutani clipboard\n *\n * Gets and sets clipboard values.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <getopt.h>\n\n#include <toaru/yutani.h>\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"yutani-clipboard - set and obtain clipboard contents\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s -g\\n\"\n\t\t\t\"       %s -s TEXT...\\n\"\n\t\t\t\"       %s -f FILE\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -s     \\033[3mset the clipboard text to argument\\033[0m\\n\"\n\t\t\t\" -f     \\033[3mset the clibboard text to file\\033[0m\\n\"\n\t\t\t\" -g     \\033[3mprint clipboard contents to stdout\\033[0m\\n\"\n\t\t\t\" -n     \\033[3mensure a linefeed is printed\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0], argv[0], argv[0]);\n}\n\nyutani_t * yctx;\nint force_linefeed = 0;\n\nint set_clipboard_from_file(char * file) {\n\tFILE * f;\n\n\tf = fopen(file, \"r\");\n\tif (!f) return 1;\n\n\tfseek(f, 0, SEEK_END);\n\tsize_t size = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\n\tchar * tmp = malloc(size+1);\n\tfread(tmp, 1, size, f);\n\ttmp[size] = '\\0';\n\n\tyutani_set_clipboard(yctx, tmp);\n\n\tfree(tmp);\n\n\treturn 0;\n}\n\nvoid get_clipboard(void) {\n\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD);\n\tyutani_msg_t * clipboard = yutani_wait_for(yctx, YUTANI_MSG_CLIPBOARD);\n\tstruct yutani_msg_clipboard * cb = (void *)clipboard->data;\n\n\tif (*cb->content == '\\002') {\n\t\tint size = atoi(&cb->content[2]);\n\t\tFILE * clipboard = yutani_open_clipboard(yctx);\n\t\tchar * selection_text = malloc(size + 1);\n\t\tfread(selection_text, 1, size, clipboard);\n\t\tselection_text[size] = '\\0';\n\t\tfclose(clipboard);\n\t\tfwrite(selection_text, 1, size, stdout);\n\t\tif (force_linefeed && size && selection_text[size-1] != '\\n') {\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t} else {\n\t\tchar * selection_text = malloc(cb->size+1);\n\t\tmemcpy(selection_text, cb->content, cb->size);\n\t\tselection_text[cb->size] = '\\0';\n\t\tfwrite(selection_text, 1, cb->size, stdout);\n\t\tif (force_linefeed && cb->size && selection_text[cb->size-1] != '\\n') {\n\t\t\tprintf(\"\\n\");\n\t\t}\n\t}\n\n}\n\nint main(int argc, char * argv[]) {\n\tyctx = yutani_init();\n\tif (!yctx) {\n\t\tfprintf(stderr, \"%s: failed to connect to compositor\\n\", argv[0]);\n\t\treturn 1;\n\t}\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?s:f:gn\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 's':\n\t\t\t\tyutani_set_clipboard(yctx, optarg);\n\t\t\t\treturn 0;\n\t\t\tcase 'f':\n\t\t\t\treturn set_clipboard_from_file(optarg);\n\t\t\tcase 'n':\n\t\t\t\tforce_linefeed = 1;\n\t\t\t\tbreak;\n\t\t\tcase 'g':\n\t\t\t\tget_clipboard();\n\t\t\t\treturn 0;\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc,argv);\n\t\t\t\treturn 1;\n\t\t}\n\t}\n\n\tshow_usage(argc, argv);\n\treturn 1;\n}\n"
  },
  {
    "path": "apps/yutani-kbd.c",
    "content": "/**\n * @brief Debug tool for keyboard input.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\nstatic int left, top, width, height;\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx;\nstatic int should_exit = 0;\n\nchar * modifiers(unsigned int m) {\n\tstatic char out[] = \"........\";\n\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_CTRL)   out[0] = 'c'; else out[0] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_SHIFT)  out[1] = 's'; else out[1] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_ALT)    out[2] = 'a'; else out[2] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_SUPER)  out[3] = 'x'; else out[3] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_CTRL)  out[4] = 'c'; else out[4] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_SHIFT) out[5] = 's'; else out[5] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_ALT)   out[6] = 'a'; else out[6] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_SUPER) out[7] = 'x'; else out[7] = '.';\n\n\treturn out;\n}\n\nvoid redraw(void) {\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\tint w = width - 1, h = height - 1;\n\n\tdraw_line(ctx, 0, w, 0, 0, rgb(255,255,255));\n\tdraw_line(ctx, 0, w, h, h, rgb(255,255,255));\n\n\tdraw_line(ctx, 0, 0, 0, h, rgb(255,255,255));\n\tdraw_line(ctx, w, w, 0, h, rgb(255,255,255));\n}\n\nint main (int argc, char ** argv) {\n\tleft   = 100;\n\ttop    = 100;\n\twidth  = 500;\n\theight = 500;\n\n\tyctx = yutani_init();\n\twina = yutani_window_create(yctx, width, height);\n\tyutani_window_move(yctx, wina, left, top);\n\n\tctx = init_graphics_yutani(wina);\n\n\tredraw();\n\n\tchar keys[256] = {0};\n\n\tprintf(\"\\033[H\\033[2J\");\n\n\twhile (!should_exit) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\tif (m) {\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tif (ke->event.keycode >= 'a' && ke->event.keycode < 'z') {\n\t\t\t\t\t\t\tkeys[ke->event.keycode] = (ke->event.action == KEY_ACTION_DOWN);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprintf(\"\\033[1;1H\");\n\t\t\t\t\t\tfor (int i = 'a'; i < 'z'; ++i) {\n\t\t\t\t\t\t\tprintf(\"\\033[%dm%c \", keys[i] ? 0 : 31, i);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfflush(stdout);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfree(m);\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/yutani-query.c",
    "content": "/**\n * @brief yutani-query - Query display server information\n *\n * At the moment, this only supports querying the display\n * resolution. An older version of this application had\n * support for getting the default font names, but the\n * font server is no longer part of the compositor, so\n * that functionality doesn't make sense here.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n\n#include <toaru/yutani.h>\n\nyutani_t * yctx;\nint quiet = 0;\n\nvoid show_usage(int argc, char * argv[]) {\n\tprintf(\n\t\t\t\"yutani-query - show misc. information about the display system\\n\"\n\t\t\t\"\\n\"\n\t\t\t\"usage: %s [-r?]\\n\"\n\t\t\t\"\\n\"\n\t\t\t\" -r     \\033[3mprint display resoluton\\033[0m\\n\"\n\t\t\t\" -e     \\033[3mask compositor to reload extensions\\033[0m\\n\"\n\t\t\t\" -?     \\033[3mshow this help text\\033[0m\\n\"\n\t\t\t\"\\n\", argv[0]);\n}\n\nint show_resolution(void) {\n\tif (!yctx) {\n\t\tif (!quiet) printf(\"(not connected)\\n\");\n\t\treturn 1;\n\t}\n\tprintf(\"%dx%d\\n\", (int)yctx->display_width, (int)yctx->display_height);\n\treturn 0;\n}\n\nint reload(void) {\n\tif (!yctx) {\n\t\tif (!quiet) printf(\"(not connected)\\n\");\n\t\treturn 1;\n\t}\n\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_RELOAD);\n\treturn 0;\n}\n\nint main(int argc, char * argv[]) {\n\tyctx = yutani_init();\n\tint opt;\n\twhile ((opt = getopt(argc, argv, \"?qre\")) != -1) {\n\t\tswitch (opt) {\n\t\t\tcase 'q':\n\t\t\t\tquiet = 1;\n\t\t\t\tbreak;\n\n\t\t\t/* Legacy options */\n\t\t\tcase 'r':\n\t\t\t\treturn show_resolution();\n\t\t\tcase 'e':\n\t\t\t\treturn reload();\n\n\t\t\tcase '?':\n\t\t\t\tshow_usage(argc,argv);\n\t\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif (optind < argc) {\n\t\tif (!strcmp(argv[optind], \"resolution\")) {\n\t\t\treturn show_resolution();\n\t\t} else if (!strcmp(argv[optind], \"reload\")) {\n\t\t\treturn reload();\n\t\t} else {\n\t\t\tfprintf(stderr, \"%s: unsupported command: %s\\n\", argv[0], argv[optind]);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/yutani-test.c",
    "content": "/**\n * @brief yutani-test - Yutani Test Tool\n *\n * Kinda like xev: Pops up a window and displays events in a\n * human-readable format.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2018 K. Lange\n */\n#include <stdlib.h>\n#include <assert.h>\n#include <unistd.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n\nstatic int left, top, width, height;\n\nstatic yutani_t * yctx;\nstatic yutani_window_t * wina;\nstatic gfx_context_t * ctx;\nstatic int should_exit = 0;\n\nconst char * action_name(unsigned int action) {\n\tswitch (action) {\n\t\tcase KEY_ACTION_UP:\n\t\t\treturn \"up\";\n\t\tcase KEY_ACTION_DOWN:\n\t\t\treturn \"down\";\n\t\tdefault:\n\t\t\treturn \"?\";\n\t}\n}\n\nchar * modifiers(unsigned int m) {\n\tstatic char out[] = \"........\";\n\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_CTRL)   out[0] = 'c'; else out[0] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_SHIFT)  out[1] = 's'; else out[1] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_ALT)    out[2] = 'a'; else out[2] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_LEFT_SUPER)  out[3] = 'x'; else out[3] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_CTRL)  out[4] = 'c'; else out[4] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_SHIFT) out[5] = 's'; else out[5] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_ALT)   out[6] = 'a'; else out[6] = '.';\n\tif (m & YUTANI_KEY_MODIFIER_RIGHT_SUPER) out[7] = 'x'; else out[7] = '.';\n\n\treturn out;\n}\n\nchar * mouse_buttons(unsigned char button) {\n\tstatic char out[] = \"....\";\n\n\tif (button & YUTANI_MOUSE_BUTTON_LEFT)   out[0] = 'l'; else out[0] = '.';\n\tif (button & YUTANI_MOUSE_BUTTON_MIDDLE) out[1] = 'm'; else out[1] = '.';\n\tif (button & YUTANI_MOUSE_BUTTON_RIGHT)  out[2] = 'r'; else out[2] = '.';\n\tif (button & YUTANI_MOUSE_SCROLL_UP)     out[3] = 'u'; else \\\n\tif (button & YUTANI_MOUSE_SCROLL_DOWN)   out[3] = 'd'; else out[3] = '.';\n\n\treturn out;\n}\n\nconst char * mouse_command(unsigned char type) {\n\tswitch (type) {\n\t\tcase (YUTANI_MOUSE_EVENT_CLICK):\n\t\t\treturn \"click\";\n\t\tcase (YUTANI_MOUSE_EVENT_DRAG ):\n\t\t\treturn \"drag\";\n\t\tcase (YUTANI_MOUSE_EVENT_RAISE):\n\t\t\treturn \"raise\";\n\t\tcase (YUTANI_MOUSE_EVENT_DOWN ):\n\t\t\treturn \"down\";\n\t\tcase (YUTANI_MOUSE_EVENT_MOVE ):\n\t\t\treturn \"move\";\n\t\tcase (YUTANI_MOUSE_EVENT_LEAVE):\n\t\t\treturn \"leave\";\n\t\tcase (YUTANI_MOUSE_EVENT_ENTER):\n\t\t\treturn \"enter\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\nvoid redraw(void) {\n\tdraw_fill(ctx, rgb(0,0,0));\n\n\tint w = width - 1, h = height - 1;\n\n\tdraw_line(ctx, 0, w, 0, 0, rgb(255,255,255));\n\tdraw_line(ctx, 0, w, h, h, rgb(255,255,255));\n\n\tdraw_line(ctx, 0, 0, 0, h, rgb(255,255,255));\n\tdraw_line(ctx, w, w, 0, h, rgb(255,255,255));\n\n\tyutani_flip(yctx, wina);\n}\n\nint main (int argc, char ** argv) {\n\tint show_cursor = 1;\n\n\tleft   = 100;\n\ttop    = 100;\n\twidth  = 500;\n\theight = 500;\n\n\tyctx = yutani_init();\n\twina = yutani_window_create(yctx, width, height);\n\tyutani_window_move(yctx, wina, left, top);\n\n\tctx = init_graphics_yutani(wina);\n\n\tredraw();\n\n\twhile (!should_exit) {\n\t\tyutani_msg_t * m = yutani_poll(yctx);\n\t\tif (m) {\n\t\t\tswitch (m->type) {\n\t\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_key_event * ke = (void*)m->data;\n\t\t\t\t\t\tfprintf(stderr, \"Key Press (wid=%d) %s\\n\"\n\t\t\t\t\t\t\t\"\\tevent.action = %d\\n\"\n\t\t\t\t\t\t\t\"\\tevent.keycode = %d\\n\"\n\t\t\t\t\t\t\t\"\\tevent.modifiers = %s\\n\"\n\t\t\t\t\t\t\t\"\\tevent.key = %d (%c)\\n\",\n\t\t\t\t\t\t\tke->wid,\n\t\t\t\t\t\t\taction_name(ke->event.action),\n\t\t\t\t\t\t\tke->event.action,\n\t\t\t\t\t\t\tke->event.keycode,\n\t\t\t\t\t\t\tmodifiers(ke->event.modifiers),\n\t\t\t\t\t\t\tke->event.key, ke->event.key == 0 ? '?' : ke->event.key);\n\n\t\t\t\t\t\tif (ke->event.key == 'm' && ke->event.action == KEY_ACTION_DOWN) {\n\t\t\t\t\t\t\tshow_cursor = !show_cursor;\n\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, wina, show_cursor);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\t\tfprintf(stderr, \"Mouse Event (wid=%d) %s\\n\"\n\t\t\t\t\t\t\t\"\\tnew = %d, %d\\n\"\n\t\t\t\t\t\t\t\"\\told = %d, %d\\n\"\n\t\t\t\t\t\t\t\"\\tbuttons = %s\\n\"\n\t\t\t\t\t\t\t\"\\tmodifiers = %s\\n\"\n\t\t\t\t\t\t\t\"\\tcommand = %d\\n\",\n\t\t\t\t\t\t\t(int)me->wid,\n\t\t\t\t\t\t\tmouse_command(me->command),\n\t\t\t\t\t\t\t(int)me->new_x, (int)me->new_y,\n\t\t\t\t\t\t\t(int)me->old_x, (int)me->old_y,\n\t\t\t\t\t\t\tmouse_buttons(me->buttons),\n\t\t\t\t\t\t\tmodifiers(me->modifiers),\n\t\t\t\t\t\t\tme->command);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_focus_change * fc = (void*)m->data;\n\t\t\t\t\t\tfprintf(stderr, \"Focus Change (wid=%d) %s\\n\", fc->wid, fc->focused ? \"on\" : \"off\");\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_MOVE:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_move * wm = (void*)m->data;\n\t\t\t\t\t\tfprintf(stderr, \"Window Moved (wid=%d) %d, %d\\n\", (int)wm->wid, (int)wm->x, (int)wm->y);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t\t\t{\n\t\t\t\t\t\tstruct yutani_msg_window_resize * wr = (void*)m->data;\n\t\t\t\t\t\tfprintf(stderr, \"Resize Offer (wid=%d) %d x %d\\n\"\n\t\t\t\t\t\t\t\"\\tbufid = %d\\n\",\n\t\t\t\t\t\t\t(int)wr->wid,\n\t\t\t\t\t\t\t(int)wr->width, (int)wr->height,\n\t\t\t\t\t\t\t(int)wr->bufid);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\t\tcase YUTANI_MSG_SESSION_END:\n\t\t\t\t\tshould_exit = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tfree(m);\n\t}\n\n\tyutani_close(yctx, wina);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "apps/yutani-tty-pipe.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <termios.h>\n\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n\nyutani_t * yctx;\nint quiet = 0;\n\nint show_resolution(void) {\n\tif (!yctx) {\n\t\tif (!quiet) printf(\"(not connected)\\n\");\n\t\treturn 1;\n\t}\n\tprintf(\"%dx%d\\n\", (int)yctx->display_width, (int)yctx->display_height);\n\treturn 0;\n}\n\nint reload(void) {\n\tif (!yctx) {\n\t\tif (!quiet) printf(\"(not connected)\\n\");\n\t\treturn 1;\n\t}\n\tyutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_RELOAD);\n\treturn 0;\n}\n\nstruct termios old;\n\nvoid set_unbuffered() {\n\ttcgetattr(fileno(stdin), &old);\n\tstruct termios new = old;\n\tnew.c_lflag &= (~ICANON & ~ECHO);\n\ttcsetattr(fileno(stdin), TCSAFLUSH, &new);\n}\n\n\nint main(int argc, char * argv[]) {\n\tyctx = yutani_init();\n\n\tif (!yctx) {\n\t\tfprintf(stderr, \"not connected; did you set $DISPLAY?\\n\");\n\t\treturn 1;\n\t}\n\n\tset_unbuffered();\n\n\tint c;\n\twhile ((c = fgetc(stdin))) {\n\t\tkey_event_t event = {0};\n\t\tkey_event_state_t state = {0};\n\n\t\tevent.keycode = c;\n\t\tevent.key = c;\n\n\t\tswitch (c) {\n\t\t\tcase 27:\n\t\t\t\tevent.keycode = KEY_ESCAPE;\n\t\t\t\tevent.key = KEY_ESCAPE;\n\t\t\t\tbreak;\n\t\t\t/* Either of the backspace keys */\n\t\t\tcase 8:\n\t\t\tcase 0x7f:\n\t\t\t\tevent.keycode = 8;\n\t\t\t\tevent.key = 8;\n\t\t\t\tbreak;\n\t\t\t/* Any of \\r or \\n */\n\t\t\tcase '\\r':\n\t\t\tcase '\\n':\n\t\t\t\tevent.keycode = '\\n';\n\t\t\t\tevent.key = '\\n';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tevent.action = KEY_ACTION_DOWN;\n\n\t\tyutani_msg_buildx_key_event_alloc(m_);\n\t\tyutani_msg_buildx_key_event(m_, 0, &event, &state);\n\t\tyutani_msg_send(yctx, m_);\n\n\t\tevent.action = KEY_ACTION_UP;\n\t\tyutani_msg_buildx_key_event(m_, 0, &event, &state);\n\t\tyutani_msg_send(yctx, m_);\n\t}\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "apps/zcat.c",
    "content": "#include <unistd.h>\n#include <stdlib.h>\n\nint main(int argc, char * argv[]) {\n\tchar ** args = malloc(sizeof(char*) * (argc + 2)); /* For -c and terminating NULL */\n\targs[0] = \"gunzip\";\n\targs[1] = \"-c\";\n\tfor (int i = 1; i < argc; ++i) {\n\t\targs[i+1] = argv[i];\n\t}\n\targs[argc+1] = NULL;\n\treturn execvp(\"gunzip\", args);\n}\n"
  },
  {
    "path": "base/etc/demo.conf",
    "content": "; this is a comment\ntest=hello\n\n[sec]\ntion=test\n\n; this is also a comment\n"
  },
  {
    "path": "base/etc/group",
    "content": "root:x:0:\nadm:x:1:local\ndialout:x:2:local\n"
  },
  {
    "path": "base/etc/hostname",
    "content": "livecd\n"
  },
  {
    "path": "base/etc/master.passwd",
    "content": "root:toor:0:0:Administrator:/home/root:/bin/esh:fancy\nlocal:local:1000:1000:Local User:/home/local:/bin/esh:fancy\nguest:guest:1001:1001:Guest User:/home/guest:/bin/esh:fancy\n"
  },
  {
    "path": "base/etc/motd",
    "content": "\n\u001b[1;33mWelcome to ToaruOS!\u001b[0m\n\nToaruOS is free software, released under the terms\nof the NCSA / University of Illinois License.\n\nhttps://toaruos.org https://github.com/klange/toaruos\n\n"
  },
  {
    "path": "base/etc/msk.conf",
    "content": "remote_order=cdrom,cdn,fallback\n\n[remotes]\ncdrom=/cdrom/extra\nlocal=http://192.168.11.2:8080\nfallback=http://toaruos.org/msk/2.0.x\ncdn=http://toaruos.sfo3.cdn.digitaloceanspaces.com/msk/2.0.x\n"
  },
  {
    "path": "base/etc/panel.menu",
    "content": ":_\n&accessories,folder,Accessories\n&demos,folder,Demos\n&games,folder,Games\n&settings,folder,Settings\n-\nexec help-browser,help,Help Browser\nexec about,star,About ToaruOS\nlog-out,exit,Log Out\n:accessories\nexec calculator,calculator,Calculator\nexec file-browser,folder,File Browser\nexec terminal,utilities-terminal,Terminal\n:demos\n&decorated,folder,Decorated\n&undecorated,folder,Undecorated\n:undecorated\nexec drawlines,drawlines,Draw Lines\n:decorated\nexec julia,julia,Julia Fractals\nexec mandelbrot,mandelbrot,Mandelbrot Explorer\nexec plasma,plasma,Plasma\n:games\nexec mines.krk,mines,Mines\n:settings\nexec gsudo package-manager,package,Package Manager\nexec wallpaper-picker,wallpaper-picker,Select Wallpaper\nexec cpuwidget,system-monitor,System Monitor\n"
  },
  {
    "path": "base/etc/passwd",
    "content": "root:x:0:0:Administrator:/home/root:/bin/esh:fancy\nadm:x:1:1:Administrators:/tmp:/bin/false:nope\ndialout:x:2:2:Serial Users:/tmp:/bin/false:nope\nlocal:x:1000:1000:Local User:/home/local:/bin/esh:fancy\nguest:x:1001:1001:Guest User:/home/guest:/bin/esh:fancy\n"
  },
  {
    "path": "base/etc/startup.d/00_startuplog.sh",
    "content": "#!/bin/esh\n\n# This daemonizes\nexec splash-log\n"
  },
  {
    "path": "base/etc/startup.d/01_migrate.sh",
    "content": "#!/bin/esh\n\nif not kcmdline -q migrate then exit 0\n\necho -n \"Migrating filesystem...\" >> /dev/pex/splash\n/bin/migrate\n"
  },
  {
    "path": "base/etc/startup.d/02_hostname.sh",
    "content": "#!/bin/esh\n\nexport-cmd HOSTNAME cat /etc/hostname\n\necho -n \"Setting hostname...\" > /dev/pex/splash\n\nif empty? \"$HOSTNAME\" then exec hostname \"localhost\" else exec hostname \"$HOSTNAME\"\n"
  },
  {
    "path": "base/etc/startup.d/03_tmpfs.sh",
    "content": "#!/bin/esh\n\necho -n \"Mounting tmpfs...\" > /dev/pex/splash\nmount tmpfs tmp,777 /tmp\nmount tmpfs var,755 /var\nmkdir /var/run\n"
  },
  {
    "path": "base/etc/startup.d/04_modprobe.sh",
    "content": "#!/bin/esh\n\necho -n \"Installing device driver modules...\" > /dev/pex/splash\n\n# Only load this in virtualbox for now, as we're not\n# even sure we're doing the remapping correctly...\nif lspci -q 80EE:CAFE,8086:7000 then insmod /mod/piix4.ko\n\n# Add module descriptions here...\nif lspci -q 8086:2415 then insmod /mod/ac97.ko\nif lspci -q 1234:1111,15ad:07a0 then insmod /mod/vmware.ko\nif lspci -q 80EE:CAFE then insmod /mod/vbox.ko\nif lspci -q 8086:0046 then insmod /mod/i965.ko\nif lspci -q 1274:1371 then insmod /mod/es1371.ko\n\nif lspci -q 8086:100e,8086:1004,8086:100f,8086:10ea,8086:10d3 then insmod /mod/e1000.ko\n\n# Device drivers\nif lspci -q 8086:7111,8086:7010 then insmod /mod/ata.ko\n"
  },
  {
    "path": "base/etc/startup.d/05_mountcd.sh",
    "content": "#!/bin/esh\n\nif not stat -Lq /dev/cdrom0 then exit 0\n\necho -n \"Mounting CD...\" > /dev/pex/splash\n\ninsmod /mod/iso9660.ko\nmount iso /dev/cdrom0 /cdrom\n\n# Does it look like it might be ours?\nif not stat -Lq /cdrom/bootcat then exit 0\n\necho -e \"icon=cd\\nrun=cd /cdrom ; exec file-browser\\ntitle=CD-ROM\" > /home/local/Desktop/5_cdrom.launcher\n\n"
  },
  {
    "path": "base/etc/startup.d/40_dhcp.sh",
    "content": "#!/bin/esh\n\nif kcmdline -q no-startup-dhcp then exit 0\n\necho -n \"Setting up network...\" >> /dev/pex/splash\n/bin/dhclient\n"
  },
  {
    "path": "base/etc/startup.d/50_msk.sh",
    "content": "#!/bin/esh\n\nif kcmdline -q no-startup-msk then exit 0\n\necho -n \"Checking for package updates...\" >> /dev/pex/splash\nmsk update &\n"
  },
  {
    "path": "base/etc/startup.d/99_runstart.sh",
    "content": "#!/bin/esh\n\nexport-cmd START kcmdline -g start\n\n# We haven't actually hit a login yet, so make sure these are set here...\nexport USER=root\nexport HOME=/home/root\n\nexport-cmd TZ_OFFSET find-timezone\nexport-cmd GETTY_ARGS qemu-fwcfg opt/org.toaruos.gettyargs\n\necho -n \"Launching startup application...\" > /dev/pex/splash\necho -n \"!quit\" > /dev/pex/splash\n\nif equals? \"$START\" \"--vga\" then exec /bin/terminal-vga -l\nif equals? \"$START\" \"--headless\" then exec /bin/getty ${GETTY_ARGS}\nif empty? \"$START\" then exec /bin/compositor else exec /bin/compositor $START\n\n"
  },
  {
    "path": "base/etc/sudoers",
    "content": "local\n"
  },
  {
    "path": "base/etc/weather.json",
    "content": "{\n    \"city\": \"guess\",\n    \"units\": \"metric\",\n\n    \"--comment\": \"The key below is provided for use in ToaruOS for free from OpenWeatherMap.org. We must provide the key so that the weather widget can make API queries. Use of this key for other purposes is against OpenWeatherMap's terms of service.\",\n    \"key\": \"78c832cfada2b0337f516891afb4f13b\"\n}\n"
  },
  {
    "path": "base/home/guest/hello",
    "content": ""
  },
  {
    "path": "base/home/local/.bim3rc",
    "content": "'''\nSample config file for bim3\n'''\n\n# Quirks work the same as they did in bim2, except for the obvious syntax change\nquirk('TERM','screen','no24bit','noitalic')\nquirk('TERM','xterm-256color','caninsert','canpaste','cansgrmouse')\nquirk('TERM','toaru-vga','no24bit','no256color')\nquirk('TERMINAL_EMULATOR','JetBrains','nobce')\n\n# checkprop() returns 0 if we _can_ do something, so\n# call it with 'not' to check capabilities...\nif not checkprop('can_unicode'):\n    tabindicator('»')\n    spaceindicator('·')\n\n# Themes are actually Kuroko functions now, but we load\n# them into a table like always for the :theme command\ntheme('sunsmoke')\n\n# Non-string values are coerced into strings, so these commands\n# can take integer values.\nglobal.git(1)\nglobal.statusbar(1)\nglobal.autohidetabs(1)\nsmartcomplete(1)\n\nlet x = wq\n"
  },
  {
    "path": "base/home/local/.eshrc",
    "content": "export PS1_TITLE=\"\\[\\e]1;\\u@\\h:\\w\\\\007\\e]2;\\u@\\h:\\w\\\\007\\]\"\nexport PS1_RIGHT=\"\\[\\\\e[1m\\e[38;5;59m\\][\\[\\e[38;5;173m\\]\\d \\[\\e[38;5;167m\\]\\t\\[\\e[38;5;59m\\]] \\[\\e[0m\\]\"\nexport PS1_LEFT=\"${PS1_TITLE}\\[\\e[1m\\e[38;5;221m\\]\\u\\[\\e[38;5;59m\\]@\\[\\e[38;5;81m\\]\\h \\[\\e[38;5;167m\\]\\r\\[\\e[0m\\]\\w\\U\\\\\\$\\[\\e[0m\\] \"\n\nif equals? \"$TERM\" \"toaru-vga\" then export RLINE_THEME=\"default\" else export RLINE_THEME=\"sunsmoke\"\n\nif stat -Lq /usr/local/bin then export PATH=\"/usr/local/bin:$PATH\"\n"
  },
  {
    "path": "base/home/local/.wallpaper.conf",
    "content": "; this doesn't get loaded with the regular confreader\nwallpaper=/usr/share/wallpaper.jpg\n"
  },
  {
    "path": "base/home/local/.yutanirc",
    "content": "#!/bin/esh\n\n# Start wallpaper\ncd ~/Desktop\nfile-browser --wallpaper &\ncd ~\n\n# Start toast daemon\ntoastd --really # Daemonizes\n\n# If we haven't shown the tutorial yet, show it.\nif not stat -q ~/.tutorial-shown then show-tutorial.sh\n\n# Login toast notifications\nshow-toasts.krk &\n\n# Replace us with the panel\nexec panel --really\n"
  },
  {
    "path": "base/home/local/Desktop/0_file_browser.launcher",
    "content": "icon=folder\nrun=cd ~ ; exec file-browser\ntitle=File Browser\n"
  },
  {
    "path": "base/home/local/Desktop/1_terminal.launcher",
    "content": "run=cd ~ ; exec terminal\ntitle=Terminal\nicon=utilities-terminal\n"
  },
  {
    "path": "base/home/local/Desktop/2_packages.launcher",
    "content": "run=exec gsudo package-manager\ntitle=Packages\nicon=package\n"
  },
  {
    "path": "base/home/local/Desktop/3_read_me.launcher",
    "content": "icon=file\nrun=exec terminal bim ~/README.md\ntitle=Read Me\n"
  },
  {
    "path": "base/home/local/README.md",
    "content": "# Welcome to ToaruOS!\n\nToaruOS provides a familiar Unix-like environment, but please be\naware that the shell is incomplete and does not implement all Unix\nshell features. For help with the shell's syntax and built-in\nfunctions, run `help`. For a list of available commands, press Tab\ntwice. Tab completion is available for both commands and file names.\n\nTo edit files, try using `bim` - a vi-like editor with syntax\nhighlighting, line and character selection, history stack, tabs, and more.\n\nTo install packages, use the `msk` tool. You can install a GCC/binutils\ntoolchain with:\n\n    sudo msk install build-essential\n\nOr you can install some games with:\n\n    sudo msk install doom quake\n\nThe password for the default user (`local`) is `local`.\n\nToaruOS's compositing window server includes many common keybindings:\n- Hold Alt to drag windows.\n- Super (Win) combined with the arrow keys will \"grid\" windows to the\n  sides or top and bottom of the screen. Combine with Ctrl and Shift\n  for quarter-sized gridding.\n- Alt-F10 maximized and unmaximizes windows.\n- Alt-F4 closes windows.\n\n(If this file is too long to view in one screenful in your terminal,\n you can open it with `bim README`)\n"
  },
  {
    "path": "base/home/local/text_layout.krk",
    "content": "#!/bin/kuroko\nfrom _yutani2 import (YutaniCtx, Font, rgb, rgb, MenuBar, decor_get_bounds, decor_render,\n                      MenuList, MenuEntry, MenuEntrySeparator, Message, decor_handle_event, decor_show_default_menu,\n                      MenuEntryCustom, Sprite, Subregion, TTContour, TransformMatrix, MenuEntrySubmenu,\n                      MenuEntryToggle)\n\nfrom yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep\nimport math\nimport random\n\nlet mainloop = AsyncMainloop()\n\ndef premul(r,g,b,a):\n    return rgb(int(r*a),int(g*a),int(b*a),a)\n\nlet show_ascent = True\ndef toggle_ascent(self):\n    show_ascent = !show_ascent\n    self.state = show_ascent\n\nlet font_size = 16\n\nclass Word:\n    def __init__(self, x, y, word, w, sw, font):\n        self.x = x\n        self.y = y\n        self.word = word\n        self.w = w\n        self.sw = sw\n        self.font = font\n        self.asc, self.desc, self.gap = font.measure()\n\n    def draw(self, ctx):\n        if show_ascent:\n            let bas = TTContour(self.x+0.5, self.y+0.5)\n            bas.line_to(self.x+0.5+self.w,self.y+0.5)\n            let bas_s = bas.stroke(0.5)\n            bas_s.paint(ctx, premul(0,255,0,0.5))\n            bas_s.free()\n            bas.free()\n\n\n            let asc = TTContour(self.x+0.5, self.y+0.5)\n            asc.line_to(self.x+0.5,self.y+0.5-self.asc)\n            asc.line_to(self.x+0.5+self.w,self.y+0.5-self.asc)\n            asc.line_to(self.x+0.5+self.w,self.y+0.5)\n            let asc_s = asc.stroke(0.5)\n            asc_s.paint(ctx, premul(255,0,0,0.5))\n            asc_s.free()\n            asc.free()\n\n            let des = TTContour(self.x+0.5,self.y+0.5)\n            des.line_to(self.x+0.5,self.y+0.5-self.desc)\n            des.line_to(self.x+0.5+self.w,self.y+0.5-self.desc)\n            des.line_to(self.x+0.5+self.w,self.y+0.5)\n            let des_s = des.stroke(0.5)\n            des_s.paint(ctx,premul(0,0,255,0.5))\n            des_s.free()\n            des.free()\n\n        self.font.draw_string(ctx, self.word, self.x, self.y)\n\nclass Line:\n    def __init__(self, x, y, cw, justify='center'):\n        self.words = []\n        self.x = x\n        self.y = y\n        self.cw = cw\n        self.justify = justify\n        self.islast = True\n\n    def add(self, word, w, sw, font):\n        self.words.append(Word(self.x, self.y, word, w, sw, font))\n\n    def finalize(self):\n        if self.justify == \"right\":\n            let _x = self.x + self.cw\n            for word in self.words[::-1]:\n                word.x = _x - word.w\n                _x -= word.w + word.sw\n        else if self.justify == \"justify\" and not self.islast and not len(self.words) < 2:\n            let mw = sum(word.w for word in self.words)\n            let av = float(self.cw - mw) / (len(self.words) - 1)\n            let _x = float(self.x)\n            for word in self.words:\n                word.x = int(_x)\n                _x += float(word.w) + av\n        else if self.justify == \"center\":\n            let mw = sum(word.w for word in self.words) + sum(word.sw for word in self.words[:-1])\n            let _x = self.x + (self.cw - mw) // 2\n            for word in self.words:\n                word.x = _x\n                _x += word.w + word.sw\n        else: # self.justify == \"left\":\n            let _x = self.x\n            for word in self.words:\n                word.x = _x\n                _x += word.w + word.sw\n\n    def draw(self, ctx):\n        for word in self.words:\n            word.draw(ctx)\n\ndef layout_and_draw(ctx, text, justify='center'):\n    let lines = []\n    let left_padding = 10\n    let right_padding = 10\n    let base_x = left_padding\n    let x = base_x\n    let cw = ctx.width - left_padding - right_padding\n    let font = Font(\"sans-serif\", font_size)\n    let asc, desc, gap = font.measure()\n    let lh = int(font_size * 1.2)\n    let lb = int((lh - int(asc - desc)) / 2)\n    let y = lb + int(asc)\n    let line = Line(x, y, cw, justify)\n\n    for word in text.split(' '):\n        let w = font.width(word)\n        if w + x >= cw:\n            line.islast = False\n            line.finalize()\n            lines.append(line)\n            x = base_x\n            y += lh\n            line = Line(x, y, cw, justify)\n        let sw = font.width(' ')\n        line.add(word, w, sw, font)\n        x += w + sw\n\n    if line.words:\n        line.finalize()\n        lines.append(line)\n\n    for line in lines:\n        line.draw(ctx)\n\n    #let sub = Subregion(ctx, 200, 200, 200, 200)\n    #sub.fill(rgb(255,255,0))\n\ndef set_menu(menu, action):\n    for sibling in menu.group:\n        sibling.state = False\n    menu.state = True\n    action()\n\ndef check_list(entries, default=0):\n    let ml = MenuList()\n    let group = []\n    for i, e in enumerate(entries):\n        let name, action = e\n        let me = MenuEntryToggle(name, lambda menu: set_menu(menu, action), i == default)\n        me.group = group\n        group.append(me)\n        ml.insert(me)\n    return ml\n\nclass MyWindow(Window):\n    def __init__(self):\n        super().__init__(640, 480, title=\"Hello\", doublebuffer=True)\n        self.bgc = rgb(255,255,255)\n        self.dejavu = Font(\"sans-serif\", 13)\n        self.mb = MenuBar(((\"File\",'file'),(\"View\",'view'),(\"Help\",'help')))\n        let _menu_File = MenuList()\n        _menu_File.insert(MenuEntry(\"Test\", lambda menu: print(\"hello, world\")))\n        _menu_File.insert(MenuEntrySeparator())\n        _menu_File.insert(MenuEntry(\"Quit\", lambda menu: self.close()))\n        self.mb.insert('file', _menu_File)\n        let _menu_View = MenuList()\n\n        let _menu_View_Justify = check_list((\n            ('Left', lambda: self.set_justification('left')),\n            ('Right', lambda: self.set_justification('right')),\n            ('Center', lambda: self.set_justification('center')),\n            ('Justify', lambda: self.set_justification('justify')),\n        ), default=2)\n        self.mb.insert('view-justify', _menu_View_Justify)\n        _menu_View.insert(MenuEntrySubmenu('Justify','view-justify'))\n\n        let _menu_View_Size = check_list((\n            (lambda x=y: (str(x), lambda: font_size = x))() for y in [8,12,16,32,64]\n        ), default=2)\n        self.mb.insert('view-size', _menu_View_Size)\n        _menu_View.insert(MenuEntrySubmenu('Font size', 'view-size'))\n\n        _menu_View.insert(MenuEntryToggle('Show ascent/descent',toggle_ascent,True))\n\n        self.mb.insert('view', _menu_View)\n        let _menu_Help = MenuList()\n        let _menu_Help_help = MenuEntry(\"Help\",lambda menu: print(\"oh no!\"))\n        _menu_Help.insert(_menu_Help_help)\n        self.mb.insert('help', _menu_Help)\n        self.mb.callback = lambda x: self.pending_updates = True\n        self.redrawer = Task(self.redraw_loop())\n        self.pending_updates = True\n        self.justification = 'center'\n        self.cursor = 1,1\n\n    def set_justification(self, justify):\n        self.justification = justify\n        self.pending_updates = True\n\n    async def redraw_loop(self):\n        while not self.closed:\n            if self.pending_updates:\n                self.draw()\n                self.pending_updates = False\n            await sleep(0.016)\n\n    def draw(self):\n        self.fill(self.bgc)\n        decor_render(self)\n        let bounds = decor_get_bounds(self)\n        self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self)\n        self.mb.render(self)\n        self.mb.height = 24 # Compatibility\n        #let layout = Layout(\"Far out in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy lies a small unregarded yellow sun.\", width=self.width)\n        let sprite = Sprite(width=self.width-bounds['width'],height=self.height-bounds['height']-self.mb.height)\n        sprite.fill(rgb(255,255,255))\n        layout_and_draw(sprite, \"Far out in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy lies a small unregarded yellow sun.\", self.justification)\n        #layout.draw(sprite)\n        self.draw_sprite(sprite,bounds['left_width'],bounds['top_height']+self.mb.height)\n        sprite.free()\n        self.flip()\n\n    def mouse_event(self, msg):\n        let decResponse = decor_handle_event(msg)\n        if decResponse == 2:\n            self.close()\n            return True\n        else if decResponse == 5:\n            decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y)\n        self.mb.mouse_event(self, msg)\n        let nc = msg.new_x, msg.new_y\n        if nc != self.cursor:\n            self.cursor = nc\n            self.pending_updates = True\n\n    def keyboard_event(self, msg):\n        if msg.keycode == 113 and msg.action == 1:\n            self.close()\n\n    def close(self):\n        super().close()\n        mainloop.exit()\n\n    def window_moved(self, msg):\n        self.pending_updates = True\n\n    def menu_close(self):\n        self.pending_updates = True\n\n    def finish_resize(self, msg):\n        if msg.width < 100 or msg.height < 100:\n            self.resize_offer(100 if msg.width < 100 else msg.width, 100 if msg.height < 100 else msg.height)\n            return\n        super().finish_resize(msg)\n\nmainloop.activate()\n\nlet w = MyWindow()\nw.move(200,200)\n\nmainloop.menu_closed_callback = w.menu_close\nmainloop.run()\n"
  },
  {
    "path": "base/home/root/.bimrc",
    "content": "rundir /usr/share/bim/themes\ncolorscheme ansi\n"
  },
  {
    "path": "base/home/root/hello",
    "content": "Hello, root.\n"
  },
  {
    "path": "base/lib/.dummy",
    "content": ""
  },
  {
    "path": "base/usr/include/_cheader.h",
    "content": "#pragma once\n\n#ifdef __cplusplus\n#   define _Begin_C_Header extern \"C\" {\n#   define _End_C_Header }\n#else\n#   define _Begin_C_Header\n#   define _End_C_Header\n#endif\n"
  },
  {
    "path": "base/usr/include/alloca.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#ifdef __GNUC__\n#define alloca(size) __builtin_alloca(size)\n#else\n#error alloca requested but this isn't gcc\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/arpa/inet.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <sys/types.h>\n#include <netinet/in.h>\n\n_Begin_C_Header\n\n#define INADDR_ANY (unsigned long int)0x0\n#define INADDR_BROADCAST (unsigned long int)0xFFffFFffUL\n\n#ifndef _KERNEL_\n\nextern uint32_t htonl(uint32_t hostlong);\nextern uint16_t htons(uint16_t hostshort);\nextern uint32_t ntohl(uint32_t netlong);\nextern uint16_t ntohs(uint16_t netshort);\n\nin_addr_t inet_addr(const char *cp);\nchar *inet_ntoa(struct in_addr in);\n\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/assert.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#ifndef NDEBUG\nextern void __assert_func(const char * file, int line, const char * func, const char * failedexpr);\n#define assert(statement) ((statement) ? (void)0 : __assert_func(__FILE__, __LINE__, __FUNCTION__, #statement))\n#else\n#define assert(statement) ((void)0)\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/bits/dirent.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n#include <sys/types.h>\n\n_Begin_C_Header\n\ntypedef struct dirent {\n\tino_t d_ino;\n\tchar d_name[256];\n} dirent;\n\n#ifndef _KERNEL_\ntypedef struct DIR {\n\tint fd;\n\tint cur_entry;\n} DIR;\n\nDIR * opendir (const char * dirname);\nint closedir (DIR * dir);\nstruct dirent * readdir (DIR * dirp) __asm__(\"readdir64\");\nlong telldir (DIR * dirp);\nvoid rewinddir (DIR * dirp);\nvoid seekdir (DIR * dirp, long loc);\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/bits/timespec.h",
    "content": "#pragma once\n\nstruct timespec {\n    time_t tv_sec;\n    long tv_nsec;\n};\n\n"
  },
  {
    "path": "base/usr/include/ctype.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\nextern int isalnum(int c);\nextern int isalpha(int c);\nextern int isdigit(int c);\nextern int islower(int c);\nextern int isprint(int c);\nextern int isgraph(int c);\nextern int iscntrl(int c);\nextern int isgraph(int c);\nextern int ispunct(int c);\nextern int isspace(int c);\nextern int isupper(int c);\nextern int isxdigit(int c);\n\nextern int isascii(int c);\n\nextern int tolower(int c);\nextern int toupper(int c);\n\n/* Derived from newlib */\n#define _U  01\n#define _L  02\n#define _N  04\n#define _S  010\n#define _P  020\n#define _C  040\n#define _X  0100\n#define _B  0200\n\nextern unsigned char _ctype_[256];\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/dirent.h",
    "content": "#pragma once\n\n#include <bits/dirent.h>\n"
  },
  {
    "path": "base/usr/include/dlfcn.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n/* Currently unused... */\n#define RTLD_LAZY   (1 << 0)\n#define RTLD_NOW    (1 << 1)\n#define RTLD_GLOBAL (1 << 2)\n\n#define RTLD_DEFAULT ((void*)0)\n\n/* Provided by ld.so, but also defined by libc.so for linking */\nextern void * dlopen(const char *, int);\nextern int dlclose(void *);\nextern void * dlsym(void *, const char *);\nextern char * dlerror(void);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/errno.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n/*\n * The bulk of these match Linux.\n * Descriptions come from newlib.\n */\n\n_Begin_C_Header\n\n#define EPERM 1               /* Not super-user */\n#define ENOENT 2              /* No such file or directory */\n#define ESRCH 3               /* No such process */\n#define EINTR 4               /* Interrupted system call */\n#define EIO 5                 /* I/O error */\n#define ENXIO 6               /* No such device or address */\n#define E2BIG 7               /* Arg list too long */\n#define ENOEXEC 8             /* Exec format error */\n#define EBADF 9               /* Bad file number */\n#define ECHILD 10             /* No children */\n#define EAGAIN 11             /* No more processes */\n#define ENOMEM 12             /* Not enough core */\n#define EACCES 13             /* Permission denied */\n#define EFAULT 14             /* Bad address */\n#define ENOTBLK 15            /* Block device required */\n#define EBUSY 16              /* Mount device busy */\n#define EEXIST 17             /* File exists */\n#define EXDEV 18              /* Cross-device link */\n#define ENODEV 19             /* No such device */\n#define ENOTDIR 20            /* Not a directory */\n#define EISDIR 21             /* Is a directory */\n#define EINVAL 22             /* Invalid argument */\n#define ENFILE 23             /* Too many open files in system */\n#define EMFILE 24             /* Too many open files */\n#define ENOTTY 25             /* Not a typewriter */\n#define ETXTBSY 26            /* Text file busy */\n#define EFBIG 27              /* File too large */\n#define ENOSPC 28             /* No space left on device */\n#define ESPIPE 29             /* Illegal seek */\n#define EROFS 30              /* Read only file system */\n#define EMLINK 31             /* Too many links */\n#define EPIPE 32              /* Broken pipe */\n#define EDOM 33               /* Math arg out of domain of func */\n#define ERANGE 34             /* Math result not representable */\n#define ENOMSG 35             /* No message of desired type */\n#define EIDRM 36              /* Identifier removed */\n#define ECHRNG 37             /* Channel number out of range */\n#define EL2NSYNC 38           /* Level 2 not synchronized */\n#define EL3HLT 39             /* Level 3 halted */\n#define EL3RST 40             /* Level 3 reset */\n#define ELNRNG 41             /* Link number out of range */\n#define EUNATCH 42            /* Protocol driver not attached */\n#define ENOCSI 43             /* No CSI structure available */\n#define EL2HLT 44             /* Level 2 halted */\n#define EDEADLK 45            /* Deadlock condition */\n#define ENOLCK 46             /* No record locks available */\n#define EBADE 50              /* Invalid exchange */\n#define EBADR 51              /* Invalid request descriptor */\n#define EXFULL 52             /* Exchange full */\n#define ENOANO 53             /* No anode */\n#define EBADRQC 54            /* Invalid request code */\n#define EBADSLT 55            /* Invalid slot */\n#define EDEADLOCK 56          /* File locking deadlock error */\n#define EBFONT 57             /* Bad font file fmt */\n#define ENOSTR 60             /* Device not a stream */\n#define ENODATA 61            /* No data (for no delay io) */\n#define ETIME 62              /* Timer expired */\n#define ENOSR 63              /* Out of streams resources */\n#define ENONET 64             /* Machine is not on the network */\n#define ENOPKG 65             /* Package not installed */\n#define EREMOTE 66            /* The object is remote */\n#define ENOLINK 67            /* The link has been severed */\n#define EADV 68               /* Advertise error */\n#define ESRMNT 69             /* Srmount error */\n#define ECOMM 70              /* Communication error on send */\n#define EPROTO 71             /* Protocol error */\n#define EMULTIHOP 74          /* Multihop attempted */\n#define ELBIN 75              /* Inode is remote (not really error) */\n#define EDOTDOT 76            /* Cross mount point (not really error) */\n#define EBADMSG 77            /* Trying to read unreadable message */\n#define EFTYPE 79             /* Inappropriate file type or format */\n#define ENOTUNIQ 80           /* Given log. name not unique */\n#define EBADFD 81             /* f.d. invalid for this operation */\n#define EREMCHG 82            /* Remote address changed */\n#define ELIBACC 83            /* Can't access a needed shared lib */\n#define ELIBBAD 84            /* Accessing a corrupted shared lib */\n#define ELIBSCN 85            /* .lib section in a.out corrupted */\n#define ELIBMAX 86            /* Attempting to link in too many libs */\n#define ELIBEXEC 87           /* Attempting to exec a shared library */\n#define ENOSYS 88             /* Function not implemented */\n#define ENOTEMPTY 90          /* Directory not empty */\n#define ENAMETOOLONG 91       /* File or path name too long */\n#define ELOOP 92              /* Too many symbolic links */\n#define EOPNOTSUPP 95         /* Operation not supported on transport endpoint */\n#define EPFNOSUPPORT 96       /* Protocol family not supported */\n#define ECONNRESET 104        /* Connection reset by peer */\n#define ENOBUFS 105           /* No buffer space available */\n#define EAFNOSUPPORT 106      /* Address family not supported by protocol family */\n#define EPROTOTYPE 107        /* Protocol wrong type for socket */\n#define ENOTSOCK 108          /* Socket operation on non-socket */\n#define ENOPROTOOPT 109       /* Protocol not available */\n#define ESHUTDOWN 110         /* Can't send after socket shutdown */\n#define ECONNREFUSED 111      /* Connection refused */\n#define EADDRINUSE 112        /* Address already in use */\n#define ECONNABORTED 113      /* Connection aborted */\n#define ENETUNREACH 114       /* Network is unreachable */\n#define ENETDOWN 115          /* Network interface is not configured */\n#define ETIMEDOUT 116         /* Connection timed out */\n#define EHOSTDOWN 117         /* Host is down */\n#define EHOSTUNREACH 118      /* Host is unreachable */\n#define EINPROGRESS 119       /* Connection already in progress */\n#define EALREADY 120          /* Socket already connected */\n#define EDESTADDRREQ 121      /* Destination address required */\n#define EMSGSIZE 122          /* Message too long */\n#define EPROTONOSUPPORT 123   /* Unknown protocol */\n#define ESOCKTNOSUPPORT 124   /* Socket type not supported */\n#define EADDRNOTAVAIL 125     /* Address not available */\n#define ENETRESET 126\n#define EISCONN 127           /* Socket is already connected */\n#define ENOTCONN 128          /* Socket is not connected */\n#define ETOOMANYREFS 129\n#define EPROCLIM 130\n#define EUSERS 131\n#define EDQUOT 132\n#define ESTALE 133\n#define ENOTSUP 134           /* Not supported */\n#define EILSEQ 138\n#define EOVERFLOW 139         /* Value too large for defined data type */\n#define ECANCELED 140         /* Operation canceled */\n#define ENOTRECOVERABLE 141   /* State not recoverable */\n#define EOWNERDEAD 142        /* Previous owner died */\n#define ESTRPIPE 143          /* Streams pipe error */\n#define EWOULDBLOCK EAGAIN    /* Operation would block */\n\n#define ERESTARTSYS 512\n#define ERESTARTSIGSUSPEND 511\n\n#ifndef _KERNEL_\nextern int errno;\n#define __sets_errno(...) long ret = __VA_ARGS__; if (ret < 0) { errno = -ret; ret = -1; } return ret\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/fcntl.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\n#define O_RDONLY     0x0000\n#define O_WRONLY     0x0001\n#define O_RDWR       0x0002\n#define O_APPEND     0x0008\n#define O_CREAT      0x0200\n#define O_TRUNC      0x0400\n#define O_EXCL       0x0800\n#define O_NOFOLLOW   0x1000\n#define O_PATH       0x2000\n#define O_NONBLOCK   0x4000\n#define O_DIRECTORY  0x8000\n\n#define F_GETFD 1\n#define F_SETFD 2\n\n#define F_GETFL 3\n#define F_SETFL 4\n\n#define F_DUPFD 10\n\n/* Advisory locks are not currently supported;\n * these definitions are stubs. */\n#define F_GETLK  5\n#define F_SETLK  6\n#define F_SETLKW 7\n\n#define F_RDLCK  0\n#define F_WRLCK  1\n#define F_UNLCK  2\n\nstruct flock {\n\tshort l_type;\n\tshort l_whence;\n\toff_t l_start;\n\toff_t l_len;\n\tpid_t l_pid;\n};\n\n#define FD_CLOEXEC (1 << 0)\n\n#ifndef __kernel__\nextern int open (const char *, int, ...);\nextern int chmod(const char *path, mode_t mode);\nextern int fchmod(int fd, mode_t mode);\nextern int fcntl(int fd, int cmd, ...);\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/getopt.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\nstruct option {\n    const char *name;\n    int has_arg;\n    int *flag;\n    int val;\n};\n\nextern char * optarg;\nextern int optind, opterr, optopt;\n\nextern int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);\n\n#define no_argument 0\n#define required_argument 1\n#define optional_argument 2 /* Unsupported */\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/iconv.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n\n_Begin_C_Header\n\ntypedef void * iconv_t;\n\nextern iconv_t iconv_open(const char *tocode, const char *fromcode);\nextern int iconv_close(iconv_t cd);\nextern size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);\n\n_End_C_Header;\n"
  },
  {
    "path": "base/usr/include/inttypes.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <stddef.h>\n\n_Begin_C_Header\n#define PRIi8  \"i\"\n#define PRIi16 \"i\"\n#define PRIi32 \"i\"\n#define PRIi64 \"li\"\n\n#define PRIx8  \"x\"\n#define PRIx16 \"x\"\n#define PRIx32 \"x\"\n#define PRIx64 \"lx\"\n\n#define PRIu8  \"u\"\n#define PRIu16 \"u\"\n#define PRIu32 \"u\"\n#define PRIu64 \"lu\"\n\n#define PRId8  \"d\"\n#define PRId16 \"d\"\n#define PRId32 \"d\"\n#define PRId64 \"ld\"\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/kernel/arch/aarch64/dtb.h",
    "content": "#pragma once\n#include <stdint.h>\n\nstruct fdt_header {\n\tuint32_t magic;\n\tuint32_t totalsize;\n\tuint32_t off_dt_struct;\n\tuint32_t off_dt_strings;\n\tuint32_t off_mem_rsvmap;\n\tuint32_t version;\n\tuint32_t last_comp_version;\n\tuint32_t boot_cpuid_phys;\n\tuint32_t size_dt_strings;\n\tuint32_t size_dt_struct;\n};\n\nstatic inline uint32_t swizzle(uint32_t from) {\n\tuint8_t a = from >> 24;\n\tuint8_t b = from >> 16;\n\tuint8_t c = from >> 8;\n\tuint8_t d = from;\n\treturn (d << 24) | (c << 16) | (b << 8) | (a);\n}\n\nstatic inline uint64_t swizzle64(uint64_t from) {\n\tuint8_t a = from >> 56;\n\tuint8_t b = from >> 48;\n\tuint8_t c = from >> 40;\n\tuint8_t d = from >> 32;\n\tuint8_t e = from >> 24;\n\tuint8_t f = from >> 16;\n\tuint8_t g = from >> 8;\n\tuint8_t h = from;\n\treturn ((uint64_t)h << 56) | ((uint64_t)g << 48) | ((uint64_t)f << 40) | ((uint64_t)e << 32) | (d << 24) | (c << 16) | (b << 8) | (a);\n}\n\nstatic inline uint16_t swizzle16(uint16_t from) {\n\tuint8_t a = from >> 8;\n\tuint8_t b = from;\n\treturn (b << 8) | (a);\n}\n\nuint32_t * dtb_find_node(const char * name);\nuint32_t * dtb_find_node_prefix(const char * name);\nuint32_t * dtb_node_find_property(uint32_t * node, const char * property);\nvoid dtb_memory_size(size_t * memsize, size_t * physsize);\nvoid dtb_callback_direct_children(uint32_t * node, void (*callback)(uint32_t * child));\nvoid dtb_locate_cmdline(char ** args_out);\nvoid dtb_pcie_base(void);\n"
  },
  {
    "path": "base/usr/include/kernel/arch/aarch64/gic.h",
    "content": "#pragma once\n\n#include <kernel/process.h>\n\nstruct irq_callback {\n\tint (*callback)(process_t * this, int irq, void *data);\n\tprocess_t * owner;\n\tvoid * data;\n\tstruct irq_callback * next;\n};\n\nextern volatile uint32_t * gic_regs;\nextern volatile uint32_t * gicc_regs;\n\nextern struct irq_callback * irq_callbacks[];\n\nvoid gic_assign_interrupt(int irq, int (*callback)(process_t*,int,void*), void * data);\nvoid gic_map_pci_interrupt(const char * name, uint32_t device, int * int_out, int (*callback)(process_t*,int,void*), void * isr_addr);\nvoid gic_map_regs(uintptr_t rpi_tag);\nvoid gic_send_sgi(uint8_t intid, int target);\n"
  },
  {
    "path": "base/usr/include/kernel/arch/aarch64/pml.h",
    "content": "#pragma once\n#include <stdint.h>\n\nunion PML {\n\tstruct {\n\t\tuint64_t present : 1;\n\t\tuint64_t table_page : 1;\n\t\tuint64_t attrindx:3;\n\t\tuint64_t ns:1;\n\t\tuint64_t ap:2;\n\t\tuint64_t sh:2;\n\t\tuint64_t af:1;\n\t\tuint64_t ng:1;\n\t\tuint64_t page:36;\n\t\tuint64_t reserved:4;\n\t\tuint64_t contiguous:1;\n\t\tuint64_t pxn:1;\n\t\tuint64_t uxn:1;\n\t\tuint64_t avail:4;\n\t\tuint64_t ignored:5;\n\t} bits;\n\n\tstruct {\n\t\tuint64_t valid : 1;\n\t\tuint64_t table : 1;\n\t\tuint64_t next:46;\n\t\tuint64_t reserved:4;\n\t\tuint64_t ignored:7;\n\t\tuint64_t pxntable:1;\n\t\tuint64_t xntable:1;\n\t\tuint64_t aptable:2;\n\t\tuint64_t nstable:1;\n\t} table_bits;\n\n\tuint64_t raw;\n};\n\n#define mmu_page_is_user_readable(p) (p->bits.ap & 1)\n#define mmu_page_is_user_writable(p) ((p->bits.ap & 1) && !(p->bits.ap & 2))\n"
  },
  {
    "path": "base/usr/include/kernel/arch/aarch64/regs.h",
    "content": "#pragma once\n#include <stdint.h>\n\nstruct regs {\n\tuint64_t x30, user_sp;\n\tuint64_t x28, x29;\n\tuint64_t x26, x27;\n\tuint64_t x24, x25;\n\tuint64_t x22, x23;\n\tuint64_t x20, x21;\n\tuint64_t x18, x19;\n\tuint64_t x16, x17;\n\tuint64_t x14, x15;\n\tuint64_t x12, x13;\n\tuint64_t x10, x11;\n\tuint64_t x8, x9;\n\tuint64_t x6, x7;\n\tuint64_t x4, x5;\n\tuint64_t x2, x3;\n\tuint64_t x0, x1;\n};\n\n"
  },
  {
    "path": "base/usr/include/kernel/arch/aarch64/rpi.h",
    "content": "#include <stdint.h>\n\nstruct rpitag {\n\tuint32_t phys_addr;\n\tuint32_t x;\n\tuint32_t y;\n\tuint32_t s;\n\tuint32_t b;\n\tuint32_t size;\n\tuint32_t ramdisk_start;\n\tuint32_t ramdisk_end;\n};\n\nvoid rpi_load_ramdisk(struct rpitag * tag, uintptr_t * ramdisk_phys_base, size_t * ramdisk_size);\nvoid rpi_set_cmdline(char ** args_out);\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/acpi.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\n\nstruct rsdp_descriptor {\n\tchar     signature[8];\n\tuint8_t  checksum;\n\tchar     oemid[6];\n\tuint8_t  revision;\n\tuint32_t rsdt_address;\n} __attribute__((packed));\n\nstruct rsdp_descriptor_20 {\n\tstruct rsdp_descriptor base;\n\n\tuint32_t length;\n\tuint64_t xsdt_address;\n\tuint8_t  ext_checksum;\n\tuint8_t  _reserved[3];\n} __attribute((packed));\n\nstruct acpi_sdt_header {\n\tchar     signature[4];\n\tuint32_t length;\n\tuint8_t  revision;\n\tuint8_t  checksum;\n\tchar     oemid[6];\n\tchar     oem_tableid[8];\n\tuint32_t oem_revision;\n\tuint32_t creator_id;\n\tuint32_t creator_revision;\n} __attribute__((packed));\n\nstruct rsdt {\n\tstruct acpi_sdt_header header;\n\tuint32_t pointers[];\n};\n\nstruct madt {\n\tstruct acpi_sdt_header header;\n\tuint32_t lapic_addr;\n\tuint32_t flags;\n\tuint8_t entries[];\n};\n\nstatic inline int acpi_checksum(struct acpi_sdt_header * header) {\n\tuint8_t check = 0;\n\tfor (size_t i = 0; i < header->length; ++i) {\n\t\tcheck += ((uint8_t *)header)[i];\n\t}\n\treturn check == 0;\n}\n\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/cmos.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\n\nuint32_t read_cmos(void);\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/irq.h",
    "content": "#pragma once\n\n#include <kernel/arch/x86_64/regs.h>\n\nextern struct regs * _isr0(struct regs*);\nextern struct regs * _isr1(struct regs*);\nextern struct regs * _isr2(struct regs*);\nextern struct regs * _isr3(struct regs*);\nextern struct regs * _isr4(struct regs*);\nextern struct regs * _isr5(struct regs*);\nextern struct regs * _isr6(struct regs*);\nextern struct regs * _isr7(struct regs*);\nextern struct regs * _isr8(struct regs*);\nextern struct regs * _isr9(struct regs*);\nextern struct regs * _isr10(struct regs*);\nextern struct regs * _isr11(struct regs*);\nextern struct regs * _isr12(struct regs*);\nextern struct regs * _isr13(struct regs*);\nextern struct regs * _isr14(struct regs*);\nextern struct regs * _isr15(struct regs*);\nextern struct regs * _isr16(struct regs*);\nextern struct regs * _isr17(struct regs*);\nextern struct regs * _isr18(struct regs*);\nextern struct regs * _isr19(struct regs*);\nextern struct regs * _isr20(struct regs*);\nextern struct regs * _isr21(struct regs*);\nextern struct regs * _isr22(struct regs*);\nextern struct regs * _isr23(struct regs*);\nextern struct regs * _isr24(struct regs*);\nextern struct regs * _isr25(struct regs*);\nextern struct regs * _isr26(struct regs*);\nextern struct regs * _isr27(struct regs*);\nextern struct regs * _isr28(struct regs*);\nextern struct regs * _isr29(struct regs*);\nextern struct regs * _isr30(struct regs*);\nextern struct regs * _isr31(struct regs*);\nextern struct regs * _irq0(struct regs*);\nextern struct regs * _irq1(struct regs*);\nextern struct regs * _irq2(struct regs*);\nextern struct regs * _irq3(struct regs*);\nextern struct regs * _irq4(struct regs*);\nextern struct regs * _irq5(struct regs*);\nextern struct regs * _irq6(struct regs*);\nextern struct regs * _irq7(struct regs*);\nextern struct regs * _irq8(struct regs*);\nextern struct regs * _irq9(struct regs*);\nextern struct regs * _irq10(struct regs*);\nextern struct regs * _irq11(struct regs*);\nextern struct regs * _irq12(struct regs*);\nextern struct regs * _irq13(struct regs*);\nextern struct regs * _irq14(struct regs*);\nextern struct regs * _irq15(struct regs*);\nextern struct regs * _isr123(struct regs*);\nextern struct regs * _isr124(struct regs*); /* Does not actually take regs */\nextern struct regs * _isr125(struct regs*); /* Does not actually take regs */\nextern struct regs * _isr126(struct regs*); /* Does not actually take regs */\nextern struct regs * _isr127(struct regs*); /* Syscall entry point */\n\ntypedef struct regs * (*interrupt_handler_t)(struct regs *);\n\n\n/**\n * Interrupt descriptor table\n */\ntypedef struct {\n\tuint16_t base_low;\n\tuint16_t selector;\n\n\tuint8_t zero;\n\tuint8_t flags;\n\n\tuint16_t base_mid;\n\tuint32_t base_high;\n\tuint32_t pad;\n} __attribute__((packed)) idt_entry_t;\n\nstruct idt_pointer {\n\tuint16_t  limit;\n\tuintptr_t base;\n} __attribute__((packed));\n\n\nextern void irq_ack(size_t irq_no);\n\ntypedef int (*irq_handler_chain_t) (struct regs *);\nextern void irq_install_handler(size_t irq, irq_handler_chain_t handler, const char * desc);\nextern const char * get_irq_handler(int irq, int chain);\n\nextern void idt_load(void *);\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/pml.h",
    "content": "#pragma once\n#include <kernel/types.h>\n\nunion PML {\n    struct {\n        uint64_t present:1;\n        uint64_t writable:1;\n        uint64_t user:1;\n        uint64_t writethrough:1;\n        uint64_t nocache:1;\n        uint64_t accessed:1;\n        uint64_t _available1:1;\n        uint64_t size:1;\n        uint64_t global:1;\n        uint64_t cow_pending:1;\n        uint64_t _available2:2;\n        uint64_t page:28;\n        uint64_t reserved:12;\n        uint64_t _available3:11;\n        uint64_t nx:1;\n    } bits;\n    uint64_t raw;\n};\n\n#define mmu_page_is_user_readable(p) (p->bits.user)\n#define mmu_page_is_user_writable(p) (p->bits.user && p->bits.writable)\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/ports.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\nextern unsigned short inports(unsigned short _port);\nextern void outports(unsigned short _port, unsigned short _data);\nextern unsigned int inportl(unsigned short _port);\nextern void outportl(unsigned short _port, unsigned int _data);\nextern unsigned char inportb(unsigned short _port);\nextern void outportb(unsigned short _port, unsigned char _data);\nextern void outportsm(unsigned short port, unsigned char * data, unsigned long size);\nextern void inportsm(unsigned short port, unsigned char * data, unsigned long size);\n\n"
  },
  {
    "path": "base/usr/include/kernel/arch/x86_64/regs.h",
    "content": "#pragma once\n#include <kernel/types.h>\n\n/**\n * Register layout for interrupt context.\n */\nstruct regs {\n\t/* Pushed by common stub */\n\tuintptr_t r15, r14, r13, r12;\n\tuintptr_t r11, r10, r9, r8;\n\tuintptr_t rbp, rdi, rsi, rdx, rcx, rbx, rax;\n\n\t/* Pushed by wrapper */\n\tuintptr_t int_no, err_code;\n\n\t/* Pushed by interrupt */\n\tuintptr_t rip, cs, rflags, rsp, ss;\n};\n"
  },
  {
    "path": "base/usr/include/kernel/args.h",
    "content": "#pragma once\n\nint args_present(const char * karg);\nchar * args_value(const char * karg);\nvoid args_parse(const char * arg);\n\n"
  },
  {
    "path": "base/usr/include/kernel/assert.h",
    "content": "#pragma once\n\nextern void __assert_failed(const char * file, int line, const char * func, const char * cond);\n#define assert(condition) do { if (!(condition)) __assert_failed(__FILE__,__LINE__,__func__,#condition); } while (0)\n"
  },
  {
    "path": "base/usr/include/kernel/elf.h",
    "content": "/**\n * @file elf.h\n * @author K. Lange\n *\n * @copyright Copyright in this work is disclaimed under the assertion\n * that its contents are purely factual and no rights may be reserved\n * for its use.\n *\n * @brief Structures for Elf binary files.\n *\n * Based primarily on the Elf and SysV ABI specification documents.\n *\n * @see https://uclibc.org/docs/elf-64-gen.pdf\n * @see https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf\n */\n#pragma once\n#include <stdint.h>\n\n/**\n * @typedef Elf64_Addr\n * @brief Unsigned program address. (uintptr_t)\n * @typedef Elf64_Off\n * @brief Unsigned file offset. (size_t)\n * @typedef Elf64_Half\n * @brief Unsigned medium integer. (unsigned short)\n * @typedef Elf64_Word\n * @brief Unsigned integer. (unsigned int)\n * @typedef Elf64_Sword\n * @brief Signed integer. (int)\n * @typedef Elf64_Xword\n * @brief Unsigned long integer. (unsigned long long)\n * @typedef Elf64_Sxword\n * @brief Signed long integer. (long long)\n */\ntypedef uint64_t Elf64_Addr;\ntypedef uint64_t Elf64_Off;\ntypedef uint16_t Elf64_Half;\ntypedef uint32_t Elf64_Word;\ntypedef int32_t  Elf64_Sword;\ntypedef uint64_t Elf64_Xword;\ntypedef int64_t  Elf64_Sxword;\n\n/**\n * Values for e_ident[EI_MAGn]\n */\n#define ELFMAG0   0x7f\n#define ELFMAG1   'E'\n#define ELFMAG2   'L'\n#define ELFMAG3   'F'\n\n/**\n * Values for e_ident[EI_CLASS]\n */\n#define ELFCLASS32 1\n#define ELFCLASS64 2\n\n/**\n * Values for e_ident[EI_DATA]\n */\n#define ELFDATA2LSB 1\n#define ELFDATA2MSB 2\n\n/**\n * Values for e_type\n */\n#define ET_NONE    0\n#define ET_REL     1\n#define ET_EXEC    2\n#define ET_DYN     3\n#define ET_CORE    4\n\n/**\n * e_ident fields\n */\n#define EI_MAG0        0\n#define EI_MAG1        1\n#define EI_MAG2        2\n#define EI_MAG3        3\n#define EI_CLASS       4\n#define EI_DATA        5\n#define EI_VERSION     6\n#define EI_OSABI       7\n#define EI_ABIVERSION  8\n#define EI_PAD         9\n#define EI_NIDENT      16\n\n#define EM_X86_64      62\n\n/**\n * @brief Elf object file header.\n */\ntypedef struct Elf64_Header {\n\tuint8_t    e_ident[EI_NIDENT]; /**< @brief Identifies the layout of the rest of the file. */\n\tElf64_Half e_type;             /**< @brief What kind of file this is, eg. object or executable... */\n\tElf64_Half e_machine;          /**< @brief The architecture this file is for. */\n\tElf64_Word e_version;          /**< @brief The version of the standard this file confirms to. */\n\tElf64_Addr e_entry;            /**< @brief The entry point of an executable. */\n\tElf64_Off  e_phoff;            /**< @brief The offset of the program headers. */\n\tElf64_Off  e_shoff;            /**< @brief The offset of the section headers. */\n\tElf64_Word e_flags;            /**< @brief Various flags. */\n\tElf64_Half e_ehsize;           /**< @brief Size of this header. */\n\tElf64_Half e_phentsize;        /**< @brief Size of one program header table entry. */\n\tElf64_Half e_phnum;            /**< @brief The number of entries in the program header table. */\n\tElf64_Half e_shentsize;        /**< @brief Size of one section header table entry. */\n\tElf64_Half e_shnum;            /**< @brief The number of entries in the section header table. */\n\tElf64_Half e_shstrndx;\n} Elf64_Header;\n\n/**\n * Special section indices\n */\n#define SHN_UNDEF    0\n#define SHN_LOPROC   0xFF00\n#define SHN_HIPROC   0xFF1F\n#define SHN_LOOS     0xFF20\n#define SHN_HIOS     0xFF3F\n#define SHN_ABS      0xFFF1\n#define SHN_COMMON   0xFFF2\n\n/**\n * Values for sh_type, sh_link, sh_info\n */\n#define SHT_NULL          0\n#define SHT_PROGBITS      1\n#define SHT_SYMTAB        2\n#define SHT_STRTAB        3\n#define SHT_RELA          4\n#define SHT_HASH          5\n#define SHT_DYNAMIC       6\n#define SHT_NOTE          7\n#define SHT_NOBITS        8\n#define SHT_REL           9\n#define SHT_SHLIB         10\n#define SHT_DYNSYM        11\n#define SHT_LOOS          0x60000000\n#define SHT_HIOS          0x6FFFFFFF\n#define SHT_LOPROC        0x70000000\n#define SHT_HIPROC        0x7FFFFFFF\n\n/**\n * Values for sh_flags\n */\n#define SHF_WRITE         0x00000001\n#define SHF_ALLOC         0x00000002\n#define SHF_EXECINSTR     0x00000004\n#define SHF_MASKOS        0x0F000000\n#define SHF_MASKPROC      0xF0000000\n/* From the SysV x86-64 ABI */\n#define SHF_X86_64_LARGE  0x10000000\n#define SHF_X86_64_UNWIND 0x70000001\n\ntypedef struct Elf64_Shdr {\n\tElf64_Word  sh_name;\n\tElf64_Word  sh_type;\n\tElf64_Xword sh_flags;\n\tElf64_Addr  sh_addr;\n\tElf64_Off   sh_offset;\n\tElf64_Xword sh_size;\n\tElf64_Word  sh_link;\n\tElf64_Word  sh_info;\n\tElf64_Xword sh_addralign;\n\tElf64_Xword sh_entsize;\n} Elf64_Shdr;\n\n/**\n * Binding types.\n * Contained in the high four bits of @p st_info\n */\n#define STB_LOCAL    0 /**< @brief Not visible outside the object file. */\n#define STB_GLOBAL   1 /**< @brief Global symbol, visible to all object files. */\n#define STB_WEAK     2 /**< @brief Global scope, but with lower precedence than global symbols. */\n#define STB_LOOS    10\n#define STB_HIOS    12\n#define STB_LOPROC  13\n#define STB_HIPROC  15\n\n/**\n * Symbol types.\n * Contained in the low four bits of @p st_info\n */\n#define STT_NOTYPE   0 /**< @brief No type specified (e.g., an absolute symbol) */\n#define STT_OBJECT   1 /**< @brief Data object */\n#define STT_FUNC     2 /**< @brief Function entry point */\n#define STT_SECTION  3 /**< @brief Symbol is associated with a section */\n#define STT_FILE     4 /**< @brief Source file associated with the object */\n#define STT_LOOS    10\n#define STT_HIOS    12\n#define STT_LOPROC  13\n#define STT_HIPROC  15\n\ntypedef struct Elf64_Sym {\n\tElf64_Word    st_name;  /**< @brief Symbol name */\n\tunsigned char st_info;  /**< @brief Type and binding attributes */\n\tunsigned char st_other; /**< @brief Reserved */\n\tElf64_Half    st_shndx; /**< @brief Section table index */\n\tElf64_Addr    st_value; /**< @brief Symbol value */\n\tElf64_Xword   st_size;  /**< @brief Size of object (e.g., common) */\n} Elf64_Sym;\n\n/**\n * Relocations\n */\n\n#define ELF64_R_SYM(i)    ((i) >> 32)\n#define ELF64_R_TYPE(i)   ((i) & 0xFFFFFFFFL)\n#define ELF64_R_INFO(s,t) (((s) << 32) + ((t) & 0xFFFFFFFFL))\n\ntypedef struct Elf64_Rel {\n\tElf64_Addr  r_offset; /**< @brief Address of reference */\n\tElf64_Xword r_info;   /**< @brief Symbol index and type of relocation */\n} Elf64_Rel;\n\ntypedef struct Elf64_Rela {\n\tElf64_Addr   r_offset; /**< @brief Address of reference */\n\tElf64_Xword  r_info;   /**< @brief Symbol index and type of relocation */\n\tElf64_Sxword r_addend; /**< @brief Constant part of expression */\n} Elf64_Rela;\n\n/**\n * x86-64 SysV Relocation types\n */\n#define R_X86_64_NONE             0  /**< @brief @p none none */\n#define R_X86_64_64               1  /**< @brief @p word64 S + A */\n#define R_X86_64_PC32             2  /**< @brief @p word32 S + A - P */\n#define R_X86_64_GOT32            3  /**< @brief @p word32 G + A */\n#define R_X86_64_PLT32            4  /**< @brief @p word32 L + A - P */\n#define R_X86_64_COPY             5  /**< @brief @p none none */\n#define R_X86_64_GLOB_DAT         6  /**< @brief @p word64 S */\n#define R_X86_64_JUMP_SLOT        7  /**< @brief @p word64 S */\n#define R_X86_64_RELATIVE         8  /**< @brief @p word64 B + A */\n#define R_X86_64_GOTPCREL         9  /**< @brief @p word32 G + GOT + A - P */\n#define R_X86_64_32               10 /**< @brief @p word32 S + A */\n#define R_X86_64_32S              11 /**< @brief @p word32 S + A */\n/* vvv These should not appear in a valid file */\n#define R_X86_64_16               12 /**< @brief @p word16 S + A */\n#define R_X86_64_PC16             13 /**< @brief @p word16 S + A - P */\n#define R_X86_64_8                14 /**< @brief @p word8  S + A */\n#define R_X86_64_PC8              15 /**< @brief @p word8  S + A - P */\n/* ^^^ These should not appear in a valid file */\n#define R_X86_64_DTPMOD64         16 /**< @brief @p word64 */\n#define R_X86_64_DTPOFF64         17 /**< @brief @p word64 */\n#define R_X86_64_TPOFF64          18 /**< @brief @p word64 */\n#define R_X86_64_TLSGD            19 /**< @brief @p word32 */\n#define R_X86_64_TLSLD            20 /**< @brief @p word32 */\n#define R_X86_64_DTPOFF32         21 /**< @brief @p word32 */\n#define R_X86_64_GOTTPOFF         22 /**< @brief @p word32 */\n#define R_X86_64_TPOFF32          23 /**< @brief @p word32 */\n#define R_X86_64_PC64             24 /**< @brief @p word64 S + A - P */\n#define R_X86_64_GOTOFF64         25 /**< @brief @p word64 S + A - GOT */\n#define R_X86_64_GOTPC32          26 /**< @brief @p word32 GOT + A - P */\n/* Large model */\n#define R_X86_64_GOT64            27 /**< @brief @p word64 G + A */\n#define R_X86_64_GOTPCREL64       28 /**< @brief @p word64 G + GOT - P + A */\n#define R_X86_64_GOTPC64          29 /**< @brief @p word64 GOT - P + A */\n#define R_X86_64_GOTPLT64         30 /**< @brief @p word64 G + A */\n#define R_X86_64_PLTOFF64         31 /**< @brief @p word64 L - GOT + A */\n/* ... */\n#define R_X86_64_SIZE32           32 /**< @brief @p word32 Z + A */\n#define R_X86_64_SIZE64           33 /**< @brief @p word64 Z + A */\n#define R_X86_64_GOTPC32_TLSDESC  34 /**< @brief @p word32 */\n#define R_X86_64_TLSDESC_CALL     35 /**< @brief @p none */\n#define R_X86_64_TLSDESC          36 /**< @brief @p word64*2 */\n#define R_X86_64_IRELATIVE        37 /**< @brief @p word64 indirect (B + A) */\n\n\n#define R_AARCH64_COPY          1024\n#define R_AARCH64_GLOB_DAT      1025\n\n\n/**\n * Program header types\n */\n#define PT_NULL     0\n#define PT_LOAD     1\n#define PT_DYNAMIC  2\n#define PT_INTERP   3\n#define PT_NOTE     4\n#define PT_SHLIB    5\n#define PT_PHDR     6\n#define PT_TLS      7\n#define PT_LOOS     0x60000000\n#define PT_HIOS     0x6FFFFFFF\n#define PT_LOPROC   0x70000000\n#define PT_HIPROC   0x7FFFFFFF\n/* From the SysV x86-64 ABI */\n#define PT_GNU_EH_FRAME  0x6474e550\n#define PT_SUNW_EH_FRAME 0x6474e550\n#define PT_SUNW_UNWIND   0x6464e550\n\n\n/**\n * Program header flags\n */\n#define PF_X        0x01\n#define PF_W        0x02\n#define PF_R        0x04\n#define PF_MASKOS   0x00FF0000\n#define PF_MAKSPROC 0xFF000000\n\ntypedef struct Elf64_Phdr {\n\tElf64_Word  p_type;\n\tElf64_Word  p_flags;\n\tElf64_Off   p_offset;\n\tElf64_Addr  p_vaddr;\n\tElf64_Addr  p_paddr;\n\tElf64_Xword p_filesz;\n\tElf64_Xword p_memsz;\n\tElf64_Xword p_align;\n} Elf64_Phdr;\n\n/**\n * Dynamic table\n */\n\n#define DT_NULL         0\n#define DT_NEEDED       1\n#define DT_PLTRELSZ     2\n#define DT_PLTGOT       3\n#define DT_HASH         4\n#define DT_STRTAB       5\n#define DT_SYMTAB       6\n#define DT_RELA         7\n#define DT_RELASZ       8\n#define DT_RELAENT      9\n#define DT_STRSZ        10\n#define DT_SYMENT       11\n#define DT_INIT         12\n#define DT_FINI         13\n#define DT_SONAME       14\n#define DT_RPATH        15\n#define DT_SYMBOLIC     16\n#define DT_REL          17\n#define DT_RELSZ        18\n#define DT_RELENT       19\n#define DT_PLTREL       20\n#define DT_DEBUG        21\n#define DT_TEXTREL      22\n#define DT_JMPREL       23\n#define DT_BIND_NOW     24\n#define DT_INIT_ARRAY   25\n#define DT_FINI_ARRAY   26\n#define DT_INIT_ARRAYSZ 27\n#define DT_FINI_ARRAYSZ 28\n#define DT_LOOS   0x60000000\n#define DT_HIOS   0x6FFFFFFF\n#define DT_LOPROC 0x70000000\n#define DT_HIPROC 0x7FFFFFFF\n\ntypedef struct Elf64_Dyn {\n\tElf64_Sxword d_tag;\n\tunion {\n\t\tElf64_Xword d_val;\n\t\tElf64_Addr  d_ptr;\n\t} d_un;\n} Elf64_Dyn;\n\n\n"
  },
  {
    "path": "base/usr/include/kernel/generic.h",
    "content": "#pragma once\n\n/**\n * @brief Initialize early subsystems.\n *\n * Should be called after the architecture-specific startup routine has\n * enabled all hardware required for tasking switching and memory management.\n *\n * Initializes the scheduler, shared memory subsystem, and virtual file system;\n * mounts generic device drivers, sets up the virtual /dev directory, parses\n * the architecture-provided kernel arguments, and enables scheduling by\n * launching the idle task and converting the current context to 'init'.\n */\nvoid generic_startup(void);\n\n/**\n * @brief Starts init.\n *\n * Should be called after all architecture-specific initialization is completed.\n * Parses the boot arguments and executes /bin/init.\n */\nint generic_main(void);\n"
  },
  {
    "path": "base/usr/include/kernel/gzip.h",
    "content": "/**\n * @brief Kernel gzip decompressor\n *\n * This is a slimmed down version of libtoaru_inflate; it's an implementation\n * of the tinf algorithm for decompressing gzip/DEFLATE payloads, with a\n * very straightforward API: Point @c gzip_inputPtr at your gzip data,\n * point @c gzip_outputPtr where you want the output to go, and then\n * run @c gzip_decompress().\n */\n#pragma once\n\n#include <stdint.h>\n\nextern int gzip_decompress(void);\nextern uint8_t * gzip_inputPtr;\nextern uint8_t * gzip_outputPtr;\n"
  },
  {
    "path": "base/usr/include/kernel/hashmap.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <kernel/string.h>\n#include <kernel/list.h>\n\ntypedef unsigned int (*hashmap_hash_t) (const void * key);\ntypedef int (*hashmap_comp_t) (const void * a, const void * b);\ntypedef void (*hashmap_free_t) (void *);\ntypedef void * (*hashmap_dupe_t) (const void *);\n\ntypedef struct hashmap_entry {\n\tchar * key;\n\tvoid * value;\n\tstruct hashmap_entry * next;\n} hashmap_entry_t;\n\ntypedef struct hashmap {\n\thashmap_hash_t hash_func;\n\thashmap_comp_t hash_comp;\n\thashmap_dupe_t hash_key_dup;\n\thashmap_free_t hash_key_free;\n\thashmap_free_t hash_val_free;\n\tsize_t         size;\n\thashmap_entry_t ** entries;\n} hashmap_t;\n\nextern hashmap_t * hashmap_create(int size);\nextern hashmap_t * hashmap_create_int(int size);\nextern void * hashmap_set(hashmap_t * map, const void * key, void * value);\nextern void * hashmap_get(hashmap_t * map, const void * key);\nextern void * hashmap_remove(hashmap_t * map, const void * key);\nextern int hashmap_has(hashmap_t * map, const void * key);\nextern list_t * hashmap_keys(hashmap_t * map);\nextern list_t * hashmap_values(hashmap_t * map);\nextern void hashmap_free(hashmap_t * map);\n\nextern unsigned int hashmap_string_hash(const void * key);\nextern int hashmap_string_comp(const void * a, const void * b);\nextern void * hashmap_string_dupe(const void * key);\nextern int hashmap_is_empty(hashmap_t * map);\n\n"
  },
  {
    "path": "base/usr/include/kernel/ksym.h",
    "content": "#pragma once\n\n#include <kernel/hashmap.h>\n\nextern void ksym_install(void);\nextern void ksym_bind(const char * symname, void * value);\nextern void * ksym_lookup(const char * symname);\nextern list_t * ksym_list(void);\nextern hashmap_t * ksym_get_map(void);\n"
  },
  {
    "path": "base/usr/include/kernel/list.h",
    "content": "/**\n * @brief General-purpose doubly-linked list.\n */\n#pragma once\n\n#include <stdint.h>\n#include <stddef.h>\n\ntypedef struct node {\n\tstruct node * next;\n\tstruct node * prev;\n\tvoid * value;\n\tstruct ListHeader * owner;\n} __attribute__((packed)) node_t;\n\ntypedef struct ListHeader {\n\tnode_t * head;\n\tnode_t * tail;\n\tsize_t length;\n\tconst char * name;\n\tconst void * metadata;\n} __attribute__((packed)) list_t;\n\nextern void list_destroy(list_t * list);\nextern void list_free(list_t * list);\nextern void list_append(list_t * list, node_t * item);\nextern node_t * list_insert(list_t * list, void * item);\nextern list_t * list_create(const char * name, const void * metadata);\nextern node_t * list_find(list_t * list, void * value);\nextern int list_index_of(list_t * list, void * value);\nextern void list_remove(list_t * list, size_t index);\nextern void list_delete(list_t * list, node_t * node);\nextern node_t * list_pop(list_t * list);\nextern node_t * list_dequeue(list_t * list);\nextern list_t * list_copy(list_t * original);\nextern void list_merge(list_t * target, list_t * source);\nextern void * list_index(list_t * list, int index);\n\nextern void list_append_after(list_t * list, node_t * before, node_t * node);\nextern node_t * list_insert_after(list_t * list, node_t * before, void * item);\n\nextern void list_append_before(list_t * list, node_t * after, node_t * node);\nextern node_t * list_insert_before(list_t * list, node_t * after, void * item);\n\n/* Known to conflict with some popular third-party libraries. */\n#ifndef TOARU_LIST_NO_FOREACH\n#  define foreach(i, list) for (node_t * i = (list)->head; i != NULL; i = i->next)\n#  define foreachr(i, list) for (node_t * i = (list)->tail; i != NULL; i = i->prev)\n#endif\n\n"
  },
  {
    "path": "base/usr/include/kernel/misc.h",
    "content": "#pragma once\n#include <kernel/types.h>\n\nsize_t arch_cpu_mhz(void);\n\nconst char * arch_get_cmdline(void);\nconst char * arch_get_loader(void);\n\nvoid arch_pause(void);\n\nvoid arch_fatal(void);\n\nvoid arch_set_tls_base(uintptr_t tlsbase);\nlong arch_reboot(void);\n\nvoid arch_fatal_prepare(void);\nvoid arch_dump_traceback(void);\n"
  },
  {
    "path": "base/usr/include/kernel/mmu.h",
    "content": "#pragma once\n#include <stdint.h>\n\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/pml.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/pml.h>\n#endif\n\n#define KERNEL_HEAP_START 0xFFFFff0000000000UL\n#define MMIO_BASE_START   0xffffff1fc0000000UL\n#define HIGH_MAP_REGION   0xffffff8000000000UL\n#define MODULE_BASE_START 0xffffffff80000000UL\n#define USER_SHM_LOW      0x0000400100000000UL\n#define USER_SHM_HIGH     0x0000500000000000UL\n#define USER_DEVICE_MAP   0x0000400000000000UL\n\n#define MMU_FLAG_KERNEL       0x01\n#define MMU_FLAG_WRITABLE     0x02\n#define MMU_FLAG_NOCACHE      0x04\n#define MMU_FLAG_WRITETHROUGH 0x08\n#define MMU_FLAG_SPEC         0x10\n#define MMU_FLAG_WC           (MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH | MMU_FLAG_SPEC)\n#define MMU_FLAG_NOEXECUTE    0x20\n\n#define MMU_GET_MAKE 0x01\n\n\n#define MMU_PTR_NULL  1\n#define MMU_PTR_WRITE 2\n\nvoid mmu_frame_set(uintptr_t frame_addr);\nvoid mmu_frame_clear(uintptr_t frame_addr);\nvoid mmu_frame_release(uintptr_t frame_addr);\nint mmu_frame_test(uintptr_t frame_addr);\nuintptr_t mmu_first_n_frames(int n);\nuintptr_t mmu_first_frame(void);\nvoid mmu_frame_allocate(union PML * page, unsigned int flags);\nvoid mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr);\nvoid mmu_frame_free(union PML * page);\nuintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr);\nunion PML * mmu_get_page(uintptr_t virtAddr, int flags);\nvoid mmu_set_directory(union PML * new_pml);\nvoid mmu_free(union PML * from);\nunion PML * mmu_clone(union PML * from);\nvoid mmu_invalidate(uintptr_t addr);\nuintptr_t mmu_allocate_a_frame(void);\nuintptr_t mmu_allocate_n_frames(int n);\nunion PML * mmu_get_kernel_directory(void);\nvoid * mmu_map_from_physical(uintptr_t frameaddress);\nvoid * mmu_map_mmio_region(uintptr_t physical_address, size_t size);\nvoid * mmu_map_module(size_t size);\nvoid mmu_unmap_module(uintptr_t base_address, size_t size);\n\nsize_t mmu_count_user(union PML * from);\nsize_t mmu_count_shm(union PML * from);\nsize_t mmu_total_memory(void);\nsize_t mmu_used_memory(void);\n\nvoid * sbrk(size_t);\n\nunion PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr);\nint mmu_validate_user_pointer(const void * addr, size_t size, int flags);\n"
  },
  {
    "path": "base/usr/include/kernel/mod/rtl.h",
    "content": "#ifndef KERNEL_MOD_RTL_H\n#define KERNEL_MOD_RTL_H\n\n#endif\n"
  },
  {
    "path": "base/usr/include/kernel/mod/shell.h",
    "content": "#ifndef KERNEL_MOD_SHELL_H\n#define KERNEL_MOD_SHELL_H\n\n#include <kernel/fs.h>\n\n/*\n * We're going to have a list of shell commands.\n * We'll search through it linearly because I don't\n * care to write a hashmap right now. Maybe later.\n */\nstruct shell_command {\n\tchar * name;\n\tint (*function) (fs_node_t * tty, int argc, char * argv[]);\n\tchar * description;\n};\n\nextern void debug_shell_install(struct shell_command * sh);\nextern int debug_shell_readline(fs_node_t * dev, char * linebuf, int max);\nextern void tty_set_buffered(fs_node_t * dev);\nextern void tty_set_unbuffered(fs_node_t * dev);\n\n#define DEFINE_SHELL_FUNCTION(n, desc) \\\n\tstatic int shell_ ## n (fs_node_t * tty, int argc, char * argv[]); \\\n\tstatic struct shell_command shell_ ## n ## _desc = { \\\n\t\t.name = #n , \\\n\t\t.function = &shell_ ## n , \\\n\t\t.description = desc \\\n\t}; \\\n\tstatic int shell_ ## n (fs_node_t * tty, int argc, char * argv[])\n\n#define BIND_SHELL_FUNCTION(name) \\\n\tdebug_shell_install(&shell_ ## name ## _desc);\n\n#endif\n"
  },
  {
    "path": "base/usr/include/kernel/mod/snd.h",
    "content": "#ifndef KERNEL_MOD_SND_H\n#define KERNEL_MOD_SND_H\n\n/* The format isn't really used for anything right now */\n#define SND_FORMAT_L16SLE 0  /* Linear 16-bit signed little endian */\n\n#include <stdint.h>\n#include <kernel/mod/sound.h>\n\n#define SND_KNOB_VENDOR 1024\n\ntypedef uint16_t snd_mixer_enum_t;\n\ntypedef struct snd_knob {\n\tchar name[SND_KNOB_NAME_SIZE];\n\tuint32_t id;\n} snd_knob_t;\n\ntypedef struct snd_device {\n\tchar name[256];            /* Name of the device. */\n\tvoid * device;             /* Private data for the device. May be NULL. */\n\tuint32_t playback_speed;   /* Playback speed in Hz */\n\tuint32_t playback_format;  /* Playback format (SND_FORMAT_*) */\n\n\tsnd_knob_t *knobs;\n\tuint32_t num_knobs;\n\tint (*mixer_read)(uint32_t knob_id, uint32_t *val);\n\tint (*mixer_write)(uint32_t knob_id, uint32_t val);\n\n\tuint32_t id;\n} snd_device_t;\n\n/*\n * Register a device to be used with snd\n */\nint snd_register(snd_device_t * device);\n\n/*\n * Unregister a device\n */\nint snd_unregister(snd_device_t * device);\n\n/*\n * Request a buffer to play from snd. This is to be called from the device in\n * order to fill a buffer on demand. After the call the buffer is garaunteed\n * to be filled to the size requested even if that means writing zeroes for\n * when there are no other samples.\n */\nint snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer);\n\n#endif  /* KERNEL_MOD_SND_H */\n"
  },
  {
    "path": "base/usr/include/kernel/mod/sound.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define SND_MAX_KNOBS 256\n#define SND_KNOB_NAME_SIZE 256\n#define SND_KNOB_MAX_VALUE UINT32_MAX\n\n#define SND_KNOB_MASTER 0\n#define SND_DEVICE_MAIN 0\n\ntypedef struct snd_knob_list {\n\tuint32_t device;              /* IN */\n\tuint32_t num;                 /* OUT */\n\tuint32_t ids[SND_MAX_KNOBS];  /* OUT */\n} snd_knob_list_t;\n\ntypedef struct snd_knob_info {\n\tuint32_t device;               /* IN */\n\tuint32_t id;                   /* IN */\n\tchar name[SND_KNOB_NAME_SIZE]; /* OUT */\n} snd_knob_info_t;\n\ntypedef struct snd_knob_value {\n\tuint32_t device; /* IN */\n\tuint32_t id;     /* IN */\n\tuint32_t val;    /* OUT for SND_MIXER_READ_KNOB, IN for SND_MIXER_WRITE_KNOB */\n} snd_knob_value_t;\n\n\n/* IOCTLs */\n#define SND_MIXER_GET_KNOBS 0\n#define SND_MIXER_GET_KNOB_INFO  1\n#define SND_MIXER_READ_KNOB 2\n#define SND_MIXER_WRITE_KNOB 3\n\n"
  },
  {
    "path": "base/usr/include/kernel/module.h",
    "content": "#pragma once\n\n#include <kernel/hashmap.h>\n\nstruct Module {\n\tconst char * name;\n\tint (*init)(int argc, char * argv[]);\n\tint (*fini)(void);\n};\n\nstruct LoadedModule {\n\tstruct Module * metadata;\n\tuintptr_t baseAddress;\n\tsize_t fileSize;\n\tsize_t loadedSize;\n};\n\nhashmap_t * modules_get_list(void);\n"
  },
  {
    "path": "base/usr/include/kernel/mouse.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\ntypedef enum {\n\tLEFT_CLICK   = 0x01,\n\tRIGHT_CLICK  = 0x02,\n\tMIDDLE_CLICK = 0x04,\n\n\tMOUSE_SCROLL_UP = 0x10,\n\tMOUSE_SCROLL_DOWN = 0x20,\n} mouse_click_t;\n\ntypedef struct {\n\tuint32_t magic;\n\tint32_t x_difference;\n\tint32_t y_difference;\n\tmouse_click_t buttons;\n} mouse_device_packet_t;\n\n#define MOUSE_MAGIC 0xFEED1234\n\n"
  },
  {
    "path": "base/usr/include/kernel/multiboot.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\n\n#define MULTIBOOT_MAGIC        0x1BADB002\n#define MULTIBOOT_EAX_MAGIC    0x2BADB002\n#define MULTIBOOT_FLAG_MEM     0x001\n#define MULTIBOOT_FLAG_DEVICE  0x002\n#define MULTIBOOT_FLAG_CMDLINE 0x004\n#define MULTIBOOT_FLAG_MODS    0x008\n#define MULTIBOOT_FLAG_AOUT    0x010\n#define MULTIBOOT_FLAG_ELF     0x020\n#define MULTIBOOT_FLAG_MMAP    0x040\n#define MULTIBOOT_FLAG_DRIVER  0x080\n#define MULTIBOOT_FLAG_CONFIG  0x100\n#define MULTIBOOT_FLAG_LOADER  0x200\n#define MULTIBOOT_FLAG_APM     0x400\n#define MULTIBOOT_FLAG_VBE     0x800\n#define MULTIBOOT_FLAG_FB     0x1000\n\nstruct multiboot\n{\n\tuint32_t flags;\n\tuint32_t mem_lower;\n\tuint32_t mem_upper;\n\tuint32_t boot_device;\n\tuint32_t cmdline;\n\tuint32_t mods_count;\n\tuint32_t mods_addr;\n\n\tuint32_t num;\n\tuint32_t size;\n\tuint32_t addr;\n\tuint32_t shndx;\n\n\tuint32_t mmap_length;\n\tuint32_t mmap_addr;\n\n\tuint32_t drives_length;\n\tuint32_t drives_addr;\n\n\tuint32_t config_table;\n\n\tuint32_t boot_loader_name;\n\n\tuint32_t apm_table;\n\n\tuint32_t vbe_control_info;\n\tuint32_t vbe_mode_info;\n\tuint16_t vbe_mode;\n\tuint16_t vbe_interface_seg;\n\tuint16_t vbe_interface_off;\n\tuint16_t vbe_interface_len;\n\n\tuint64_t framebuffer_addr;\n\tuint32_t framebuffer_pitch;\n\tuint32_t framebuffer_width;\n\tuint32_t framebuffer_height;\n\tuint8_t  framebuffer_bpp;\n\tuint8_t  framebuffer_type;\n} __attribute__ ((packed));\n\ntypedef struct {\n\tuint16_t attributes;\n\tuint8_t  winA, winB;\n\tuint16_t granularity;\n\tuint16_t winsize;\n\tuint16_t segmentA, segmentB;\n\tuint32_t realFctPtr;\n\tuint16_t pitch;\n\n\tuint16_t Xres, Yres;\n\tuint8_t  Wchar, Ychar, planes, bpp, banks;\n\tuint8_t  memory_model, bank_size, image_pages;\n\tuint8_t  reserved0;\n\n\tuint8_t  red_mask, red_position;\n\tuint8_t  green_mask, green_position;\n\tuint8_t  blue_mask, blue_position;\n\tuint8_t  rsv_mask, rsv_position;\n\tuint8_t  directcolor_attributes;\n\n\tuint32_t physbase;\n\tuint32_t reserved1;\n\tuint16_t reserved2;\n} __attribute__ ((packed)) vbe_info_t;\n\ntypedef struct {\n\tuint32_t mod_start;\n\tuint32_t mod_end;\n\tuint32_t cmdline;\n\tuint32_t reserved;\n} __attribute__ ((packed)) mboot_mod_t;\n\ntypedef struct {\n\tuint32_t size;\n\tuint64_t base_addr;\n\tuint64_t length;\n\tuint32_t type;\n} __attribute__ ((packed)) mboot_memmap_t;\n\n"
  },
  {
    "path": "base/usr/include/kernel/mutex.h",
    "content": "/**\n * Mutex that sleeps... and can be owned across sleeping...\n *\n * @copyright 2014-2021 K. Lange <klange@toaruos.org>\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n */\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n#include <kernel/process.h>\n\ntypedef struct {\n\tspin_lock_t inner_lock;\n\tvolatile int status;\n\tprocess_t * owner;\n\tlist_t * waiters;\n} sched_mutex_t;\n\nextern sched_mutex_t * mutex_init(const char * name);\nextern int mutex_acquire(sched_mutex_t * mutex);\nextern int mutex_release(sched_mutex_t * mutex);\n"
  },
  {
    "path": "base/usr/include/kernel/net/e1000.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define E1000_REG_CTRL       0x0000\n#define E1000_REG_STATUS     0x0008\n#define E1000_REG_EEPROM     0x0014\n#define E1000_REG_CTRL_EXT   0x0018\n#define E1000_REG_ICR        0x00C0\n#define E1000_REG_ITR        0x00c4\n#define E1000_REG_IMS        0x00d0\n#define E1000_REG_IMC        0x00d8\n\n#define E1000_REG_RCTRL      0x0100\n#define E1000_REG_RXDESCLO   0x2800\n#define E1000_REG_RXDESCHI   0x2804\n#define E1000_REG_RXDESCLEN  0x2808\n#define E1000_REG_RXDESCHEAD 0x2810\n#define E1000_REG_RXDESCTAIL 0x2818\n#define E1000_REG_RDTR       0x2820\n\n#define E1000_REG_TCTRL      0x0400\n#define E1000_REG_TXDESCLO   0x3800\n#define E1000_REG_TXDESCHI   0x3804\n#define E1000_REG_TXDESCLEN  0x3808\n#define E1000_REG_TXDESCHEAD 0x3810\n#define E1000_REG_TXDESCTAIL 0x3818\n\n#define E1000_REG_RXADDR     0x5400\n\n#define E1000_NUM_RX_DESC 512\n#define E1000_NUM_TX_DESC 512\n\n#define RCTL_EN                         (1 << 1)    /* Receiver Enable */\n#define RCTL_SBP                        (1 << 2)    /* Store Bad Packets */\n#define RCTL_UPE                        (1 << 3)    /* Unicast Promiscuous Enabled */\n#define RCTL_MPE                        (1 << 4)    /* Multicast Promiscuous Enabled */\n#define RCTL_LPE                        (1 << 5)    /* Long Packet Reception Enable */\n#define RCTL_LBM_NONE                   (0 << 6)    /* No Loopback */\n#define RCTL_LBM_PHY                    (3 << 6)    /* PHY or external SerDesc loopback */\n#define RCTL_RDMTS_HALF                 (0 << 8)    /* Free Buffer Threshold is 1/2 of RDLEN */\n#define RCTL_RDMTS_QUARTER              (1 << 8)    /* Free Buffer Threshold is 1/4 of RDLEN */\n#define RCTL_RDMTS_EIGHTH               (2 << 8)    /* Free Buffer Threshold is 1/8 of RDLEN */\n#define RCTL_MO_36                      (0 << 12)   /* Multicast Offset - bits 47:36 */\n#define RCTL_MO_35                      (1 << 12)   /* Multicast Offset - bits 46:35 */\n#define RCTL_MO_34                      (2 << 12)   /* Multicast Offset - bits 45:34 */\n#define RCTL_MO_32                      (3 << 12)   /* Multicast Offset - bits 43:32 */\n#define RCTL_BAM                        (1 << 15)   /* Broadcast Accept Mode */\n#define RCTL_VFE                        (1 << 18)   /* VLAN Filter Enable */\n#define RCTL_CFIEN                      (1 << 19)   /* Canonical Form Indicator Enable */\n#define RCTL_CFI                        (1 << 20)   /* Canonical Form Indicator Bit Value */\n#define RCTL_DPF                        (1 << 22)   /* Discard Pause Frames */\n#define RCTL_PMCF                       (1 << 23)   /* Pass MAC Control Frames */\n#define RCTL_SECRC                      (1 << 26)   /* Strip Ethernet CRC */\n\n#define RCTL_BSIZE_256                  (3 << 16)\n#define RCTL_BSIZE_512                  (2 << 16)\n#define RCTL_BSIZE_1024                 (1 << 16)\n#define RCTL_BSIZE_2048                 (0 << 16)\n#define RCTL_BSIZE_4096                 ((3 << 16) | (1 << 25))\n#define RCTL_BSIZE_8192                 ((2 << 16) | (1 << 25))\n#define RCTL_BSIZE_16384                ((1 << 16) | (1 << 25))\n\n#define TCTL_EN                         (1 << 1)    /* Transmit Enable */\n#define TCTL_PSP                        (1 << 3)    /* Pad Short Packets */\n#define TCTL_CT_SHIFT                   4           /* Collision Threshold */\n#define TCTL_COLD_SHIFT                 12          /* Collision Distance */\n#define TCTL_SWXOFF                     (1 << 22)   /* Software XOFF Transmission */\n#define TCTL_RTLC                       (1 << 24)   /* Re-transmit on Late Collision */\n\n#define CMD_EOP                         (1 << 0)    /* End of Packet */\n#define CMD_IFCS                        (1 << 1)    /* Insert FCS */\n#define CMD_IC                          (1 << 2)    /* Insert Checksum */\n#define CMD_RS                          (1 << 3)    /* Report Status */\n#define CMD_RPS                         (1 << 4)    /* Report Packet Sent */\n#define CMD_VLE                         (1 << 6)    /* VLAN Packet Enable */\n#define CMD_IDE                         (1 << 7)    /* Interrupt Delay Enable */\n\n#define ICR_TXDW   (1 << 0)\n#define ICR_TXQE   (1 << 1)  /* Transmit queue is empty */\n#define ICR_LSC    (1 << 2)  /* Link status changed */\n#define ICR_RXSEQ  (1 << 3)  /* Receive sequence count error */\n#define ICR_RXDMT0 (1 << 4)  /* Receive descriptor minimum threshold */\n/* what's 5 (0x20)? */\n#define ICR_RXO    (1 << 6)  /* Receive overrun */\n#define ICR_RXT0   (1 << 7)  /* Receive timer interrupt? */\n#define ICR_ACK    (1 << 17)\n#define ICR_SRPD   (1 << 16)\n\nstruct e1000_rx_desc {\n\tvolatile uint64_t addr;\n\tvolatile uint16_t length;\n\tvolatile uint16_t checksum;\n\tvolatile uint8_t  status;\n\tvolatile uint8_t  errors;\n\tvolatile uint16_t special;\n} __attribute__((packed)); /* this looks like it should pack fine as-is */\n\nstruct e1000_tx_desc {\n\tvolatile uint64_t addr;\n\tvolatile uint16_t length;\n\tvolatile uint8_t  cso;\n\tvolatile uint8_t  cmd;\n\tvolatile uint8_t  status;\n\tvolatile uint8_t  css;\n\tvolatile uint16_t special;\n} __attribute__((packed));\n\n"
  },
  {
    "path": "base/usr/include/kernel/net/eth.h",
    "content": "#pragma once\n\n#include <kernel/vfs.h>\n\n#define ETHERNET_TYPE_IPV4 0x0800\n#define ETHERNET_TYPE_ARP  0x0806\n#define ETHERNET_BROADCAST_MAC (uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}\n\n#define MAC_FORMAT \"%02x:%02x:%02x:%02x:%02x:%02x\"\n#define FORMAT_MAC(m) (m)[0], (m)[1], (m)[2], (m)[3], (m)[4], (m)[5]\n\nstruct ethernet_packet {\n\tuint8_t destination[6];\n\tuint8_t source[6];\n\tuint16_t type;\n\tuint8_t payload[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nvoid net_eth_handle(struct ethernet_packet * frame, fs_node_t * nic, size_t size);\n\nstruct EthernetDevice {\n\tchar if_name[32];\n\tuint8_t mac[6];\n\n\tsize_t mtu;\n\n\t/* XXX: just to get things going */\n\tuint32_t ipv4_addr;\n\tuint32_t ipv4_subnet;\n\tuint32_t ipv4_gateway;\n\n\tuint8_t ipv6_addr[16];\n\t/* TODO: Address lists? */\n\n\tfs_node_t * device_node;\n};\n\nvoid net_eth_send(struct EthernetDevice *, size_t, void*, uint16_t, uint8_t*);\n\nstruct ArpCacheEntry {\n\tuint8_t hwaddr[6];\n\tuint16_t flags;\n\tstruct EthernetDevice * iface;\n};\n\nstruct ArpCacheEntry * net_arp_cache_get(uint32_t addr);\nvoid net_arp_cache_add(struct EthernetDevice * iface, uint32_t addr, uint8_t * hwaddr, uint16_t flags);\nvoid net_arp_ask(uint32_t addr, fs_node_t * fsnic);\n\n"
  },
  {
    "path": "base/usr/include/kernel/net/ipv4.h",
    "content": "#pragma once\n#include <stdint.h>\n\nstruct ipv4_packet {\n\tuint8_t  version_ihl;\n\tuint8_t  dscp_ecn;\n\tuint16_t length;\n\tuint16_t ident;\n\tuint16_t flags_fragment;\n\tuint8_t  ttl;\n\tuint8_t  protocol;\n\tuint16_t checksum;\n\tuint32_t source;\n\tuint32_t destination;\n\tuint8_t  payload[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct icmp_header {\n\tuint8_t type;\n\tuint8_t code;\n\tuint16_t csum;\n\tuint16_t identifier;\n\tuint8_t data[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nstruct udp_packet {\n\tuint16_t source_port;\n\tuint16_t destination_port;\n\tuint16_t length;\n\tuint16_t checksum;\n\tuint8_t  payload[];\n} __attribute__ ((packed)) __attribute__((aligned(2)));\n\nstruct tcp_header {\n\tuint16_t source_port;\n\tuint16_t destination_port;\n\n\tuint32_t seq_number;\n\tuint32_t ack_number;\n\n\tuint16_t flags;\n\tuint16_t window_size;\n\tuint16_t checksum;\n\tuint16_t urgent;\n\n\tuint8_t  payload[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nstruct tcp_check_header {\n\tuint32_t source;\n\tuint32_t destination;\n\tuint8_t  zeros;\n\tuint8_t  protocol;\n\tuint16_t tcp_len;\n\tuint8_t  tcp_header[];\n};\n\n#define IPV4_PROT_UDP 17\n#define IPV4_PROT_TCP 6\n\n"
  },
  {
    "path": "base/usr/include/kernel/net/netif.h",
    "content": "#pragma once\n\n#include <kernel/vfs.h>\n#include <sys/socket.h>\n\n#define htonl(l)  ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24))\n#define htons(s)  ( (((s) & 0xFF) << 8) | (((s) & 0xFF00) >> 8) )\n#define ntohl(l)  htonl((l))\n#define ntohs(s)  htons((s))\n\nint net_add_interface(const char * name, fs_node_t * deviceNode);\nfs_node_t * net_if_lookup(const char * name);\nfs_node_t * net_if_route(uint32_t addr);\n\ntypedef struct SockData {\n\tfs_node_t _fnode;\n\tspin_lock_t alert_lock;\n\tspin_lock_t rx_lock;\n\tlist_t * alert_wait;\n\tlist_t * rx_wait;\n\tlist_t * rx_queue;\n\n\tuint16_t priv[4];\n\n\tlong (*sock_recv)(struct SockData * sock, struct msghdr * msg, int flags);\n\tlong (*sock_send)(struct SockData * sock, const struct msghdr *msg, int flags);\n\tvoid (*sock_close)(struct SockData * sock);\n\tlong (*sock_connect)(struct SockData * sock, const struct sockaddr *addr, socklen_t addrlen);\n\tlong (*sock_bind)(struct SockData * sock, const struct sockaddr *addr, socklen_t addrlen);\n\tlong (*sock_getsockname)(struct SockData * sock, struct sockaddr *addr, socklen_t *addrlen);\n\tlong (*sock_getpeername)(struct SockData * sock, struct sockaddr *addr, socklen_t *addrlen);\n\n\tstruct sockaddr dest;\n\tuint32_t priv32[4];\n\n\tsize_t unread;\n\tchar * buf;\n\tint nonblocking;\n} sock_t;\n\nvoid net_sock_alert(sock_t * sock);\nvoid net_sock_add(sock_t * sock, void * frame, size_t size);\nvoid * net_sock_get(sock_t * sock);\nsock_t * net_sock_create(void);\n\nextern long net_socket(int,int,int);\nextern long net_setsockopt(int,int,int,const void*,socklen_t);\nextern long net_bind(int, const struct sockaddr*, socklen_t);\nextern long net_accept(int, struct sockaddr*, socklen_t*);\nextern long net_listen(int,int);\nextern long net_connect(int, const struct sockaddr*, socklen_t);\nextern long net_getsockopt(int,int,int,void*,socklen_t*);\nextern long net_recv(int,struct msghdr*,int);\nextern long net_send(int, const struct msghdr*, int);\nextern long net_shutdown(int, int);\nextern long net_getsockname(int,struct sockaddr*,socklen_t*);\nextern long net_getpeername(int,struct sockaddr*,socklen_t*);\n\n"
  },
  {
    "path": "base/usr/include/kernel/pci.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define PCI_VENDOR_ID            0x00 // 2\n#define PCI_DEVICE_ID            0x02 // 2\n#define PCI_COMMAND              0x04 // 2\n#define PCI_STATUS               0x06 // 2\n#define PCI_REVISION_ID          0x08 // 1\n\n#define PCI_PROG_IF              0x09 // 1\n#define PCI_SUBCLASS             0x0a // 1\n#define PCI_CLASS                0x0b // 1\n#define PCI_CACHE_LINE_SIZE      0x0c // 1\n#define PCI_LATENCY_TIMER        0x0d // 1\n#define PCI_HEADER_TYPE          0x0e // 1\n#define PCI_BIST                 0x0f // 1\n#define PCI_BAR0                 0x10 // 4\n#define PCI_BAR1                 0x14 // 4\n#define PCI_BAR2                 0x18 // 4\n#define PCI_BAR3                 0x1C // 4\n#define PCI_BAR4                 0x20 // 4\n#define PCI_BAR5                 0x24 // 4\n\n#define PCI_INTERRUPT_LINE       0x3C // 1\n#define PCI_INTERRUPT_PIN        0x3D\n\n#define PCI_SECONDARY_BUS        0x19 // 1\n\n#define PCI_HEADER_TYPE_DEVICE  0\n#define PCI_HEADER_TYPE_BRIDGE  1\n#define PCI_HEADER_TYPE_CARDBUS 2\n\n#define PCI_TYPE_BRIDGE 0x0604\n#define PCI_TYPE_SATA   0x0106\n\n#define PCI_ADDRESS_PORT 0xCF8\n#define PCI_VALUE_PORT   0xCFC\n\n#define PCI_NONE 0xFFFF\n\ntypedef void (*pci_func_t)(uint32_t device, uint16_t vendor_id, uint16_t device_id, void * extra);\n\nstatic inline int pci_extract_bus(uint32_t device) {\n\treturn (uint8_t)((device >> 16));\n}\nstatic inline int pci_extract_slot(uint32_t device) {\n\treturn (uint8_t)((device >> 8));\n}\nstatic inline int pci_extract_func(uint32_t device) {\n\treturn (uint8_t)(device);\n}\n\nstatic inline uint32_t pci_get_addr(uint32_t device, int field) {\n\treturn 0x80000000 | (pci_extract_bus(device) << 16) | (pci_extract_slot(device) << 11) | (pci_extract_func(device) << 8) | ((field) & 0xFC);\n}\n\nstatic inline uint32_t pci_box_device(int bus, int slot, int func) {\n\treturn (uint32_t)((bus << 16) | (slot << 8) | func);\n}\n\nuint32_t pci_read_field(uint32_t device, int field, int size);\nvoid pci_write_field(uint32_t device, int field, int size, uint32_t value);\nuint16_t pci_find_type(uint32_t dev);\nvoid pci_scan_func(pci_func_t f, int type, int bus, int slot, int func, void * extra);\nvoid pci_scan_slot(pci_func_t f, int type, int bus, int slot, void * extra);\nvoid pci_scan_bus(pci_func_t f, int type, int bus, void * extra);\nvoid pci_scan(pci_func_t f, int type, void * extra);\nvoid pci_remap(void);\nint pci_get_interrupt(uint32_t device);\n\n"
  },
  {
    "path": "base/usr/include/kernel/pipe.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <kernel/vfs.h>\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n\ntypedef struct _pipe_device {\n\tuint8_t * buffer;\n\tsize_t write_ptr;\n\tsize_t read_ptr;\n\tsize_t size;\n\tsize_t refcount;\n\tlist_t * wait_queue_readers;\n\tlist_t * wait_queue_writers;\n\tint dead;\n\tlist_t * alert_waiters;\n\n\tspin_lock_t lock_read;\n\tspin_lock_t lock_write;\n\tspin_lock_t alert_lock;\n\tspin_lock_t wait_lock;\n\tspin_lock_t ptr_lock;\n} pipe_device_t;\n\nfs_node_t * make_pipe(size_t size);\nint pipe_size(fs_node_t * node);\nint pipe_unsize(fs_node_t * node);\n\n"
  },
  {
    "path": "base/usr/include/kernel/printf.h",
    "content": "#pragma once\n\n#include <stdarg.h>\n#include <kernel/types.h>\n\n__attribute__((format(__printf__,1,2)))\nextern int printf(const char *fmt, ...);\nextern size_t (*printf_output)(size_t, uint8_t *);\n__attribute__((format(__printf__,3,4)))\nextern int snprintf(char * str, size_t size, const char * format, ...);\nextern size_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args);\n\n__attribute__((format(__printf__,1,2)))\nextern int dprintf(const char *fmt, ...);\nextern void console_set_output(size_t (*output)(size_t,uint8_t*));\n"
  },
  {
    "path": "base/usr/include/kernel/process.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/tree.h>\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <sys/signal_defs.h>\n#include <sys/signal.h>\n\n#ifdef __x86_64__\n#include <kernel/arch/x86_64/pml.h>\n#endif\n\n#ifdef __aarch64__\n#include <kernel/arch/aarch64/pml.h>\n#endif\n\n\n#define PROC_REUSE_FDS 0x0001\n#define KERNEL_STACK_SIZE 0x9000\n#define USER_ROOT_UID 0\n\ntypedef struct {\n\tintptr_t refcount;\n\tunion PML * directory;\n\tspin_lock_t lock;\n} page_directory_t;\n\ntypedef struct {\n\tuintptr_t sp;        /* 0 */\n\tuintptr_t bp;        /* 8 */\n\tuintptr_t ip;        /* 16 */\n\tuintptr_t tls_base;  /* 24 */\n#ifdef __x86_64__\n\tuintptr_t saved[5]; /* XXX Arch dependent */\n#elif defined(__aarch64__)\n\tuintptr_t saved[32];\n#endif\n\t/**\n\t * 32: rbx\n\t * 40: r12\n\t * 48: r13\n\t * 56: r14\n\t * 64: r15\n\t */\n} kthread_context_t;\n\ntypedef struct thread {\n\tkthread_context_t context;\n\tuint8_t fp_regs[512] __attribute__((aligned(16)));\n\tpage_directory_t * page_directory;\n} thread_t;\n\ntypedef struct image {\n\tuintptr_t entry;\n\tuintptr_t heap;\n\tuintptr_t stack;\n\tuintptr_t shm_heap;\n\tuintptr_t userstack;\n\tspin_lock_t lock;\n} image_t;\n\ntypedef struct file_descriptors {\n\tfs_node_t ** entries;\n\tuint64_t * offsets;\n\tint * modes;\n\tsize_t length;\n\tsize_t capacity;\n\tsize_t refs;\n\tspin_lock_t lock;\n} fd_table_t;\n\nstruct signal_config {\n\tuintptr_t handler;\n\tsigset_t  mask;\n\tint flags;\n};\n\n#define PROC_FLAG_IS_TASKLET 0x01\n#define PROC_FLAG_FINISHED   0x02\n#define PROC_FLAG_STARTED    0x04\n#define PROC_FLAG_RUNNING    0x08\n#define PROC_FLAG_SLEEP_INT  0x10\n#define PROC_FLAG_SUSPENDED  0x20\n\n#define PROC_FLAG_TRACE_SYSCALLS     0x40\n#define PROC_FLAG_TRACE_SIGNALS      0x80\n\n#define PROC_FLAG_RESTORE_SIGMASK    0x100\n\ntypedef struct process {\n\tpid_t id;    /* PID */\n\tpid_t group; /* thread group */\n\tpid_t job;   /* tty job */\n\tpid_t session; /* tty session */\n\tint status; /* status code */\n\tunsigned int flags; /* finished, started, running, isTasklet */\n\tint owner;\n\n\tuid_t user;\n\tuid_t real_user;\n\n\tgid_t user_group;\n\tgid_t real_user_group;\n\n\tunsigned int mask;\n\n\tchar * name;\n\tchar * description;\n\tchar ** cmdline;\n\n\tchar * wd_name;\n\tfs_node_t * wd_node;\n\tfd_table_t *  fds;               /* File descriptor table */\n\n\ttree_node_t * tree_entry;\n\tstruct regs * syscall_registers;\n\tlist_t * wait_queue;\n\tlist_t * shm_mappings;\n\tlist_t * node_waits;\n\n\tnode_t sched_node;\n\tnode_t sleep_node;\n\tnode_t * timed_sleep_node;\n\tnode_t * timeout_node;\n\n\tstruct timeval start;\n\tint awoken_index;\n\n\tthread_t thread;\n\timage_t image;\n\n\tspin_lock_t sched_lock;\n\n\tstruct signal_config signals[NUMSIGNALS+1];\n\tsigset_t blocked_signals;\n\tsigset_t pending_signals;\n\tsigset_t awaited_signals;\n\n\tint supplementary_group_count;\n\tgid_t * supplementary_group_list;\n\n\t/* Process times */\n\tuint64_t time_prev;         /* user time from previous update of usage[] */\n\tuint64_t time_total;        /* user time */\n\tuint64_t time_sys;          /* system time */\n\tuint64_t time_in;           /* tsc stamp of when this process last entered the running state */\n\tuint64_t time_switch;       /* tsc stamp of when this process last started doing system things */\n\tuint64_t time_children;     /* sum of user times from waited-for children */\n\tuint64_t time_sys_children; /* sum of sys times from waited-for children */\n\tuint16_t usage[4];          /* four permille samples over some period (currently 4Hz) */\n\n\t/* Tracing */\n\tpid_t tracer;\n\tspin_lock_t wait_lock;\n\tlist_t * tracees;\n\n\t/* Syscall restarting */\n\tlong interrupted_system_call;\n\tsigset_t restored_signals;\n} process_t;\n\ntypedef struct {\n\tuint64_t end_tick;\n\tuint64_t end_subtick;\n\tprocess_t * process;\n\tint is_fswait;\n} sleeper_t;\n\nstruct ProcessorLocal {\n\t/**\n\t * @brief The running process on this core.\n\t *\n\t * The current_process is a pointer to the process struct for\n\t * the process, userspace-thread, or kernel tasklet currently\n\t * executing. Once the scheduler is active, this should always\n\t * be set. If a core is not currently doing, its current_process\n\t * should be the core's idle task.\n\t *\n\t * Because a process's data can be modified by nested interrupt\n\t * contexts, we mark them as volatile to avoid making assumptions\n\t * based on register-stored cached values.\n\t */\n\tvolatile process_t * current_process;\n\t/**\n\t * @brief Idle loop.\n\t *\n\t * This is a special kernel tasklet that sits in a loop\n\t * waiting for an interrupt from a preemption source or hardware\n\t * device. Its context should never be saved, it should never\n\t * be added to a sleep queue, and it should be scheduled whenever\n\t * there is nothing else to do.\n\t */\n\tprocess_t * kernel_idle_task;\n\t/**\n\t * @brief Process this core was last scheduled to run.\n\t */\n\tvolatile process_t * previous_process;\n\n\tint cpu_id;\n\tunion PML * current_pml;\n\n#ifdef __x86_64__\n\tint lapic_id;\n\t/* Processor information loaded at startup. */\n\tint  cpu_model;\n\tint  cpu_family;\n\tchar cpu_model_name[48];\n\tconst char * cpu_manufacturer; /* 0x68 */\n\tuintptr_t syscall_stack;       /* 0x70: Should match TSS.RSP[0] */\n\tuintptr_t user_sysret_stack;   /* 0x78: Used only at start of SYSCALL entry to store user RSP before pushing it */\n#endif\n\n#ifdef __aarch64__\n\tuintptr_t sp_el1;\n\tuint64_t  midr;\n#endif\n};\n\nextern struct ProcessorLocal processor_local_data[];\nextern int processor_count;\n\n/**\n * @brief Core-local kernel data.\n *\n * x86-64: Marking this as __seg_gs makes it %gs-base-relative.\n * aarch64: We shove this in x18 and ref off of that; -ffixed-x18 and don't forget to reload it from TPIDR_EL1\n */\n#ifdef __x86_64__\nstatic struct ProcessorLocal __seg_gs * const this_core = 0;\n#else\nregister struct ProcessorLocal * this_core asm(\"x18\");\n#endif\n\nextern unsigned long process_append_fd(process_t * proc, fs_node_t * node);\nextern long process_move_fd(process_t * proc, long src, long dest);\nextern void initialize_process_tree(void);\nextern process_t * process_from_pid(pid_t pid);\n\nextern void process_delete(process_t * proc);\nextern void make_process_ready(volatile process_t * proc);\nextern volatile process_t * next_ready_process(void);\nextern int wakeup_queue(list_t * queue);\nextern int wakeup_queue_interrupted(list_t * queue);\nextern int sleep_on(list_t * queue);\nextern int sleep_on_unlocking(list_t * queue, spin_lock_t * release);\nextern int process_alert_node(process_t * process, void * value);\nextern void sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds);\nextern void switch_task(uint8_t reschedule);\nextern int process_wait_nodes(process_t * process,fs_node_t * nodes[], int timeout);\nextern process_t * process_get_parent(process_t * process);\nextern int process_is_ready(process_t * proc);\nextern void wakeup_sleepers(unsigned long seconds, unsigned long subseconds);\nextern void task_exit(int retval);\nextern __attribute__((noreturn)) void switch_next(void);\nextern int process_awaken_from_fswait(process_t * process, int index);\nextern void process_awaken_signal(process_t * process);\nextern void process_release_directory(page_directory_t * dir);\nextern process_t * spawn_worker_thread(void (*entrypoint)(void * argp), const char * name, void * argp);\nextern pid_t fork(void);\nextern pid_t clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg);\nextern int waitpid(int pid, int * status, int options);\nextern int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth);\nextern void update_process_usage(uint64_t clock_ticks, uint64_t perf_scale);\nextern void update_process_times_on_exit(void);\n\nextern tree_t * process_tree;  /* Parent->Children tree */\nextern list_t * process_list;  /* Flat storage */\nextern list_t * process_queue; /* Ready queue */\nextern list_t * sleep_queue;\n\nextern void arch_enter_tasklet(void);\nextern __attribute__((noreturn)) void arch_resume_user(void);\nextern __attribute__((noreturn)) void arch_restore_context(volatile thread_t * buf);\nextern __attribute__((returns_twice)) int arch_save_context(volatile thread_t * buf);\nextern void arch_restore_floating(process_t * proc);\nextern void arch_save_floating(process_t * proc);\nextern void arch_set_kernel_stack(uintptr_t);\nextern void arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack);\n__attribute__((noreturn))\nextern void arch_enter_signal_handler(uintptr_t,int,struct regs*);\nextern void arch_wakeup_others(void);\nextern int arch_return_from_signal_handler(struct regs *r);\n\n"
  },
  {
    "path": "base/usr/include/kernel/procfs.h",
    "content": "#pragma once\n\n#include <kernel/vfs.h>\n\ntypedef void (*procfs_populate_t)(fs_node_t * node);\n\nstruct procfs_entry {\n\tintptr_t     id;\n\tconst char *       name;\n\tprocfs_populate_t func;\n};\n\nextern int procfs_install(struct procfs_entry * entry);\nextern void procfs_initialize(void);\nextern int procfs_printf(fs_node_t * node, const char * fmt, ...);\n"
  },
  {
    "path": "base/usr/include/kernel/ptrace.h",
    "content": "#pragma once\n\nlong ptrace_attach(pid_t pid);\nlong ptrace_self(void);\nlong ptrace_signal(int signal, int reason);\nlong ptrace_continue(pid_t pid, int signum);\n"
  },
  {
    "path": "base/usr/include/kernel/pty.h",
    "content": "#pragma once\n\n#include <kernel/vfs.h>\n#include <kernel/ringbuffer.h>\n#include <sys/types.h>\n#include <sys/ioctl.h>\n#include <sys/termios.h>\n\ntypedef struct pty {\n\t/* the PTY number */\n\tintptr_t       name;\n\n\t/* Master and slave endpoints */\n\tfs_node_t *    master;\n\tfs_node_t *    slave;\n\n\t/* term io \"window size\" struct (width/height) */\n\tstruct winsize size;\n\n\t/* termios data structure */\n\tstruct termios tios;\n\n\t/* directional pipes */\n\tring_buffer_t * in;\n\tring_buffer_t * out;\n\n\tchar * canon_buffer;\n\tsize_t canon_bufsize;\n\tsize_t canon_buflen;\n\n\tpid_t ct_proc; /* Controlling process (shell) */\n\tpid_t fg_proc; /* Foreground process (might also be shell) */\n\n\tvoid (*write_in)(struct pty *, uint8_t);\n\tvoid (*write_out)(struct pty *, uint8_t);\n\n\tint next_is_verbatim;\n\n\tvoid (*fill_name)(struct pty *, char *);\n\n\tvoid * _private;\n} pty_t;\n\nvoid tty_output_process_slave(pty_t * pty, uint8_t c);\nvoid tty_output_process(pty_t * pty, uint8_t c);\nvoid tty_input_process(pty_t * pty, uint8_t c);\npty_t * pty_new(struct winsize * size, int index);\n"
  },
  {
    "path": "base/usr/include/kernel/ramdisk.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/vfs.h>\n\nextern fs_node_t * ramdisk_mount(uintptr_t, size_t);\n"
  },
  {
    "path": "base/usr/include/kernel/ringbuffer.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <kernel/list.h>\n#include <kernel/vfs.h>\n#include <kernel/spinlock.h>\n\ntypedef struct {\n\tunsigned char * buffer;\n\tsize_t write_ptr;\n\tsize_t read_ptr;\n\tsize_t size;\n\tspin_lock_t lock;\n\tlist_t * wait_queue_readers;\n\tlist_t * wait_queue_writers;\n\tint internal_stop;\n\tlist_t * alert_waiters;\n\tint discard;\n\tint soft_stop;\n} ring_buffer_t;\n\nsize_t ring_buffer_unread(ring_buffer_t * ring_buffer);\nsize_t ring_buffer_size(fs_node_t * node);\nsize_t ring_buffer_available(ring_buffer_t * ring_buffer);\nsize_t ring_buffer_read(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer);\nsize_t ring_buffer_write(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer);\n\nring_buffer_t * ring_buffer_create(size_t size);\nvoid ring_buffer_destroy(ring_buffer_t * ring_buffer);\nvoid ring_buffer_interrupt(ring_buffer_t * ring_buffer);\nvoid ring_buffer_alert_waiters(ring_buffer_t * ring_buffer);\nvoid ring_buffer_select_wait(ring_buffer_t * ring_buffer, void * process);\nvoid ring_buffer_eof(ring_buffer_t * ring_buffer);\nvoid ring_buffer_discard(ring_buffer_t * ring_buffer);\n\n"
  },
  {
    "path": "base/usr/include/kernel/shm.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n#include <kernel/types.h>\n\n#define SHM_PATH_SEPARATOR \".\"\n\n/* Types */\nstruct shm_node;\n\ntypedef struct {\n\tstruct shm_node * parent;\n\tvolatile uint8_t lock;\n\tssize_t ref_count;\n\tsize_t num_frames;\n\tuintptr_t *frames;\n} shm_chunk_t;\n\ntypedef struct shm_node {\n\tchar name[256];\n\tshm_chunk_t * chunk;\n} shm_node_t;\n\ntypedef struct {\n\tshm_chunk_t * chunk;\n\tuint8_t volatile lock;\n\tsize_t num_vaddrs;\n\tuintptr_t *vaddrs;\n} shm_mapping_t;\n\n/* Syscalls */\nextern void * shm_obtain(char * path, size_t * size);\nextern int    shm_release(char * path);\n\n/* Other exposed functions */\nextern void shm_install(void);\nextern void shm_release_all(process_t * proc);\n\n"
  },
  {
    "path": "base/usr/include/kernel/signal.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <sys/types.h>\n#include <sys/signal.h>\n\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/regs.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/regs.h>\n#else\n#error \"no regs\"\n#endif\n\ntypedef struct {\n\tint signum;\n} signal_t;\n\nextern void fix_signal_stacks(void);\nextern int send_signal(pid_t process, int signal, int force_root);\nextern int group_send_signal(pid_t group, int signal, int force_root);\nextern void return_from_signal_handler(struct regs*);\nextern void process_check_signals(struct regs*);\nextern int signal_await(sigset_t awaited, int * sig);\nextern int handle_signal(process_t * proc, int signum, struct regs *r);\n"
  },
  {
    "path": "base/usr/include/kernel/spinlock.h",
    "content": "#pragma once\n\n#include <kernel/time.h>\n#include <kernel/misc.h>\n#include <kernel/printf.h>\n\ntypedef volatile struct {\n    volatile int latch[1];\n    int owner;\n    const char * func;\n} spin_lock_t;\n#define spin_init(lock) do { (lock).owner = 0; (lock).latch[0] = 0; (lock).func = NULL; } while (0)\n\n#ifdef __aarch64__\nextern void arch_spin_lock_acquire(const char * name, spin_lock_t * lock, const char * func);\nextern void arch_spin_lock_release(spin_lock_t * lock);\n#define spin_lock(lock) arch_spin_lock_acquire(#lock, &lock, __func__)\n#define spin_unlock(lock) arch_spin_lock_release(&lock)\n#else\n#define spin_lock(lock) do { while (__sync_lock_test_and_set((lock).latch, 0x01)); (lock).owner = this_core->cpu_id+1; (lock).func = __func__; } while (0)\n#define spin_unlock(lock) do { (lock).func = NULL; (lock).owner = -1; __sync_lock_release((lock).latch); } while (0)\n#endif\n\n#include <kernel/process.h>\n"
  },
  {
    "path": "base/usr/include/kernel/string.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\n\nextern void * memcpy(void * restrict dest, const void * restrict src, size_t n);\nextern void * memset(void * dest, int c, size_t n);\nextern unsigned short * memsetw(unsigned short * dest, unsigned short val, int count);\nextern void * memchr(const void * src, int c, size_t n);\nextern void * memrchr(const void * m, int c, size_t n);\nextern void * memmove(void *dest, const void *src, size_t n);\n\nextern int memcmp(const void *vl, const void *vr, size_t n);\n\nextern char * strdup(const char * s);\nextern char * stpcpy(char * restrict d, const char * restrict s);\nextern char * strcpy(char * restrict dest, const char * restrict src);\nextern char * strchrnul(const char * s, int c);\nextern char * strchr(const char * s, int c);\nextern char * strrchr(const char * s, int c);\nextern char * strpbrk(const char * s, const char * b);\nextern char * strstr(const char * h, const char * n);\n\nextern int strcmp(const char * l, const char * r);\n\nextern size_t strcspn(const char * s, const char * c);\nextern size_t strspn(const char * s, const char * c);\nextern size_t strlen(const char * s);\n\nextern int atoi(const char * s);\n\nextern char * strtok_r(char * str, const char * delim, char ** saveptr);\n\nextern void * __attribute__ ((malloc)) malloc(uintptr_t size);\nextern void * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size);\nextern void * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size);\nextern void * __attribute__ ((malloc)) valloc(uintptr_t size);\nextern void free(void * ptr);\nextern uint8_t startswith(const char * str, const char * accept);\n"
  },
  {
    "path": "base/usr/include/kernel/symboltable.h",
    "content": "#pragma once\n\nextern char kernel_symbols_start[];\nextern char kernel_symbols_end[];\n\ntypedef struct {\n\tuintptr_t addr;\n\tchar name[];\n} kernel_symbol_t;\n\n\n"
  },
  {
    "path": "base/usr/include/kernel/syscall.h",
    "content": "#pragma once\n#include <kernel/types.h>\n#include <kernel/process.h>\n\n#define FD_INRANGE(FD) \\\n\t((FD) < (int)this_core->current_process->fds->length && (FD) >= 0)\n#define FD_ENTRY(FD) \\\n\t(this_core->current_process->fds->entries[(FD)])\n#define FD_CHECK(FD) \\\n\t(FD_INRANGE(FD) && FD_ENTRY(FD))\n#define FD_OFFSET(FD) \\\n\t(this_core->current_process->fds->offsets[(FD)])\n#define FD_MODE(FD) \\\n\t(this_core->current_process->fds->modes[(FD)])\n\n#define PTR_INRANGE(PTR) \\\n\t((uintptr_t)(PTR) > this_core->current_process->image.entry && ((uintptr_t)(PTR) < 0x8000000000000000))\n#define PTR_VALIDATE(PTR) \\\n\tdo { if (ptr_validate((void *)(PTR), __func__)) return -EINVAL; } while (0)\nextern int ptr_validate(void * ptr, const char * syscall);\n\nextern long arch_syscall_number(struct regs * r);\nextern long arch_syscall_arg0(struct regs * r);\nextern long arch_syscall_arg1(struct regs * r);\nextern long arch_syscall_arg2(struct regs * r);\nextern long arch_syscall_arg3(struct regs * r);\nextern long arch_syscall_arg4(struct regs * r);\n\nextern long arch_stack_pointer(struct regs * r);\nextern long arch_user_ip(struct regs * r);\n\nextern void arch_syscall_return(struct regs * r, long retval);\n\nextern void syscall_handler(struct regs * r);\n"
  },
  {
    "path": "base/usr/include/kernel/time.h",
    "content": "#pragma once\n\n#include <kernel/types.h>\n\nextern void relative_time(unsigned long, unsigned long, unsigned long *, unsigned long *);\nextern uint64_t now(void);\nextern uint64_t arch_perf_timer(void);\n"
  },
  {
    "path": "base/usr/include/kernel/tmpfs.h",
    "content": "#pragma once\n#include <kernel/vfs.h>\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n#include <sys/types.h>\n\nfs_node_t * tmpfs_create(char * name);\n\nstruct tmpfs_file {\n\tspin_lock_t lock;\n\tchar * name;\n\tint    type;\n\tint    mask;\n\tuid_t  uid;\n\tuid_t  gid;\n\tunsigned int atime;\n\tunsigned int mtime;\n\tunsigned int ctime;\n\tfs_node_t * mount;\n\tsize_t length;\n\tsize_t block_count;\n\tsize_t pointers;\n\tuintptr_t * blocks;\n\tchar * target;\n};\n\nstruct tmpfs_dir;\n\nstruct tmpfs_dir {\n\tspin_lock_t lock;\n\tchar * name;\n\tint    type;\n\tint    mask;\n\tuid_t  uid;\n\tuid_t  gid;\n\tunsigned int atime;\n\tunsigned int mtime;\n\tunsigned int ctime;\n\tfs_node_t * mount;\n\tlist_t * files;\n\tstruct tmpfs_dir * parent;\n\tspin_lock_t nest_lock;\n};\n\n"
  },
  {
    "path": "base/usr/include/kernel/tokenize.h",
    "content": "#pragma once\n\nint tokenize(char * str, const char * sep, char **buf);\n"
  },
  {
    "path": "base/usr/include/kernel/tree.h",
    "content": "#pragma once\n\n#include <kernel/list.h>\n\ntypedef struct tree_node {\n\tvoid * value;\n\tlist_t * children;\n\tstruct tree_node * parent;\n} tree_node_t;\n\ntypedef struct {\n\tsize_t nodes;\n\ttree_node_t * root;\n} tree_t;\n\ntypedef uint8_t (*tree_comparator_t) (void *, void *);\n\nextern tree_t * tree_create(void);\nextern void tree_set_root(tree_t * tree, void * value);\nextern void tree_node_destroy(tree_node_t * node);\nextern void tree_destroy(tree_t * tree);\nextern void tree_free(tree_t * tree);\nextern tree_node_t * tree_node_create(void * value);\nextern void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node);\nextern tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value);\nextern tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle);\nextern void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node);\nextern void tree_node_remove(tree_t * tree, tree_node_t * node);\nextern void tree_remove(tree_t * tree, tree_node_t * node);\nextern tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator);\nextern void tree_break_off(tree_t * tree, tree_node_t * node);\n\n"
  },
  {
    "path": "base/usr/include/kernel/types.h",
    "content": "#pragma once\n\n#include <limits.h>\n#include <stdint.h>\n#include <stddef.h>\n\n#define asm __asm__\n#define volatile __volatile__\n\n#define ALIGN (sizeof(size_t))\n\n#define ONES ((size_t)-1/UCHAR_MAX)\n#define HIGHS (ONES * (UCHAR_MAX/2+1))\n#define HASZERO(X) (((X)-ONES) & ~(X) & HIGHS)\n\n"
  },
  {
    "path": "base/usr/include/kernel/version.h",
    "content": "#pragma once\n\nextern const char * __kernel_name;\nextern const char * __kernel_version_format;\n\nextern int    __kernel_version_major;\nextern int    __kernel_version_minor;\nextern int    __kernel_version_lower;\n\nextern const char * __kernel_version_suffix;\nextern const char * __kernel_version_codename;\n\nextern const char * __kernel_arch;\n\nextern const char * __kernel_build_date;\nextern const char * __kernel_build_time;\n\nextern const char * __kernel_compiler_version;\n\n"
  },
  {
    "path": "base/usr/include/kernel/vfs.h",
    "content": "#pragma once\n\n#include <stdint.h>\n#include <stddef.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <bits/dirent.h>\n\n#define PATH_SEPARATOR '/'\n#define PATH_SEPARATOR_STRING \"/\"\n#define PATH_UP  \"..\"\n#define PATH_DOT \".\"\n\n#include <fcntl.h>\n\n#define FS_FILE        0x01\n#define FS_DIRECTORY   0x02\n#define FS_CHARDEVICE  0x04\n#define FS_BLOCKDEVICE 0x08\n#define FS_PIPE        0x10\n#define FS_SYMLINK     0x20\n#define FS_MOUNTPOINT  0x40\n#define FS_SOCKET      0x80\n\n#define _IFMT       0170000 /* type of file */\n#define     _IFDIR  0040000 /* directory */\n#define     _IFCHR  0020000 /* character special */\n#define     _IFBLK  0060000 /* block special */\n#define     _IFREG  0100000 /* regular */\n#define     _IFLNK  0120000 /* symbolic link */\n#define     _IFSOCK 0140000 /* socket */\n#define     _IFIFO  0010000 /* fifo */\n\nstruct fs_node;\n\ntypedef ssize_t (*read_type_t) (struct fs_node *,  off_t, size_t, uint8_t *);\ntypedef ssize_t (*write_type_t) (struct fs_node *, off_t, size_t, uint8_t *);\ntypedef void (*open_type_t) (struct fs_node *, unsigned int flags);\ntypedef void (*close_type_t) (struct fs_node *);\ntypedef struct dirent *(*readdir_type_t) (struct fs_node *, unsigned long);\ntypedef struct fs_node *(*finddir_type_t) (struct fs_node *, char *name);\ntypedef int (*create_type_t) (struct fs_node *, char *name, mode_t permission);\ntypedef int (*unlink_type_t) (struct fs_node *, char *name);\ntypedef int (*mkdir_type_t) (struct fs_node *, char *name, mode_t permission);\ntypedef int (*ioctl_type_t) (struct fs_node *, unsigned long request, void * argp);\ntypedef int (*get_size_type_t) (struct fs_node *);\ntypedef int (*chmod_type_t) (struct fs_node *, mode_t mode);\ntypedef int (*symlink_type_t) (struct fs_node *, char * name, char * value);\ntypedef ssize_t (*readlink_type_t) (struct fs_node *, char * buf, size_t size);\ntypedef int (*selectcheck_type_t) (struct fs_node *);\ntypedef int (*selectwait_type_t) (struct fs_node *, void * process);\ntypedef int (*chown_type_t) (struct fs_node *, uid_t, gid_t);\ntypedef int (*truncate_type_t) (struct fs_node *, size_t size);\ntypedef int (*rename_type_t) (struct fs_node *, struct fs_node *, const char *, struct fs_node *, const char *);\n\ntypedef struct fs_node {\n\tstruct fs_node * mount;      /* Root fs_node_t entry of mountpoint. */\n\tchar name[256];         /* The filename. */\n\tvoid * device;          /* Device object (optional) */\n\tmode_t mask;            /* The permissions mask. */\n\tuid_t uid;              /* The owning user. */\n\tuid_t gid;              /* The owning group. */\n\tuint64_t flags;         /* Flags (node type, etc). */\n\tuint64_t inode;         /* Inode number. */\n\tuint64_t length;        /* Size of the file, in byte. */\n\tuint64_t impl;          /* Used to keep track which fs it belongs to. */\n\tuint64_t open_flags;    /* Flags passed to open (read/write/append, etc.) */\n\tstruct fs_node *ptr;    /* Alias pointer, for symlinks. */\n\tint64_t refcount;       /* Node reference count */\n\tuint64_t nlink;         /* Number of links in underlying filesystem */\n\n\t/* times */\n\ttime_t atime;         /* Accessed */\n\ttime_t mtime;         /* Modified */\n\ttime_t ctime;         /* Created  */\n\n\t/* File operations */\n\tread_type_t read;\n\twrite_type_t write;\n\topen_type_t open;\n\tclose_type_t close;\n\treaddir_type_t readdir;\n\tfinddir_type_t finddir;\n\tcreate_type_t create;\n\tmkdir_type_t mkdir;\n\tioctl_type_t ioctl;\n\tget_size_type_t get_size;\n\tchmod_type_t chmod;\n\tunlink_type_t unlink;\n\tsymlink_type_t symlink;\n\treadlink_type_t readlink;\n\ttruncate_type_t truncate;\n\tselectcheck_type_t selectcheck;\n\tselectwait_type_t selectwait;\n\tchown_type_t chown;\n\trename_type_t rename;\n} fs_node_t;\n\nstruct vfs_entry {\n\tchar * name;\n\tfs_node_t * file;\n\tchar * device;\n\tchar * fs_type;\n};\n\nextern fs_node_t *fs_root;\nextern int pty_create(void *size, fs_node_t ** fs_master, fs_node_t ** fs_slave);\n\nint has_permission(fs_node_t *node, int permission_bit);\nssize_t read_fs(fs_node_t *node,  off_t offset, size_t size, uint8_t *buffer);\nssize_t write_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer);\nvoid open_fs(fs_node_t *node, unsigned int flags);\nvoid close_fs(fs_node_t *node);\nstruct dirent *readdir_fs(fs_node_t *node, unsigned long index);\nfs_node_t *finddir_fs(fs_node_t *node, char *name);\nint mkdir_fs(char *name, mode_t permission);\nint create_file_fs(char *name, mode_t permission);\nfs_node_t *kopen(const char *filename, unsigned int flags);\nchar *canonicalize_path(const char *cwd, const char *input);\nfs_node_t *clone_fs(fs_node_t * source);\nint ioctl_fs(fs_node_t *node, unsigned long request, void * argp);\nint chmod_fs(fs_node_t *node, mode_t mode);\nint chown_fs(fs_node_t *node, uid_t uid, gid_t gid);\nint unlink_fs(char * name);\nint symlink_fs(char * value, char * name);\nssize_t readlink_fs(fs_node_t * node, char * buf, size_t size);\nint selectcheck_fs(fs_node_t * node);\nint selectwait_fs(fs_node_t * node, void * process);\nint truncate_fs(fs_node_t * node, size_t size);\n\nvoid vfs_install(void);\nvoid * vfs_mount(const char * path, fs_node_t * local_root, const char * type, const char * options);\ntypedef fs_node_t * (*vfs_mount_callback)(const char * arg, const char * mount_point);\nint vfs_register(const char * name, vfs_mount_callback callback);\nint vfs_mount_type(const char * type, const char * arg, const char * mountpoint);\nvoid vfs_lock(fs_node_t * node);\n\n/* Debug purposes only, please */\nvoid debug_print_vfs_tree(void);\n\nvoid map_vfs_directory(const char *);\n\nint make_unix_pipe(fs_node_t ** pipes);\n\nint fprintf(fs_node_t * f, const char * fmt, ...);\n"
  },
  {
    "path": "base/usr/include/kernel/video.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#define IO_VID_WIDTH  0x5001\n#define IO_VID_HEIGHT 0x5002\n#define IO_VID_DEPTH  0x5003\n#define IO_VID_ADDR   0x5004\n#define IO_VID_SIGNAL 0x5005\n#define IO_VID_SET    0x5006\n#define IO_VID_STRIDE 0x5007\n#define IO_VID_DRIVER 0x5008\n#define IO_VID_REINIT 0x5009\n\nstruct vid_size {\n\tuint32_t width;\n\tuint32_t height;\n};\n\n#ifdef _KERNEL_\nextern void lfb_set_resolution(uint16_t x, uint16_t y);\nextern uint16_t lfb_resolution_x;\nextern uint16_t lfb_resolution_y;\nextern uint16_t lfb_resolution_b;\nextern uint32_t lfb_resolution_s;\nextern uint8_t * lfb_vid_memory;\nextern const char * lfb_driver_name;\nextern void (*lfb_resolution_impl)(uint16_t,uint16_t);\nextern int framebuffer_initialize(void);\nextern size_t lfb_memsize;\n#endif\n"
  },
  {
    "path": "base/usr/include/libgen.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\nextern char * dirname(char * path);\nextern char * basename(char * path);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/libintl.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\nextern char * gettext (const char * msgid);\nextern char * dgettext (const char * domainname, const char * msgid);\nextern char * dcgettext (const char * domainname, const char * msgid, int category);\n\nextern char * ngettext (const char * msgid, const char * msgid_plural, unsigned long int n);\nextern char * dngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n);\nextern char * dcngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n, int category);\n\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/limits.h",
    "content": "#pragma once\n\n#define _LITTLE_ENDIAN\n\n/* dummy */\n"
  },
  {
    "path": "base/usr/include/locale.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#define LC_ALL      0\n#define LC_COLLATE  1\n#define LC_CTYPE    2\n#define LC_MONETARY 3\n#define LC_NUMERIC  4\n#define LC_TIME     5\n#define LC_MESSAGES 6\n\nstruct lconv {\n    char * decimal_point;\n    char * thousands_sep;\n    char * grouping;\n    char * int_curr_symbol;\n    char * currency_symbol;\n    char * mon_decimal_point;\n    char * mon_thousands_sep;\n    char * mon_grouping;\n    char * positive_sign;\n    char * negative_sign;\n    char   int_frac_digits;\n    char   frac_digits;\n    char   p_cs_precedes;\n    char   p_sep_by_space;\n    char   n_cs_precedes;\n    char   n_sep_by_space;\n    char   p_sign_posn;\n    char   n_sign_posn;\n};\n\nextern struct lconv * localeconv(void);\nextern char * setlocale(int category, const char *locale);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/math.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#define M_PI 3.14159265358979323846\n#define M_E  2.7182818284590452354\n#define NAN (__builtin_nanf(\"\"))\n#define INFINITY (__builtin_inff())\n\nextern double floor(double x);\nextern int abs(int j);\nextern double pow(double x, double y);\nextern double exp(double x);\nextern double fmod(double x, double y);\nextern double sqrt(double x);\nextern float sqrtf(float x);\nextern double fabs(double x);\nextern float fabsf(float x);\nextern double sin(double x);\nextern double cos(double x);\n\ndouble frexp(double x, int *exp);\n\n#define HUGE_VAL (__builtin_huge_val())\n\n/* Unimplemented, but stubbed */\nextern double acos(double x);\nextern double asin(double x);\nextern double atan2(double y, double x);\nextern double ceil(double x);\nextern double cosh(double x);\nextern double ldexp(double a, int exp);\nextern double log(double x);\nextern double log10(double x);\nextern double log2(double x);\nextern double sinh(double x);\nextern double tan(double x);\nextern double tanh(double x);\nextern double atan(double x);\nextern double log1p(double x);\nextern double expm1(double x);\n\nextern double modf(double x, double *iptr);\n\nextern double hypot(double x, double y);\n\nextern double trunc(double x);\nextern double acosh(double x);\nextern double asinh(double x);\nextern double atanh(double x);\nextern double erf(double x);\nextern double erfc(double x);\nextern double gamma(double x);\nextern double tgamma(double x);\nextern double lgamma(double x);\nextern double copysign(double x, double y);\nextern double remainder(double x, double y);\n\nenum {\n    FP_NAN, FP_INFINITE, FP_ZERO, FP_SUBNORMAL, FP_NORMAL\n};\n\nextern int fpclassify(double x);\n\n#define isfinite(x) ((fpclassify(x) != FP_NAN && fpclassify(x) != FP_INFINITE))\n#define isnormal(x) (fpclassify(x) == FP_NORMAL)\n#define isnan(x)    (fpclassify(x) == FP_NAN)\n#define isinf(x)    (fpclassify(x) == FP_INFINITE)\n\nextern float ceilf(float x);\nextern double round(double x);\nextern float roundf(float x);\nextern long lroundf(float x);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/memory.h",
    "content": "#pragma once\n#include <string.h>\n"
  },
  {
    "path": "base/usr/include/net/if.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\n/* I would love to fully implement the Linux API for these, but\n * for now these are just cleaner versions of the temporary API\n * we currently provide. */\n\n#define SIOCGIFHWADDR   0x12340001 /* Get hardware address */\n#define SIOCGIFADDR     0x12340002 /* Get IPv4 address */\n#define SIOCSIFADDR     0x12340012 /* Set IPv4 address */\n#define SIOCGIFNETMASK  0x12340004 /* Get IPv4 subnet mask */\n#define SIOCSIFNETMASK  0x12340014 /* Set IPv4 subnet mask */\n#define SIOCGIFADDR6    0x12340003 /* Get IPv6 address */\n#define SIOCSIFADDR6    0x12340013 /* Set IPv6 address */\n#define SIOCGIFFLAGS    0x12340005 /* Get interface flags */\n#define SIOCGIFMTU      0x12340006 /* Get interface mtu */\n#define SIOCGIFGATEWAY  0x12340007\n#define SIOCSIFGATEWAY  0x12340017\n#define SIOCGIFCOUNTS   0x12340018\n\n/**\n * Flags for interface status\n */\n#define IFF_UP            0x0001\n#define IFF_BROADCAST     0x0002\n#define IFF_DEBUG         0x0004\n#define IFF_LOOPBACK      0x0008\n#define IFF_RUNNING       0x0010\n#define IFF_MULTICAST     0x0020\n\ntypedef struct {\n\tsize_t tx_count;\n\tsize_t tx_bytes;\n\tsize_t rx_count;\n\tsize_t rx_bytes;\n} netif_counters_t;\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/netdb.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n#include <sys/socket.h>\n\n_Begin_C_Header\n\nextern int getnameinfo(const struct sockaddr *addr, socklen_t addrlen,\n                       char *host, socklen_t hostlen,\n                       char *serv, socklen_t servlen, int flags);\n\nextern int getaddrinfo(const char *node, const char *service,\n                       const struct addrinfo *hints,\n                       struct addrinfo **res);\n\nextern void freeaddrinfo(struct addrinfo *res);\n\nstruct hostent {\n\tchar  *h_name;            /* official name of host */\n\tchar **h_aliases;         /* alias list */\n\tint    h_addrtype;        /* host address type */\n\tint    h_length;          /* length of address */\n\tchar **h_addr_list;       /* list of addresses */\n};\n\nextern struct hostent * gethostbyname(const char * name);\n\n#ifndef _KERNEL_\n#define h_addr h_addr_list[0]\n#endif\n\n#define NI_NUMERICHOST 1\n#define NI_MAXHOST     255\n\n/* Defined error codes returned by getaddrinfo */\n#define EAI_AGAIN      -1\n#define EAI_BADFLAGS   -2\n#define EAI_BADEXFLAGS -3\n#define EAI_FAMILY     -4\n#define EAI_MEMORY     -5\n#define EAI_NONAME     -6\n#define EAI_SERVICE    -7\n#define EAI_SOCKTYPE   -8\n\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/netinet/in.h",
    "content": "#pragma once\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\ntypedef uint32_t in_addr_t;\ntypedef uint16_t in_port_t;\n\nstruct in_addr {\n\tin_addr_t s_addr;\n};\n\nstruct sockaddr_in {\n\tshort            sin_family;   // e.g. AF_INET, AF_INET6\n\tunsigned short   sin_port;     // e.g. htons(3490)\n\tstruct in_addr   sin_addr;     // see struct in_addr, below\n\tchar             sin_zero[8];  // zero this if you want to\n};\n\n#define IP_TTL 2\n#define IP_RECVTTL 12\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/poll.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#define POLLIN    0x0001\n#define POLLOUT   0x0002\n#define POLLRDHUP 0x0004\n#define POLLERR   0x0008\n#define POLLHUP   0x0010\n#define POLLNVAL  0x0020\n#define POLLPRI   0x0040\n\ntypedef unsigned int nfds_t;\n\nstruct pollfd {\n\tint fd;\n\tshort events;\n\tshort revents;\n};\n\nextern int poll(struct pollfd * fds, nfds_t nfds, int timeout);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/pthread.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\ntypedef struct __pthread * pthread_t;\ntypedef unsigned int pthread_attr_t;\n\ntypedef struct {\n\tint volatile atomic_lock;\n\tint volatile readers;\n\tint writerPid;\n} pthread_rwlock_t;\n\nextern int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);\nextern void pthread_exit(void * value);\nextern int pthread_kill(pthread_t thread, int sig);\n\nextern int clone(uintptr_t,uintptr_t,void*);\nextern int gettid();\n\nextern void pthread_cleanup_push(void (*routine)(void *), void *arg);\nextern void pthread_cleanup_pop(int execute);\n\ntypedef int volatile pthread_mutex_t;\ntypedef int pthread_mutexattr_t;\n\nextern int pthread_join(pthread_t thread, void **retval);\n\n#define PTHREAD_MUTEX_INITIALIZER 0\n\nextern int pthread_mutex_lock(pthread_mutex_t *mutex);\nextern int pthread_mutex_trylock(pthread_mutex_t *mutex);\nextern int pthread_mutex_unlock(pthread_mutex_t *mutex);\nextern int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);\nextern int pthread_mutex_destroy(pthread_mutex_t *mutex);\n\nextern int pthread_attr_init(pthread_attr_t *attr);\nextern int pthread_attr_destroy(pthread_attr_t *attr);\n\nextern int pthread_rwlock_init(pthread_rwlock_t * lock, void * args);\nextern int pthread_rwlock_wrlock(pthread_rwlock_t * lock);\nextern int pthread_rwlock_rdlock(pthread_rwlock_t * lock);\nextern int pthread_rwlock_unlock(pthread_rwlock_t * lock);\nextern int pthread_rwlock_destroy(pthread_rwlock_t * lock);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/pty.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/ioctl.h>\n\n_Begin_C_Header\nextern int openpty(int * amaster, int * aslave, char * name, const struct termios *termp, const struct winsize * winp);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/pwd.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdio.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nstruct passwd {\n\tchar * pw_name;    // username\n\tchar * pw_passwd;  // password (not meaningful)\n\tuid_t  pw_uid;     // user id\n\tgid_t  pw_gid;     // group id\n\tchar * pw_comment; // used for decoration settings in toaruos\n\tchar * pw_gecos;   // full name\n\tchar * pw_dir;     // home directory\n\tchar * pw_shell;   // shell\n};\n\nstruct passwd * fgetpwent(FILE * stream);\n\nstruct passwd * getpwent(void);\nvoid setpwent(void);\nvoid endpwent(void);\nstruct passwd * getpwnam(const char * name);\nstruct passwd * getpwuid(uid_t uid);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sched.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\nextern int sched_yield(void);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/setjmp.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n/* i386 */\n#ifdef __aarch64__\n#define _JBLEN 32\n#else\n#define _JBLEN 9\n#endif\n\ntypedef long long jmp_buf[_JBLEN];\n\nextern void longjmp(jmp_buf j, int r);\nextern int setjmp(jmp_buf j);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/signal.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/signal.h>\n#include <sys/signal_defs.h>\n\n_Begin_C_Header\n\ntypedef _sig_func_ptr sighandler_t;\n\n#define SIG_DFL ((_sig_func_ptr)0)/* Default action */\n#define SIG_IGN ((_sig_func_ptr)1)/* Ignore action */\n#define SIG_ERR ((_sig_func_ptr)-1)/* Error return */\n\ntypedef int sig_atomic_t;\n\nextern sighandler_t signal(int signum, sighandler_t handler);\nextern int raise(int sig);\nextern int sigaction(int signum, struct sigaction *act, struct sigaction *oldact);\nextern int sigemptyset(sigset_t *);\nextern int sigfillset(sigset_t * set);\nextern int sigaddset(sigset_t * set, int signum);\nextern int sigdelset(sigset_t * set, int signum);\nextern int sigismember(sigset_t * set, int signum);\nextern int sigprocmask(int how, const sigset_t * set, sigset_t * oset);\nextern int sigpending(sigset_t * set);\nextern int sigsuspend(const sigset_t * set);\nextern int sigwait(const sigset_t * set, int * sig);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/stdint.h",
    "content": "/*\n * Copyright (c) 2004, 2005 by\n * Ralf Corsepius, Ulm/Germany. All rights reserved.\n *\n * Permission to use, copy, modify, and distribute this software\n * is freely granted, provided that this notice is preserved.\n */\n\n#ifndef _STDINT_H\n#define _STDINT_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(__GNUC__) && \\\n  ( (__GNUC__ >= 4) || \\\n    ( (__GNUC__ >= 3) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ > 2) ) )\n/* gcc > 3.2 implicitly defines the values we are interested */\n#define __STDINT_EXP(x) __##x##__\n#else\n#define __STDINT_EXP(x) x\n#include <limits.h>\n#endif\n\n/* Check if \"long long\" is 64bit wide */\n/* Modern GCCs provide __LONG_LONG_MAX__, SUSv3 wants LLONG_MAX */\n#if ( defined(__LONG_LONG_MAX__) && (__LONG_LONG_MAX__ > 0x7fffffff) ) \\\n  || ( defined(LLONG_MAX) && (LLONG_MAX > 0x7fffffff) )\n#define __have_longlong64 1\n#endif\n\n/* Check if \"long\" is 64bit or 32bit wide */\n#if __STDINT_EXP(LONG_MAX) > 0x7fffffff\n#define __have_long64 1\n#elif __STDINT_EXP(LONG_MAX) == 0x7fffffff && !defined(__SPU__)\n#define __have_long32 1\n#endif\n\n#if __STDINT_EXP(SCHAR_MAX) == 0x7f\ntypedef signed char int8_t ;\ntypedef unsigned char uint8_t ;\n#define __int8_t_defined 1\n#endif\n\n#if __int8_t_defined\ntypedef signed char int_least8_t;\ntypedef unsigned char uint_least8_t;\n#define __int_least8_t_defined 1\n#endif\n\n#if __STDINT_EXP(SHRT_MAX) == 0x7fff\ntypedef signed short int16_t;\ntypedef unsigned short uint16_t;\n#define __int16_t_defined 1\n#elif __STDINT_EXP(INT_MAX) == 0x7fff\ntypedef signed int int16_t;\ntypedef unsigned int uint16_t;\n#define __int16_t_defined 1\n#elif __STDINT_EXP(SCHAR_MAX) == 0x7fff\ntypedef signed char int16_t;\ntypedef unsigned char uint16_t;\n#define __int16_t_defined 1\n#endif\n\n#if __int16_t_defined\ntypedef int16_t   \tint_least16_t;\ntypedef uint16_t \tuint_least16_t;\n#define __int_least16_t_defined 1\n\n#if !__int_least8_t_defined\ntypedef int16_t\t   \tint_least8_t;\ntypedef uint16_t  \tuint_least8_t;\n#define __int_least8_t_defined 1\n#endif\n#endif\n\n#if __have_long32\ntypedef signed long int32_t;\ntypedef unsigned long uint32_t;\n#define __int32_t_defined 1\n#elif __STDINT_EXP(INT_MAX) == 0x7fffffffL\ntypedef signed int int32_t;\ntypedef unsigned int uint32_t;\n#define __int32_t_defined 1\n#elif __STDINT_EXP(SHRT_MAX) == 0x7fffffffL\ntypedef signed short int32_t;\ntypedef unsigned short uint32_t;\n#define __int32_t_defined 1\n#elif __STDINT_EXP(SCHAR_MAX) == 0x7fffffffL\ntypedef signed char int32_t;\ntypedef unsigned char uint32_t;\n#define __int32_t_defined 1\n#endif\n\n#if __int32_t_defined\ntypedef int32_t   \tint_least32_t;\ntypedef uint32_t \tuint_least32_t;\n#define __int_least32_t_defined 1\n\n#if !__int_least8_t_defined\ntypedef int32_t\t   \tint_least8_t;\ntypedef uint32_t  \tuint_least8_t;\n#define __int_least8_t_defined 1\n#endif\n\n#if !__int_least16_t_defined\ntypedef int32_t\t   \tint_least16_t;\ntypedef uint32_t  \tuint_least16_t;\n#define __int_least16_t_defined 1\n#endif\n#endif\n\n#if __have_long64\ntypedef signed long int64_t;\ntypedef unsigned long uint64_t;\n#define __int64_t_defined 1\n#elif __have_longlong64\ntypedef signed long long int64_t;\ntypedef unsigned long long uint64_t;\n#define __int64_t_defined 1\n#elif  __STDINT_EXP(INT_MAX) > 0x7fffffff\ntypedef signed int int64_t;\ntypedef unsigned int uint64_t;\n#define __int64_t_defined 1\n#endif\n\n#if __int64_t_defined\ntypedef int64_t   \tint_least64_t;\ntypedef uint64_t \tuint_least64_t;\n#define __int_least64_t_defined 1\n\n#if !__int_least8_t_defined\ntypedef int64_t\t   \tint_least8_t;\ntypedef uint64_t  \tuint_least8_t;\n#define __int_least8_t_defined 1\n#endif\n\n#if !__int_least16_t_defined\ntypedef int64_t\t   \tint_least16_t;\ntypedef uint64_t  \tuint_least16_t;\n#define __int_least16_t_defined 1\n#endif\n\n#if !__int_least32_t_defined\ntypedef int64_t\t   \tint_least32_t;\ntypedef uint64_t  \tuint_least32_t;\n#define __int_least32_t_defined 1\n#endif\n#endif\n\n/*\n * Fastest minimum-width integer types\n *\n * Assume int to be the fastest type for all types with a width \n * less than __INT_MAX__ rsp. INT_MAX\n */\n#if __STDINT_EXP(INT_MAX) >= 0x7f\n  typedef signed int int_fast8_t;\n  typedef unsigned int uint_fast8_t;\n#define __int_fast8_t_defined 1\n#endif\n\n#if __STDINT_EXP(INT_MAX) >= 0x7fff\n  typedef signed int int_fast16_t;\n  typedef unsigned int uint_fast16_t;\n#define __int_fast16_t_defined 1\n#endif\n\n#if __STDINT_EXP(INT_MAX) >= 0x7fffffff\n  typedef signed int int_fast32_t;\n  typedef unsigned int uint_fast32_t;\n#define __int_fast32_t_defined 1\n#endif\n\n#if __STDINT_EXP(INT_MAX) > 0x7fffffff\n  typedef signed int int_fast64_t;\n  typedef unsigned int uint_fast64_t;\n#define __int_fast64_t_defined 1\n#endif\n\n/*\n * Fall back to [u]int_least<N>_t for [u]int_fast<N>_t types\n * not having been defined, yet.\n * Leave undefined, if [u]int_least<N>_t should not be available.\n */\n#if !__int_fast8_t_defined\n#if __int_least8_t_defined\n  typedef int_least8_t int_fast8_t;\n  typedef uint_least8_t uint_fast8_t;\n#define __int_fast8_t_defined 1\n#endif\n#endif\n\n#if !__int_fast16_t_defined\n#if __int_least16_t_defined\n  typedef int_least16_t int_fast16_t;\n  typedef uint_least16_t uint_fast16_t;\n#define __int_fast16_t_defined 1\n#endif\n#endif\n\n#if !__int_fast32_t_defined\n#if __int_least32_t_defined\n  typedef int_least32_t int_fast32_t;\n  typedef uint_least32_t uint_fast32_t;\n#define __int_fast32_t_defined 1\n#endif\n#endif\n\n#if !__int_fast64_t_defined\n#if __int_least64_t_defined\n  typedef int_least64_t int_fast64_t;\n  typedef uint_least64_t uint_fast64_t;\n#define __int_fast64_t_defined 1\n#endif\n#endif\n\n/* Greatest-width integer types */\n/* Modern GCCs provide __INTMAX_TYPE__ */\n#if defined(__INTMAX_TYPE__)\n  typedef __INTMAX_TYPE__ intmax_t;\n#elif __have_longlong64\n  typedef signed long long intmax_t;\n#else\n  typedef signed long intmax_t;\n#endif\n\n/* Modern GCCs provide __UINTMAX_TYPE__ */\n#if defined(__UINTMAX_TYPE__)\n  typedef __UINTMAX_TYPE__ uintmax_t;\n#elif __have_longlong64\n  typedef unsigned long long uintmax_t;\n#else\n  typedef unsigned long uintmax_t;\n#endif\n\n/*\n * GCC doesn't provide an appropriate macro for [u]intptr_t\n * For now, use __PTRDIFF_TYPE__\n */\n#if defined(__PTRDIFF_TYPE__)\ntypedef signed __PTRDIFF_TYPE__ intptr_t;\ntypedef unsigned __PTRDIFF_TYPE__ uintptr_t;\n#define INTPTR_MAX PTRDIFF_MAX\n#define INTPTR_MIN PTRDIFF_MIN\n#ifdef __UINTPTR_MAX__\n#define UINTPTR_MAX __UINTPTR_MAX__\n#else\n#define UINTPTR_MAX (2UL * PTRDIFF_MAX + 1)\n#endif\n#else\n/*\n * Fallback to hardcoded values, \n * should be valid on cpu's with 32bit int/32bit void*\n */\ntypedef signed long intptr_t;\ntypedef unsigned long uintptr_t;\n#define INTPTR_MAX __STDINT_EXP(LONG_MAX)\n#define INTPTR_MIN (-__STDINT_EXP(LONG_MAX) - 1)\n#define UINTPTR_MAX (__STDINT_EXP(LONG_MAX) * 2UL + 1)\n#endif\n\n/* Limits of Specified-Width Integer Types */\n\n#if __int8_t_defined\n#define INT8_MIN \t-128\n#define INT8_MAX \t 127\n#define UINT8_MAX \t 255\n#endif\n\n#if __int_least8_t_defined\n#define INT_LEAST8_MIN \t-128\n#define INT_LEAST8_MAX \t 127\n#define UINT_LEAST8_MAX\t 255\n#else\n#error required type int_least8_t missing\n#endif\n\n#if __int16_t_defined\n#define INT16_MIN \t-32768\n#define INT16_MAX \t 32767\n#define UINT16_MAX \t 65535\n#endif\n\n#if __int_least16_t_defined\n#define INT_LEAST16_MIN\t-32768\n#define INT_LEAST16_MAX\t 32767\n#define UINT_LEAST16_MAX 65535\n#else\n#error required type int_least16_t missing\n#endif\n\n#if __int32_t_defined\n#if __have_long32\n#define INT32_MIN \t (-2147483647L-1)\n#define INT32_MAX \t 2147483647L\n#define UINT32_MAX       4294967295UL\n#else\n#define INT32_MIN \t (-2147483647-1)\n#define INT32_MAX \t 2147483647\n#define UINT32_MAX       4294967295U\n#endif\n#endif\n\n#if __int_least32_t_defined\n#if __have_long32\n#define INT_LEAST32_MIN  (-2147483647L-1)\n#define INT_LEAST32_MAX  2147483647L\n#define UINT_LEAST32_MAX 4294967295UL\n#else\n#define INT_LEAST32_MIN  (-2147483647-1)\n#define INT_LEAST32_MAX  2147483647\n#define UINT_LEAST32_MAX 4294967295U\n#endif\n#else\n#error required type int_least32_t missing\n#endif\n\n#if __int64_t_defined\n#if __have_long64\n#define INT64_MIN \t(-9223372036854775807L-1L)\n#define INT64_MAX \t 9223372036854775807L\n#define UINT64_MAX \t18446744073709551615U\n#elif __have_longlong64\n#define INT64_MIN \t(-9223372036854775807LL-1LL)\n#define INT64_MAX \t 9223372036854775807LL\n#define UINT64_MAX \t18446744073709551615ULL\n#endif\n#endif\n\n#if __int_least64_t_defined\n#if __have_long64\n#define INT_LEAST64_MIN  (-9223372036854775807L-1L)\n#define INT_LEAST64_MAX  9223372036854775807L\n#define UINT_LEAST64_MAX 18446744073709551615U\n#elif __have_longlong64\n#define INT_LEAST64_MIN  (-9223372036854775807LL-1LL)\n#define INT_LEAST64_MAX  9223372036854775807LL\n#define UINT_LEAST64_MAX 18446744073709551615ULL\n#endif\n#endif\n\n#if __int_fast8_t_defined\n#if __STDINT_EXP(INT_MAX) >= 0x7f\n#define INT_FAST8_MIN\t(-__STDINT_EXP(INT_MAX)-1)\n#define INT_FAST8_MAX\t__STDINT_EXP(INT_MAX)\n#define UINT_FAST8_MAX\t(__STDINT_EXP(INT_MAX)*2U+1U)\n#else\n#define INT_FAST8_MIN\tINT_LEAST8_MIN\n#define INT_FAST8_MAX\tINT_LEAST8_MAX\n#define UINT_FAST8_MAX\tUINT_LEAST8_MAX\n#endif\n#endif\n\n#if __int_fast16_t_defined\n#if __STDINT_EXP(INT_MAX) >= 0x7fff\n#define INT_FAST16_MIN\t(-__STDINT_EXP(INT_MAX)-1)\n#define INT_FAST16_MAX\t__STDINT_EXP(INT_MAX)\n#define UINT_FAST16_MAX\t(__STDINT_EXP(INT_MAX)*2U+1U)\n#else\n#define INT_FAST16_MIN\tINT_LEAST16_MIN\n#define INT_FAST16_MAX\tINT_LEAST16_MAX\n#define UINT_FAST16_MAX\tUINT_LEAST16_MAX\n#endif\n#endif\n\n#if __int_fast32_t_defined\n#if __STDINT_EXP(INT_MAX) >= 0x7fffffff\n#define INT_FAST32_MIN\t(-__STDINT_EXP(INT_MAX)-1)\n#define INT_FAST32_MAX\t__STDINT_EXP(INT_MAX)\n#define UINT_FAST32_MAX\t(__STDINT_EXP(INT_MAX)*2U+1U)\n#else\n#define INT_FAST32_MIN\tINT_LEAST32_MIN\n#define INT_FAST32_MAX\tINT_LEAST32_MAX\n#define UINT_FAST32_MAX\tUINT_LEAST32_MAX\n#endif\n#endif\n\n#if __int_fast64_t_defined\n#if __STDINT_EXP(INT_MAX) > 0x7fffffff\n#define INT_FAST64_MIN\t(-__STDINT_EXP(INT_MAX)-1)\n#define INT_FAST64_MAX\t__STDINT_EXP(INT_MAX)\n#define UINT_FAST64_MAX\t(__STDINT_EXP(INT_MAX)*2U+1U)\n#else\n#define INT_FAST64_MIN\tINT_LEAST64_MIN\n#define INT_FAST64_MAX\tINT_LEAST64_MAX\n#define UINT_FAST64_MAX\tUINT_LEAST64_MAX\n#endif\n#endif\n\n#ifdef __INTMAX_MAX__\n#define INTMAX_MAX __INTMAX_MAX__\n#define INTMAX_MIN (-INTMAX_MAX - 1)\n#elif defined(__INTMAX_TYPE__)\n/* All relevant GCC versions prefer long to long long for intmax_t.  */\n#define INTMAX_MAX INT64_MAX\n#define INTMAX_MIN INT64_MIN\n#endif\n\n#ifdef __UINTMAX_MAX__\n#define UINTMAX_MAX __UINTMAX_MAX__\n#elif defined(__UINTMAX_TYPE__)\n/* All relevant GCC versions prefer long to long long for intmax_t.  */\n#define UINTMAX_MAX UINT64_MAX\n#endif\n\n/* This must match size_t in stddef.h, currently long unsigned int */\n#ifdef __SIZE_MAX__\n#define SIZE_MAX __SIZE_MAX__\n#else\n#define SIZE_MAX (__STDINT_EXP(LONG_MAX) * 2UL + 1)\n#endif\n\n/* This must match sig_atomic_t in <signal.h> (currently int) */\n#define SIG_ATOMIC_MIN (-__STDINT_EXP(INT_MAX) - 1)\n#define SIG_ATOMIC_MAX __STDINT_EXP(INT_MAX)\n\n/* This must match ptrdiff_t  in <stddef.h> (currently long int) */\n#ifdef __PTRDIFF_MAX__\n#define PTRDIFF_MAX __PTRDIFF_MAX__\n#else\n#define PTRDIFF_MAX __STDINT_EXP(LONG_MAX)\n#endif\n#define PTRDIFF_MIN (-PTRDIFF_MAX - 1)\n\n#ifdef __WCHAR_MAX__\n#define WCHAR_MAX __WCHAR_MAX__\n#endif\n#ifdef __WCHAR_MIN__\n#define WCHAR_MIN __WCHAR_MIN__\n#endif\n\n/* wint_t is unsigned int on almost all GCC targets.  */\n#ifdef __WINT_MAX__\n#define WINT_MAX __WINT_MAX__\n#else\n#define WINT_MAX (__STDINT_EXP(INT_MAX) * 2U + 1U)\n#endif\n#ifdef __WINT_MIN__\n#define WINT_MIN __WINT_MIN__\n#else\n#define WINT_MIN 0U\n#endif\n\n/** Macros for minimum-width integer constant expressions */\n#define INT8_C(x)\tx\n#if __STDINT_EXP(INT_MAX) > 0x7f\n#define UINT8_C(x)\tx\n#else\n#define UINT8_C(x)\tx##U\n#endif\n\n#define INT16_C(x)\tx\n#if __STDINT_EXP(INT_MAX) > 0x7fff\n#define UINT16_C(x)\tx\n#else\n#define UINT16_C(x)\tx##U\n#endif\n\n#if __have_long32\n#define INT32_C(x)\tx##L\n#define UINT32_C(x)\tx##UL\n#else\n#define INT32_C(x)\tx\n#define UINT32_C(x)\tx##U\n#endif\n\n#if __int64_t_defined\n#if __have_long64\n#define INT64_C(x)\tx##L\n#define UINT64_C(x)\tx##UL\n#else\n#define INT64_C(x)\tx##LL\n#define UINT64_C(x)\tx##ULL\n#endif\n#endif\n\n/** Macros for greatest-width integer constant expression */\n#if __have_long64\n#define INTMAX_C(x)\tx##L\n#define UINTMAX_C(x)\tx##UL\n#else\n#define INTMAX_C(x)\tx##LL\n#define UINTMAX_C(x)\tx##ULL\n#endif\n\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* _STDINT_H */\n"
  },
  {
    "path": "base/usr/include/stdio.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n#include <sys/types.h>\n#include <va_list.h>\n\n_Begin_C_Header\n\ntypedef struct _FILE FILE;\n#define __DEFINED_FILE\n\n#define BUFSIZ 8192\n\nextern FILE * stdin;\nextern FILE * stdout;\nextern FILE * stderr;\n\n#define EOF (-1)\n\n#define SEEK_SET 0\n#define SEEK_CUR 1\n#define SEEK_END 2\n\nextern FILE * fopen(const char *path, const char *mode);\nextern int fclose(FILE * stream);\nextern int fseek(FILE * stream, long offset, int whence);\nextern long ftell(FILE * stream);\nextern FILE * fdopen(int fd, const char *mode);\nextern FILE * freopen(const char *path, const char *mode, FILE * stream);\n\nextern size_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream);\nextern size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream);\n\nextern int fileno(FILE * stream);\nextern int fflush(FILE * stream);\n\nextern int asprintf(char ** ret, const char * fmt, ...);\nextern int vasprintf(char ** buf, const char *fmt, va_list args);\nextern int sprintf(char *buf, const char *fmt, ...);\nextern int fprintf(FILE *stream, const char *fmt, ...);\nextern int printf(const char *fmt, ...);\nextern int snprintf(char * buf, size_t size, const char * fmt, ...);\nextern int vsprintf(char * buf, const char *fmt, va_list args);\nextern int vsnprintf(char * buf, size_t size, const char *fmt, va_list args);\nextern int vfprintf(FILE * device, const char *format, va_list ap);\nextern int vprintf(const char *format, va_list ap);\n\nextern int puts(const char *s);\nextern int fputs(const char *s, FILE *stream);\nextern int fputc(int c, FILE *stream);\nextern int putc(int c, FILE *stream);\nextern int putchar(int c);\nextern int fgetc(FILE *stream);\nextern int getc(FILE *stream);\nextern char *fgets(char *s, int size, FILE *stream);\nextern int getchar(void);\n\nextern void rewind(FILE *stream);\nextern void setbuf(FILE * stream, char * buf);\n\nextern void perror(const char *s);\n\nextern int ungetc(int c, FILE * stream);\n\nextern int feof(FILE * stream);\nextern void clearerr(FILE * stream);\nextern int ferror(FILE * stream);\n\nextern int _fwouldblock(FILE * stream);\n\nextern FILE * tmpfile(void);\n\nextern int setvbuf(FILE * stream, char * buf, int mode, size_t size);\n\nextern int remove(const char * pathname);\nextern int rename(const char * oldpath, const char * newpath);\n\n#define _IONBF 0\n#define _IOLBF 1\n#define _IOFBF 2\n\nextern char * tmpnam(char * s);\n#define L_tmpnam 256\n\nextern int vsscanf(const char *str, const char *format, va_list ap);\nextern int sscanf(const char *str, const char *format, ...);\nextern int vfscanf(FILE * stream, const char *format, va_list ap);\nextern int fscanf(FILE *stream, const char *format, ...);\nextern int scanf(const char *format, ...);\n\ntypedef long fpos_t;\n\nextern int fgetpos(FILE *stream, fpos_t *pos);\nextern int fsetpos(FILE *stream, const fpos_t *pos);\n\n/* Compatibility */\n#define FILENAME_MAX 1024\n\nextern ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream);\nextern ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream);\n\n_End_C_Header;\n"
  },
  {
    "path": "base/usr/include/stdlib.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n\n_Begin_C_Header\n\nextern void exit(int status);\nextern char * getenv(const char *name);\n\nextern void *malloc(size_t size);\nextern void free(void *ptr);\nextern void *calloc(size_t nmemb, size_t size);\nextern void *realloc(void *ptr, size_t size);\nextern void *valloc(size_t size);\n\nextern void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void*,const void*));\nextern void qsort_r(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void * arg);\n\nextern int system(const char * command);\n\nextern int abs(int j);\n\nextern int putenv(char * name);\nextern int setenv(const char *name, const char *value, int overwrite);\nextern int unsetenv(const char * str);\n\nextern double strtod(const char *nptr, char **endptr);\nextern float strtof(const char *nptr, char **endptr);\nextern double atof(const char * nptr);\nextern int atoi(const char * nptr);\nextern long atol(const char * nptr);\nextern long int labs(long int j);\nextern long int strtol(const char * s, char **endptr, int base);\nextern long long int strtoll(const char *nptr, char **endptr, int base);\nextern unsigned long int strtoul(const char *nptr, char **endptr, int base);\nextern unsigned long long int strtoull(const char *nptr, char **endptr, int base);\n\nextern void srand(unsigned int);\nextern int rand(void);\n\n#define ATEXIT_MAX 32\nextern int atexit(void (*h)(void));\nextern void _handle_atexit(void);\n\n#define RAND_MAX 0x7FFFFFFF\n\nextern void abort(void);\n\n#define EXIT_SUCCESS 0\n#define EXIT_FAILURE 1\n\n#define NULL 0\n\nextern void *bsearch(const void *key, const void *base, size_t nmemb, size_t size,\n\tint (*compar)(const void *, const void *));\n\nextern char * mktemp(char *);\nextern int mkstemp(char *);\n\nextern size_t mbstowcs(wchar_t *dest, const char *src, size_t n);\nextern size_t wcstombs(char * dest, const wchar_t *src, size_t n);\n\ntypedef struct { int quot; int rem; } div_t;\ntypedef struct { long int quot; long int rem; } ldiv_t;\n\nextern div_t div(int numerator, int denominator);\nextern ldiv_t ldiv(long numerator, long denominator);\n\n/* These are supposed to be in limits, but gcc screwed us */\n#define PATH_MAX 4096\n#define NAME_MAX 255\nextern char *realpath(const char *path, char *resolved_path);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/string.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n\n_Begin_C_Header\n\nextern void * memset(void * dest, int c, size_t n);\nextern void * memcpy(void * dest, const void * src, size_t n);\nextern void * memmove(void * dest, const void * src, size_t n);\n\nextern void * memchr(const void * src, int c, size_t n);\nextern void * memrchr(const void * m, int c, size_t n);\nextern int memcmp(const void *vl, const void *vr, size_t n);\n\nextern char * strdup(const char * s);\nextern char * stpcpy(char * d, const char * s);\nextern char * strcpy(char * dest, const char * src);\nextern char * strchrnul(const char * s, int c);\nextern char * strchr(const char * s, int c);\nextern char * strrchr(const char * s, int c);\nextern char * strpbrk(const char * s, const char * b);\nextern char * strstr(const char * h, const char * n);\n\nextern char * strncpy(char * dest, const char * src, size_t n);\n\nextern int strcmp(const char * l, const char * r);\nextern int strncmp(const char *s1, const char *s2, size_t n);\nextern int strcoll(const char * s1, const char * s2);\n\nextern size_t strcspn(const char * s, const char * c);\nextern size_t strspn(const char * s, const char * c);\nextern size_t strlen(const char * s);\n\nextern char * strcat(char *dest, const char *src);\nextern char * strncat(char *dest, const char *src, size_t n);\n\nextern char * strtok(char * str, const char * delim);\nextern char * strtok_r(char * str, const char * delim, char ** saveptr);\n\nextern char * strncpy(char *dest, const char *src, size_t n);\n\nextern char * strerror(int errnum);\nextern size_t strxfrm(char *dest, const char *src, size_t n);\n\nextern char * strsignal(int sign);\nextern const char * const sys_siglist[];\n\n_End_C_Header\n\n#include <strings.h>\n"
  },
  {
    "path": "base/usr/include/strings.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n\n_Begin_C_Header\nextern int strcasecmp(const char *s1, const char *s2);\nextern int strncasecmp(const char *s1, const char *s2, size_t n);\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/sys/fswait.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\nextern int fswait(int count, int * fds);\nextern int fswait2(int count, int * fds, int timeout);\nextern int fswait3(int count, int * fds, int timeout, int * out);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/ioctl.h",
    "content": "#pragma once\n\n#include <termios.h>\n\n#define IOCTLDTYPE 0x4F00\n\n#define IOCTL_DTYPE_UNKNOWN -1\n#define IOCTL_DTYPE_FILE     1\n#define IOCTL_DTYPE_TTY      2\n\n#define IOCTLTTYNAME  0x4F01\n#define IOCTLTTYLOGIN 0x4F02\n#define IOCTLSYNC     0x4F03\n\n#define IOCTL_PACKETFS_QUEUED 0x5050\n\n#define FIONBIO  0x4e424c4b\n\n"
  },
  {
    "path": "base/usr/include/sys/mman.h",
    "content": "#pragma once\n\n/* Nothing here, we don't have an mmap implementation yet? */\n"
  },
  {
    "path": "base/usr/include/sys/mount.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\nextern int mount(char * source, char * target, char * type, unsigned long flags, void * data);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/param.h",
    "content": "#pragma once\n\n#include <sys/types.h>\n#include <stddef.h>\n#include <limits.h>\n#include <signal.h>\n\n#define __LITTLE_ENDIAN 1234\n#define __BIG_ENDIAN    4321\n#define __BYTE_ORDER    __LITLE_ENDIAN\n\n#define BIG_ENDIAN __BIG_ENDIAN\n#define LITTLE_ENDIAN __LITTLE_ENDIAN\n#define BYTE_ORDER __BYTE_ORDER\n"
  },
  {
    "path": "base/usr/include/sys/ptrace.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nenum __ptrace_request {\n\tPTRACE_ATTACH,\n\tPTRACE_CONT,\n\tPTRACE_DETACH,\n\tPTRACE_TRACEME,\n\tPTRACE_GETREGS,\n\tPTRACE_PEEKDATA,\n\tPTRACE_SIGNALS_ONLY_PLZ,\n\tPTRACE_POKEDATA,\n\tPTRACE_SINGLESTEP,\n\tPTRACE_SETREGS\n};\n\nenum __ptrace_event {\n\tPTRACE_EVENT_SYSCALL_ENTER,\n\tPTRACE_EVENT_SYSCALL_EXIT,\n\tPTRACE_EVENT_SINGLESTEP,\n};\n\n#ifndef __kernel__\nextern long ptrace(enum __ptrace_request request, pid_t pid, void * addr, void * data);\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/shm.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\nextern void * shm_obtain(const char * path, size_t * size);\nextern int shm_release(const char * path);\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/sys/signal.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n#define SIGEV_NONE   1\n#define SIGEV_SIGNAL 2\n#define SIGEV_THREAD 3\n\n#define SI_USER    1\n#define SI_QUEUE   2\n#define SI_TIMER   3\n#define SI_ASYNCIO 4\n#define SI_MESGQ   5\n\n#define SA_NOCLDSTOP 1\n#define SA_SIGINFO   2\n#define SA_NODEFER   4\n#define SA_RESETHAND 8\n#define SA_RESTART   16\n\n#define SIG_SETMASK 0\n#define SIG_BLOCK 1\n#define SIG_UNBLOCK 2\n\n#define sa_handler   _signal_handlers._handler\n#define sa_sigaction _signal_handlers._sigaction\n\nunion sigval {\n\tint    sival_int;\n\tvoid  *sival_ptr;\n};\n\nstruct sigevent {\n\tint              sigev_notify;\n\tint              sigev_signo;\n\tunion sigval     sigev_value;\n};\n\ntypedef struct {\n\tint          si_signo;\n\tint          si_code;\n\tunion sigval si_value;\n} siginfo_t;\n\ntypedef unsigned long sigset_t;\ntypedef void (*_sig_func_ptr)(int);\n\nstruct sigaction {\n\tint         sa_flags;\n\tsigset_t    sa_mask;\n\tunion {\n\t\t_sig_func_ptr _handler;\n\t\tvoid      (*_sigaction)( int, siginfo_t *, void * );\n\t} _signal_handlers;\n};\n\n\nextern int kill(pid_t, int);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/signal_defs.h",
    "content": "#pragma once\n\n/* Signal names (from the Unix specification on signals) */\n#define SIGHUP      1  /* Hangup */\n#define SIGINT      2  /* Interupt */\n#define SIGQUIT     3  /* Quit */\n#define SIGILL      4  /* Illegal instruction */\n#define SIGTRAP     5  /* A breakpoint or trace instruction has been reached */\n#define SIGABRT     6  /* Another process has requested that you abort */\n#define SIGEMT      7  /* Emulation trap XXX */\n#define SIGFPE      8  /* Floating-point arithmetic exception */\n#define SIGKILL     9  /* You have been stabbed repeated with a large knife */\n#define SIGBUS      10 /* Bus error (device error) */\n#define SIGSEGV     11 /* Segmentation fault */\n#define SIGSYS      12 /* Bad system call */\n#define SIGPIPE     13 /* Attempted to read or write from a broken pipe */\n#define SIGALRM     14 /* This is your wakeup call. */\n#define SIGTERM     15 /* You have been Schwarzenegger'd */\n#define SIGUSR1     16 /* User Defined Signal #1 */\n#define SIGUSR2     17 /* User Defined Signal #2 */\n#define SIGCHLD     18 /* Child status report */\n#define SIGPWR      19 /* We need moar powah! */\n#define SIGWINCH    20 /* Your containing terminal has changed size */\n#define SIGURG      21 /* An URGENT! event (On a socket) */\n#define SIGPOLL     22 /* XXX OBSOLETE; socket i/o possible */\n#define SIGSTOP     23 /* Stopped (signal) */\n#define SIGTSTP     24 /* ^Z (suspend) */\n#define SIGCONT     25 /* Unsuspended (please, continue) */\n#define SIGTTIN     26 /* TTY input has stopped */\n#define SIGTTOUT    27 /* TTY output has stopped */\n#define SIGVTALRM   28 /* Virtual timer has expired */\n#define SIGPROF     29 /* Profiling timer expired */\n#define SIGXCPU     30 /* CPU time limit exceeded */\n#define SIGXFSZ     31 /* File size limit exceeded */\n#define SIGWAITING  32 /* Herp */\n#define SIGDIAF     33 /* Die in a fire */\n#define SIGHATE     34 /* The sending process does not like you */\n#define SIGWINEVENT 35 /* Window server event */\n#define SIGCAT      36 /* Everybody loves cats */\n#define SIGTTOU     37\n\n#define NUMSIGNALS  38\n#define NSIG        NUMSIGNALS\n"
  },
  {
    "path": "base/usr/include/sys/socket.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\n#define AF_UNSPEC 0\n#define AF_INET 1\n#define AF_RAW 2\n\n#define PF_INET AF_INET\n\n#define SOCK_STREAM 1\n#define SOCK_DGRAM  2\n#define SOCK_RAW    3\n\n#define IPPROTO_IP  0\n#define IPPROTO_ICMP 1\n#define IPPROTO_TCP 6\n#define IPPROTO_UDP 17\n\n#define SOL_SOCKET 1\n\n#define SO_KEEPALIVE 1\n#define SO_REUSEADDR 2\n#define SO_BINDTODEVICE 3\n\ntypedef size_t socklen_t;\n\nstruct sockaddr {\n\tunsigned short    sa_family;    // address family, AF_xxx\n\tchar              sa_data[14];  // 14 bytes of protocol address\n};\n\nstruct addrinfo {\n\tint              ai_flags;\n\tint              ai_family;\n\tint              ai_socktype;\n\tint              ai_protocol;\n\tsocklen_t        ai_addrlen;\n\tstruct sockaddr *ai_addr;\n\tchar            *ai_canonname;\n\tstruct addrinfo *ai_next;\n};\n\nstruct iovec {                    /* Scatter/gather array items */\n\tvoid  *iov_base;              /* Starting address */\n\tsize_t iov_len;               /* Number of bytes to transfer */\n};\n\nstruct msghdr {\n\tvoid         *msg_name;       /* optional address */\n\tsocklen_t     msg_namelen;    /* size of address */\n\tstruct iovec *msg_iov;        /* scatter/gather array */\n\tsize_t        msg_iovlen;     /* # elements in msg_iov */\n\tvoid         *msg_control;    /* ancillary data, see below */\n\tsize_t        msg_controllen; /* ancillary data buffer len */\n\tint           msg_flags;      /* flags on received message */\n};\n\nstruct sockaddr_storage {\n\tunsigned short ss_family;\n\tchar _ss_pad[128];\n};\n\nstruct cmsghdr {\n\tsocklen_t cmsg_len;\n\tint cmsg_level;\n\tint cmsg_type;\n\tunsigned char cmsg_data[];\n};\n\n#define CMSG_DATA(cmsg) (&((struct cmsghdr*)(cmsg))->cmsg_data)\n\n#ifndef _KERNEL_\nextern ssize_t recv(int sockfd, void *buf, size_t len, int flags);\nextern ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);\nextern ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);\n\nssize_t send(int sockfd, const void *buf, size_t len, int flags);\nssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);\nssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);\n\nextern int socket(int domain, int type, int protocol);\n\nextern int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);\nextern int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen);\nextern int accept4(int sockfd, struct sockaddr * addr, socklen_t * addrlen, int flags);\nextern int listen(int sockfd, int backlog);\nextern int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);\nextern int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);\nextern int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);\nextern int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);\n\nextern int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);\nextern int shutdown(int sockfd, int how);\n#endif\n\n_End_C_Header\n\n\n"
  },
  {
    "path": "base/usr/include/sys/stat.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n#include <sys/types.h>\n#include <bits/timespec.h>\n\n_Begin_C_Header\n\nstruct stat  {\n\tdev_t  st_dev;\n\tino_t  st_ino;\n\tmode_t  st_mode;\n\tnlink_t  st_nlink;\n\tuid_t  st_uid;\n\tgid_t  st_gid;\n\tdev_t  st_rdev;\n\toff_t  st_size;\n\tstruct timespec st_atim;\n\tstruct timespec st_mtim;\n\tstruct timespec st_ctim;\n\tblksize_t  st_blksize;\n\tblkcnt_t  st_blocks;\n#define st_atime st_atim.tv_sec\n#define st_mtime st_mtim.tv_sec\n#define st_ctime st_ctim.tv_sec\n};\n\n#define _IFMT       0170000 /* type of file */\n#define     _IFDIR  0040000 /* directory */\n#define     _IFCHR  0020000 /* character special */\n#define     _IFBLK  0060000 /* block special */\n#define     _IFREG  0100000 /* regular */\n#define     _IFLNK  0120000 /* symbolic link */\n#define     _IFSOCK 0140000 /* socket */\n#define     _IFIFO  0010000 /* fifo */\n\n#define \tS_BLKSIZE  1024 /* size of a block */\n\n#define\tS_ISUID\t\t0004000\t/* set user id on execution */\n#define\tS_ISGID\t\t0002000\t/* set group id on execution */\n#define\tS_ISVTX\t\t0001000\t/* save swapped text even after use */\n#define\tS_IREAD\t\t0000400\t/* read permission, owner */\n#define\tS_IWRITE \t0000200\t/* write permission, owner */\n#define\tS_IEXEC\t\t0000100\t/* execute/search permission, owner */\n#define\tS_ENFMT \t0002000\t/* enforcement-mode locking */\n\n#define\tS_IFMT\t\t_IFMT\n#define\tS_IFDIR\t\t_IFDIR\n#define\tS_IFCHR\t\t_IFCHR\n#define\tS_IFBLK\t\t_IFBLK\n#define\tS_IFREG\t\t_IFREG\n#define\tS_IFLNK\t\t_IFLNK\n#define\tS_IFSOCK\t_IFSOCK\n#define\tS_IFIFO\t\t_IFIFO\n\n#define\tS_IRWXU \t(S_IRUSR | S_IWUSR | S_IXUSR)\n#define\t\tS_IRUSR\t0000400\t/* read permission, owner */\n#define\t\tS_IWUSR\t0000200\t/* write permission, owner */\n#define\t\tS_IXUSR 0000100/* execute/search permission, owner */\n#define\tS_IRWXG\t\t(S_IRGRP | S_IWGRP | S_IXGRP)\n#define\t\tS_IRGRP\t0000040\t/* read permission, group */\n#define\t\tS_IWGRP\t0000020\t/* write permission, grougroup */\n#define\t\tS_IXGRP 0000010/* execute/search permission, group */\n#define\tS_IRWXO\t\t(S_IROTH | S_IWOTH | S_IXOTH)\n#define\t\tS_IROTH\t0000004\t/* read permission, other */\n#define\t\tS_IWOTH\t0000002\t/* write permission, other */\n#define\t\tS_IXOTH 0000001/* execute/search permission, other */\n\n#define\tS_ISBLK(m)\t(((m)&_IFMT) == _IFBLK)\n#define\tS_ISCHR(m)\t(((m)&_IFMT) == _IFCHR)\n#define\tS_ISDIR(m)\t(((m)&_IFMT) == _IFDIR)\n#define\tS_ISFIFO(m)\t(((m)&_IFMT) == _IFIFO)\n#define\tS_ISREG(m)\t(((m)&_IFMT) == _IFREG)\n#define\tS_ISLNK(m)\t(((m)&_IFMT) == _IFLNK)\n#define\tS_ISSOCK(m)\t(((m)&_IFMT) == _IFSOCK)\n\nextern int stat(const char *file, struct stat *st);\n  int stat(const char*, struct stat*) __asm__(\"__statns\");\nextern int lstat(const char *path, struct stat *st);\n  int lstat(const char *, struct stat*) __asm__(\"__lstatns\");\nextern int fstat(int fd, struct stat *st);\n  int fstat(int fd, struct stat*) __asm__(\"__fstatns\");\nextern int mkdir(const char *pathname, mode_t mode);\nextern mode_t umask(mode_t mask);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/sysfunc.h",
    "content": "#pragma once\n/**\n * The sysfunc interface is deprecated. Anything still using these\n * should be migrated to real system calls. The sysfunc interface\n * exists because it was annoying to add new syscall bindings to\n * newlib, but we're not using newlib anymore, so adding new system\n * calls should be easy.\n */\n\n#include <_cheader.h>\n\n/* Privileged */\n#define TOARU_SYS_FUNC_SYNC          3\n#define TOARU_SYS_FUNC_LOGHERE       4\n#define TOARU_SYS_FUNC_KDEBUG        7\n#define TOARU_SYS_FUNC_INSMOD        8\n\n/* Unpriviliged */\n#define TOARU_SYS_FUNC_SETHEAP       9\n#define TOARU_SYS_FUNC_MMAP         10\n#define TOARU_SYS_FUNC_THREADNAME   11\n#define TOARU_SYS_FUNC_SETVGACURSOR 13\n#define TOARU_SYS_FUNC_SETGSBASE    14\n#define TOARU_SYS_FUNC_NPROC        15\n\n/* Experimental */\n#define TOARU_SYS_FUNC_CLEARICACHE  42\n#define TOARU_SYS_FUNC_MUNMAP       43\n\n_Begin_C_Header\nextern int sysfunc(int command, char ** args);\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/sys/termios.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n#include <sys/types.h>\n\n_Begin_C_Header\n\n/* Technically part of ioctl */\nstruct winsize {\n\tunsigned short ws_row;\n\tunsigned short ws_col;\n\tunsigned short ws_xpixel;\n\tunsigned short ws_ypixel;\n};\n\ntypedef unsigned int tcflag_t;\ntypedef unsigned int speed_t;\ntypedef unsigned char cc_t;\n\n/* reserving 0 for particular reason */\n#define VEOF     1 /* ^D (end of file) */\n#define VEOL     2 /* NULL (end of line) */\n#define VERASE   3 /* ^H (backspace/del) */\n#define VINTR    4 /* ^C (interrupt) */\n#define VKILL    5 /* ^U (erase input buffer) */\n#define VMIN     6 /* minimum number of characters for non-canonical read */\n#define VQUIT    7 /* ^\\ send SIGQUIT */\n#define VSTART   8 /* ^Q restart STOPped input */\n#define VSTOP    9 /* ^S stop input */\n#define VSUSP   10 /* ^Z suspend foreground applicatioan (send SIGTSTP) */\n#define VTIME   11 /* Timeout for non-canonical read, deciseconds */\n#define VLNEXT  12 /* ^V literal next */\n#define VWERASE 13 /* ^W erase word */\n\n/* flags for input modes */\n#define BRKINT  0000001\n#define ICRNL   0000002\n#define IGNBRK  0000004\n#define IGNCR   0000010\n#define IGNPAR  0000020\n#define INLCR   0000040\n#define INPCK   0000100\n#define ISTRIP  0000200\n#define IUCLC   0000400\n#define IXANY   0001000\n#define IXOFF   0002000\n#define IXON    0004000\n#define PARMRK  0010000\n\n/* flags for output modes */\n#define OPOST   0000001\n#define OLCUC   0000002\n#define ONLCR   0000004\n#define OCRNL   0000010\n#define ONOCR   0000020\n#define ONLRET  0000040\n#define OFILL   0000100\n#define OFDEL   0000200\n#define NLDLY   0000400\n#define   NL0   0000000\n#define   NL1   0000400\n#define CRDLY   0003000\n#define   CR0   0000000\n#define   CR1   0001000\n#define   CR2   0002000\n#define   CR3   0003000\n#define TABDLY  0014000\n#define   TAB0  0000000\n#define   TAB1  0004000\n#define   TAB2  0010000\n#define   TAB3  0014000\n#define BSDLY   0020000\n#define   BS0   0000000\n#define   BS1   0020000\n#define FFDLY   0100000\n#define   FF0   0000000\n#define   FF1   0100000\n#define VTDLY   0040000\n#define   VT0   0000000\n#define   VT1   0040000\n\n/* baud rates */\n#define CBAUD   0100017\n#define B0      0000000\n#define B50     0000001\n#define B75     0000002\n#define B110    0000003\n#define B134    0000004\n#define B150    0000005\n#define B200    0000006\n#define B300    0000007\n#define B600    0000010\n#define B1200   0000011\n#define B1800   0000012\n#define B2400   0000013\n#define B4800   0000014\n#define B9600   0000015\n#define B19200  0000016\n#define B38400  0000017\n#define B57600  0100000\n#define B115200 0100001\n#define B230400 0100002\n#define B460800 0100003\n#define B921600 0100004\n\n/* control modes */\n#define CSIZE   0000060\n#define   CS5   0000000\n#define   CS6   0000020\n#define   CS7   0000040\n#define   CS8   0000060\n#define CSTOPB  0000100\n#define CREAD   0000200\n#define PARENB  0000400\n#define PARODD  0001000\n#define HUPCL   0002000\n#define CLOCAL  0004000\n\n/* local modes */\n#define ISIG    0000001\n#define ICANON  0000002\n#define XCASE   0000004\n#define ECHO    0000010\n#define ECHOE   0000020\n#define ECHOK   0000040\n#define ECHONL  0000100\n#define NOFLSH  0000200\n#define TOSTOP  0000400\n#define IEXTEN  0001000\n#define ECHOCTL 0002000\n\n/* attributes */\n#define TCSANOW   0x0001\n#define TCSADRAIN 0x0002\n#define TCSAFLUSH 0x0004\n\n#define TCIFLUSH  0x0001\n#define TCIOFLUSH 0x0003\n#define TCOFLUSH  0x0002\n\n#define TCIOFF    0x0001\n#define TCION     0x0002\n#define TCOOFF    0x0004\n#define TCOON     0x0008\n\n#define NCCS 32\n\nstruct termios {\n\ttcflag_t c_iflag;\n\ttcflag_t c_oflag;\n\ttcflag_t c_cflag;\n\ttcflag_t c_lflag;\n\tcc_t     c_cc[NCCS];\n};\n\n/* ioctl commands */\n\n#define TCGETS   0x4000 /* Get termios struct */\n#define TCSETS   0x4001 /* Set termios struct */\n#define TCSETSW  0x4002 /* Set, but let drain first */\n#define TCSETSF  0x4003 /* Set, but let flush first */\n\n#define TCGETA   TCGETS\n#define TCSETA   TCSETS\n#define TCGETAW  TCGETSW\n#define TCGETAF  TCGETSF\n\n#define TCSBRK   0x4004\n#define TCXONC   0x4005\n#define TCFLSH   0x4006\n\n#define TIOCEXCL     0x4007\n#define TIOCNXCL     0x4008\n#define TIOCSCTTY    0x4009\n#define TIOCGPGRP    0x400A\n#define TIOCSPGRP    0x400B\n#define TIOCOUTQ     0x400C\n#define TIOCSTI      0x400D\n#define TIOCGWINSZ   0x400E\n#define TIOCSWINSZ   0x400F\n#define TIOCMGET     0x4010\n#define TIOCMBIS     0x4011\n#define TIOCMBIC     0x4012\n#define TIOCMSET     0x4013\n#define TIOCGSOFTCAR 0x4014\n#define TIOCSSOFTCAR 0x4015\n\n/* termios functions */\n#ifndef _KERNEL_\nextern speed_t cfgetispeed(const struct termios *);\nextern speed_t cfgetospeed(const struct termios *);\nextern int     cfsetispeed(struct termios *, speed_t);\nextern int     cfsetospeed(struct termios *, speed_t);\nextern int     tcdrain(int);\nextern int     tcflow(int, int);\nextern int     tcflush(int, int);\nextern int     tcgetattr(int, struct termios *);\nextern pid_t   tcgetsid(int);\nextern int     tcsendbreak(int, int);\nextern int     tcsetattr(int, int, struct termios *);\nextern int     ioctl(int, unsigned long, void*);\n#endif /* ndef _KERNEL_ */\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/time.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nstruct timeval {\n\ttime_t      tv_sec;     /* seconds */\n\tsuseconds_t tv_usec;    /* microseconds */\n};\n\nstruct timezone {\n\tint tz_minuteswest;     /* minutes west of Greenwich */\n\tint tz_dsttime;         /* type of DST correction */\n};\n\nextern int gettimeofday(struct timeval *p, void *z);\nextern int settimeofday(struct timeval *p, void *z);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/times.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nstruct tms {\n\tclock_t tms_utime;\n\tclock_t tms_stime;\n\tclock_t tms_cutime;\n\tclock_t tms_cstime;\n};\n\nextern clock_t times(struct tms *buf);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/types.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <limits.h>\n\n_Begin_C_Header\n\ntypedef int gid_t;\ntypedef int uid_t;\ntypedef unsigned long dev_t;\ntypedef unsigned long ino_t;\ntypedef int mode_t;\ntypedef int caddr_t;\n\ntypedef unsigned short nlink_t;\n\ntypedef unsigned long blksize_t;\ntypedef unsigned long blkcnt_t;\n\ntypedef long off_t;\ntypedef long time_t;\ntypedef long clock_t;\n\n#define __need_size_t\n#include <stddef.h>\n\ntypedef long ssize_t;\n\ntypedef unsigned long useconds_t;\ntypedef long suseconds_t;\ntypedef int pid_t;\n\n#define FD_SETSIZE 64 /* compatibility with newlib */\ntypedef unsigned int fd_mask;\ntypedef struct _fd_set {\n    fd_mask fds_bits[1]; /* should be 64 bits */\n} fd_set;\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/uregs.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\n#if defined(__x86_64__)\n# include <kernel/arch/x86_64/regs.h>\n# define uregs_syscall_result(r) ((r)->rax)\n# define uregs_syscall_num(r)    ((r)->rax)\n# define uregs_syscall_arg1(r)   ((r)->rdi)\n# define uregs_syscall_arg2(r)   ((r)->rsi)\n# define uregs_syscall_arg3(r)   ((r)->rdx)\n# define uregs_syscall_arg4(r)   ((r)->r10)\n# define uregs_syscall_arg5(r)   ((r)->r8)\n# define uregs_ip(r)             ((r)->rip)\n# define uregs_bp(r)             ((r)->rbp)\n# define UREGS_FMT \\\n\t\t\"  $rip=0x%016lx\\n\" \\\n\t\t\"  $rsi=0x%016lx,$rdi=0x%016lx,$rbp=0x%016lx,$rsp=0x%016lx\\n\" \\\n\t\t\"  $rax=0x%016lx,$rbx=0x%016lx,$rcx=0x%016lx,$rdx=0x%016lx\\n\" \\\n\t\t\"  $r8= 0x%016lx,$r9= 0x%016lx,$r10=0x%016lx,$r11=0x%016lx\\n\" \\\n\t\t\"  $r12=0x%016lx,$r13=0x%016lx,$r14=0x%016lx,$r15=0x%016lx\\n\" \\\n\t\t\"  cs=0x%016lx  ss=0x%016lx rflags=0x%016lx int=0x%02lx err=0x%02lx\\n\"\n# define UREGS_ARGS(r) \\\n\t\t(r)->rip, \\\n\t\t(r)->rsi, (r)->rdi, (r)->rbp, (r)->rsp, \\\n\t\t(r)->rax, (r)->rbx, (r)->rcx, (r)->rdx, \\\n\t\t(r)->r8,  (r)->r9,  (r)->r10, (r)->r11, \\\n\t\t(r)->r12, (r)->r13, (r)->r14, (r)->r15, \\\n\t\t(r)->cs,  (r)->ss,  (r)->rflags, (r)->int_no, (r)->err_code\n#elif defined(__aarch64__)\n# include <kernel/arch/aarch64/regs.h>\n# define uregs_syscall_result(r) ((r)->x0)\n# define uregs_syscall_num(r)    ((r)->x0)\n# define uregs_syscall_arg1(r)   ((r)->x1)\n# define uregs_syscall_arg2(r)   ((r)->x2)\n# define uregs_syscall_arg3(r)   ((r)->x3)\n# define uregs_syscall_arg4(r)   ((r)->x4)\n# define uregs_syscall_arg5(r)   ((r)->x5)\n# define uregs_ip(r)             ((r)->elr)\n# define uregs_bp(r)             ((r)->x29)\n# define UREGS_FMT \\\n\t\t\" $x00=0x%016lx,$x01=0x%016lx,$x02=0x%016lx,$x03=0x%016lx\\n\" \\\n\t\t\" $x04=0x%016lx,$x05=0x%016lx,$x06=0x%016lx,$x07=0x%016lx\\n\" \\\n\t\t\" $x08=0x%016lx,$x09=0x%016lx,$x10=0x%016lx,$x11=0x%016lx\\n\" \\\n\t\t\" $x12=0x%016lx,$x13=0x%016lx,$x14=0x%016lx,$x15=0x%016lx\\n\" \\\n\t\t\" $x16=0x%016lx,$x17=0x%016lx,$x18=0x%016lx,$x19=0x%016lx\\n\" \\\n\t\t\" $x20=0x%016lx,$x21=0x%016lx,$x22=0x%016lx,$x23=0x%016lx\\n\" \\\n\t\t\" $x24=0x%016lx,$x25=0x%016lx,$x26=0x%016lx,$x27=0x%016lx\\n\" \\\n\t\t\" $x28=0x%016lx,$x29=0x%016lx,$x30=0x%016lx\\n\" \\\n\t\t\" sp=0x%016lx    elr=0x%016lx\\n\"\n# define UREGS_ARGS(r) \\\n\t\t(r)->x0, (r)->x1, (r)->x2, (r)->x3, (r)->x4, (r)->x5, (r)->x6, (r)->x7, \\\n\t\t(r)->x8, (r)->x9, (r)->x10, (r)->x11, (r)->x12, (r)->x13, (r)->x14, (r)->x15, \\\n\t\t(r)->x16, (r)->x17, (r)->x18, (r)->x19, (r)->x20, (r)->x21, (r)->x22, (r)->x23, \\\n\t\t(r)->x24, (r)->x25, (r)->x26, (r)->x27, (r)->x28, (r)->x29, (r)->x30, \\\n\t\t(r)->user_sp, (r)->elr\n#else\n# error Unsupported architecture\n#endif\n\nstruct URegs {\n\tstruct regs;\n#if defined(__aarch64__)\n\tuint64_t elr;\n#endif\n};\n\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/utsname.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n#define _UTSNAME_LENGTH 256\n\nstruct utsname {\n\tchar  sysname[_UTSNAME_LENGTH];\n\tchar nodename[_UTSNAME_LENGTH];\n\tchar  release[_UTSNAME_LENGTH];\n\tchar  version[_UTSNAME_LENGTH];\n\tchar  machine[_UTSNAME_LENGTH];\n\tchar domainname[_UTSNAME_LENGTH];\n};\n\n\n#ifndef _KERNEL_\nextern int uname(struct utsname *__name);\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/sys/wait.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\n#define WNOHANG   0x0001\n#define WUNTRACED 0x0002\n#define WSTOPPED  0x0004\n#define WNOKERN   0x0010\n\n/* This were taken from newlib, but they remain true */\n#define WIFEXITED(w)    (((w) & 0xff) == 0)\n#define WIFSIGNALED(w)  (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f))\n#define WIFSTOPPED(w)   (((w) & 0xff) == 0x7f)\n#define WEXITSTATUS(w)  (((w) >> 8) & 0xff)\n#define WTERMSIG(w) ((w) & 0x7f)\n#define WSTOPSIG    WEXITSTATUS\n\n\n#ifndef _KERNEL_\nextern pid_t wait(int*);\nextern pid_t waitpid(pid_t, int *, int);\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/syscall.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <stddef.h>\n\n_Begin_C_Header\n\n#define DECL_SYSCALL0(fn)                long syscall_##fn()\n#define DECL_SYSCALL1(fn,p1)             long syscall_##fn(p1)\n#define DECL_SYSCALL2(fn,p1,p2)          long syscall_##fn(p1,p2)\n#define DECL_SYSCALL3(fn,p1,p2,p3)       long syscall_##fn(p1,p2,p3)\n#define DECL_SYSCALL4(fn,p1,p2,p3,p4)    long syscall_##fn(p1,p2,p3,p4)\n#define DECL_SYSCALL5(fn,p1,p2,p3,p4,p5) long syscall_##fn(p1,p2,p3,p4,p5)\n\n#ifdef __x86_64__\n\n#ifdef __SYSCALL_INT7F\n#  define __SYSCALL_ENTRY_INST \"int $0x7F\"\n#  define __SYSCALL_CLOBBERS   \"memory\"\n#else\n#  define __SYSCALL_ENTRY_INST \"syscall\"\n#  define __SYSCALL_CLOBBERS   \"rcx\", \"r11\", \"memory\"\n#endif\n\n#define DEFN_SYSCALL0(fn, num) \\\n\tlong syscall_##fn() { \\\n\t\tlong a = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST : \"=a\" (a) : \"a\" ((long)a) : __SYSCALL_CLOBBERS); \\\n\t\treturn a; \\\n\t}\n\n#define DEFN_SYSCALL1(fn, num, P1) \\\n\tlong syscall_##fn(P1 p1) { \\\n\t\tlong __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \\\n\t\t\t\t: \"=a\" (__res) \\\n\t\t\t\t: \"a\" (__res), \"D\" ((long)(p1)) : __SYSCALL_CLOBBERS ); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL2(fn, num, P1, P2) \\\n\tlong syscall_##fn(P1 p1, P2 p2) { \\\n\t\tlong __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \\\n\t\t\t\t: \"=a\" (__res) \\\n\t\t\t\t: \"a\" (__res), \"D\" ((long)(p1)), \"S\"((long)(p2)) : __SYSCALL_CLOBBERS ); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL3(fn, num, P1, P2, P3) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3) { \\\n\t\tlong __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \\\n\t\t\t\t: \"=a\" (__res) \\\n\t\t\t\t: \"a\" (__res), \"D\" ((long)(p1)), \"S\"((long)(p2)), \"d\"((long)(p3)) : __SYSCALL_CLOBBERS ); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL4(fn, num, P1, P2, P3, P4) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4) { \\\n\t\tregister long p4_ __asm__(\"r10\") = (long)p4; \\\n\t\tlong __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \\\n\t\t\t\t: \"=a\" (__res) \\\n\t\t\t\t: \"a\" (__res), \"D\" ((long)(p1)), \"S\"((long)(p2)), \"d\"((long)(p3)), \"r\"((long)(p4_)) : __SYSCALL_CLOBBERS ); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL5(fn, num, P1, P2, P3, P4, P5) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { \\\n\t\tregister long p4_ __asm__(\"r10\") = (long)p4; \\\n\t\tregister long p5_ __asm__(\"r8\") = (long)p5; \\\n\t\tlong __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \\\n\t\t\t\t: \"=a\" (__res) \\\n\t\t\t\t: \"a\" (__res), \"D\" ((long)(p1)), \"S\"((long)(p2)), \"d\"((long)(p3)), \"r\"((long)(p4_)), \"r\"((long)(p5_)) : __SYSCALL_CLOBBERS ); \\\n\t\treturn __res; \\\n\t}\n#elif defined(__aarch64__)\n\n#define DEFN_SYSCALL0(fn, num) \\\n\tlong syscall_##fn() { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL1(fn, num, P1) \\\n\tlong syscall_##fn(P1 p1) { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\tregister long x1 __asm__(\"x1\") = (long)p1; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res), \\\n\t\t\t\"r\" (x1) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL2(fn, num, P1, P2) \\\n\tlong syscall_##fn(P1 p1, P2 p2) { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\tregister long x1 __asm__(\"x1\") = (long)p1; \\\n\t\tregister long x2 __asm__(\"x2\") = (long)p2; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res), \\\n\t\t\t\"r\" (x1), \\\n\t\t\t\"r\" (x2) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL3(fn, num, P1, P2, P3) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3) { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\tregister long x1 __asm__(\"x1\") = (long)p1; \\\n\t\tregister long x2 __asm__(\"x2\") = (long)p2; \\\n\t\tregister long x3 __asm__(\"x3\") = (long)p3; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res), \\\n\t\t\t\"r\" (x1), \\\n\t\t\t\"r\" (x2), \\\n\t\t\t\"r\" (x3) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL4(fn, num, P1, P2, P3, P4) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4) { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\tregister long x1 __asm__(\"x1\") = (long)p1; \\\n\t\tregister long x2 __asm__(\"x2\") = (long)p2; \\\n\t\tregister long x3 __asm__(\"x3\") = (long)p3; \\\n\t\tregister long x4 __asm__(\"x4\") = (long)p4; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res), \\\n\t\t\t\"r\" (x1), \\\n\t\t\t\"r\" (x2), \\\n\t\t\t\"r\" (x3), \\\n\t\t\t\"r\" (x4) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#define DEFN_SYSCALL5(fn, num, P1, P2, P3, P4, P5) \\\n\tlong syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { \\\n\t\tregister long __res __asm__ (\"x0\") = num; \\\n\t\tregister long x1 __asm__(\"x1\") = (long)p1; \\\n\t\tregister long x2 __asm__(\"x2\") = (long)p2; \\\n\t\tregister long x3 __asm__(\"x3\") = (long)p3; \\\n\t\tregister long x4 __asm__(\"x4\") = (long)p4; \\\n\t\tregister long x5 __asm__(\"x5\") = (long)p5; \\\n\t\t__asm__ __volatile__(\"svc 0\" : \"=r\" (__res) : \\\n\t\t\t\"r\" (__res), \\\n\t\t\t\"r\" (x1), \\\n\t\t\t\"r\" (x2), \\\n\t\t\t\"r\" (x3), \\\n\t\t\t\"r\" (x4), \\\n\t\t\t\"r\" (x5) \\\n\t\t); \\\n\t\treturn __res; \\\n\t}\n\n#else\n# error \"Invalid target, no system call linkage.\"\n#endif\n\n\nDECL_SYSCALL1(exit, int);\nDECL_SYSCALL0(geteuid);\nDECL_SYSCALL3(open, const char *, int, int);\nDECL_SYSCALL3(read, int, char *, size_t);\nDECL_SYSCALL3(write, int, char *, size_t);\nDECL_SYSCALL1(close, int);\nDECL_SYSCALL2(gettimeofday, void *, void *);\nDECL_SYSCALL3(execve, char *, char **, char **);\nDECL_SYSCALL0(fork);\nDECL_SYSCALL0(getpid);\nDECL_SYSCALL1(sbrk, int);\nDECL_SYSCALL3(socket, int, int, int);\nDECL_SYSCALL1(uname, void *);\nDECL_SYSCALL5(openpty, int *, int *, char *, void *, void *);\nDECL_SYSCALL3(seek, int, long, int);\nDECL_SYSCALL2(stat, int, void *);\nDECL_SYSCALL5(setsockopt,int,int,int,const void*,size_t);\nDECL_SYSCALL3(bind,int,const void*,size_t);\nDECL_SYSCALL4(accept,int,void*,size_t*,int);\nDECL_SYSCALL2(listen,int,int);\nDECL_SYSCALL3(connect,int,const void*,size_t);\nDECL_SYSCALL2(dup2, int, int);\nDECL_SYSCALL0(getuid);\nDECL_SYSCALL1(setuid, unsigned int);\nDECL_SYSCALL5(getsockopt,int,int,int,void*,size_t*);\nDECL_SYSCALL0(reboot);\nDECL_SYSCALL3(readdir, int, int, void *);\nDECL_SYSCALL1(chdir, char *);\nDECL_SYSCALL2(getcwd, char *, size_t);\nDECL_SYSCALL3(clone, uintptr_t, uintptr_t, void *);\nDECL_SYSCALL1(sethostname, char *);\nDECL_SYSCALL1(gethostname, char *);\nDECL_SYSCALL2(mkdir, char *, unsigned int);\nDECL_SYSCALL2(shm_obtain, const char *, size_t *);\nDECL_SYSCALL1(shm_release, const char *);\nDECL_SYSCALL2(kill, int, int);\nDECL_SYSCALL2(signal, int, void *);\nDECL_SYSCALL3(recv,int,void*,int);\nDECL_SYSCALL3(send,int,const void*,int);\nDECL_SYSCALL0(gettid);\nDECL_SYSCALL0(yield);\nDECL_SYSCALL2(sysfunc, int, char **);\nDECL_SYSCALL2(shutdown, int, int);\nDECL_SYSCALL2(sleepabs, unsigned long, unsigned long);\nDECL_SYSCALL2(sleep, unsigned long, unsigned long);\nDECL_SYSCALL3(ioctl, int, unsigned long, void *);\nDECL_SYSCALL2(access, char *, int);\nDECL_SYSCALL2(statf, char *, void *);\nDECL_SYSCALL2(chmod, char *, int);\nDECL_SYSCALL1(umask, int);\nDECL_SYSCALL1(unlink, char *);\nDECL_SYSCALL3(waitpid, int, int *, int);\nDECL_SYSCALL1(pipe,  int *);\nDECL_SYSCALL5(mount, char *, char *, char *, unsigned long, void *);\nDECL_SYSCALL2(symlink, const char *, const char *);\nDECL_SYSCALL3(readlink, char *, char *, int);\nDECL_SYSCALL2(lstat, char *, void *);\nDECL_SYSCALL2(fswait,int,int*);\nDECL_SYSCALL3(fswait2,int,int*,int);\nDECL_SYSCALL3(chown,char*,int,int);\nDECL_SYSCALL0(setsid);\nDECL_SYSCALL2(setpgid,int,int);\nDECL_SYSCALL1(getpgid,int);\nDECL_SYSCALL4(fswait3, int, int*, int, int*);\nDECL_SYSCALL0(getgid);\nDECL_SYSCALL0(getegid);\nDECL_SYSCALL1(setgid, unsigned int);\nDECL_SYSCALL2(getgroups, int, int*);\nDECL_SYSCALL2(setgroups, int, const int*);\nDECL_SYSCALL1(times, struct tms*);\nDECL_SYSCALL4(ptrace, int, int, void*, void*);\nDECL_SYSCALL2(settimeofday, void *, void *);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/syscall_nums.h",
    "content": "#pragma once\n\n#define SYS_EXT 0\n#define SYS_GETEUID 1\n#define SYS_OPEN 2\n#define SYS_READ 3\n#define SYS_WRITE 4\n#define SYS_CLOSE 5\n#define SYS_GETTIMEOFDAY 6\n#define SYS_EXECVE 7\n#define SYS_FORK 8\n#define SYS_GETPID 9\n#define SYS_SBRK 10\n#define SYS_SOCKET 11\n#define SYS_UNAME 12\n#define SYS_OPENPTY 13\n#define SYS_SEEK 14\n#define SYS_STAT 15\n#define SYS_SETSOCKOPT 16\n#define SYS_BIND   17\n#define SYS_ACCEPT 18\n#define SYS_LISTEN 19\n#define SYS_CONNECT 20\n/* 21 - unused, was mkpipe */\n#define SYS_DUP2 22\n#define SYS_GETUID 23\n#define SYS_SETUID 24\n#define SYS_GETSOCKOPT 25\n#define SYS_REBOOT 26\n#define SYS_READDIR 27\n#define SYS_CHDIR 28\n#define SYS_GETCWD 29\n#define SYS_CLONE 30\n#define SYS_SETHOSTNAME 31\n#define SYS_GETHOSTNAME 32\n#define SYS_PTRACE 33\n#define SYS_MKDIR 34\n#define SYS_SHM_OBTAIN 35\n#define SYS_SHM_RELEASE 36\n#define SYS_KILL 37\n#define SYS_SIGNAL 38\n#define SYS_RECV 39\n#define SYS_SEND 40\n#define SYS_GETTID 41\n#define SYS_YIELD 42\n#define SYS_SYSFUNC 43\n#define SYS_SHUTDOWN 44\n#define SYS_SLEEPABS 45\n#define SYS_SLEEP 46\n#define SYS_IOCTL 47\n#define SYS_ACCESS 48\n#define SYS_STATF 49\n#define SYS_CHMOD 50\n#define SYS_UMASK 51\n#define SYS_UNLINK 52\n#define SYS_WAITPID 53\n#define SYS_PIPE 54\n#define SYS_MOUNT 55\n#define SYS_SYMLINK 56\n#define SYS_READLINK 57\n#define SYS_LSTAT 58\n#define SYS_FSWAIT 59\n#define SYS_FSWAIT2 60\n#define SYS_CHOWN 61\n#define SYS_SETSID 62\n#define SYS_SETPGID 63\n#define SYS_GETPGID 64\n#define SYS_FSWAIT3 65\n#define SYS_GETGID 66\n#define SYS_GETEGID 67\n#define SYS_SETGID 68\n#define SYS_GETGROUPS 69\n#define SYS_SETGROUPS 70\n#define SYS_TIMES 71\n#define SYS_SETTIMEOFDAY 72\n#define SYS_SIGACTION 73\n#define SYS_SIGPENDING 74\n#define SYS_SIGPROCMASK 75\n#define SYS_SIGSUSPEND 76\n#define SYS_SIGWAIT 77\n#define SYS_GETSOCKNAME 78\n#define SYS_GETPEERNAME 79\n#define SYS_PREAD 80\n#define SYS_PWRITE 81\n#define SYS_RENAME 82\n#define SYS_FCNTL 83\n#define SYS_FCHMOD 84\n#define SYS_FCHOWN 85\n#define SYS_TRUNCATE 86\n#define SYS_FTRUNCATE 87\n"
  },
  {
    "path": "base/usr/include/termio.h",
    "content": "#pragma once\n#include <termios.h>\n#include <sys/ioctl.h>\n"
  },
  {
    "path": "base/usr/include/termios.h",
    "content": "#pragma once\n\n#include <sys/termios.h>\n"
  },
  {
    "path": "base/usr/include/time.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nstruct tm {\n    int tm_sec;    /* Seconds (0-60) */\n    int tm_min;    /* Minutes (0-59) */\n    int tm_hour;   /* Hours (0-23) */\n    int tm_mday;   /* Day of the month (1-31) */\n    int tm_mon;    /* Month (0-11) */\n    int tm_year;   /* Year - 1900 */\n    int tm_wday;   /* Day of the week (0-6, Sunday = 0) */\n    int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */\n    int tm_isdst;  /* Daylight saving time */\n\n    const char * _tm_zone_name;\n    int _tm_zone_offset;\n};\n\nextern struct tm *localtime(const time_t *timep);\nextern struct tm *gmtime(const time_t *timep);\n\nextern struct tm *localtime_r(const time_t *timep, struct tm * buf);\nextern struct tm *gmtime_r(const time_t *timep, struct tm * buf);\n\nextern size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);\nextern time_t time(time_t * out);\nextern double difftime(time_t a, time_t b);\nextern time_t mktime(struct tm *tm);\n\nextern char * asctime(const struct tm *tm);\nextern char * ctime(const time_t * timep);\n\nextern clock_t clock(void);\n#define CLOCKS_PER_SEC 1000000\n\n#include <bits/timespec.h>\n\ntypedef int clockid_t;\n\n#define CLOCK_REALTIME  0\n#define CLOCK_MONOTONIC 1\n\nextern int clock_gettime(clockid_t clk_id, struct timespec *tp);\nextern int clock_getres(clockid_t clk_id, struct timespec *res);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/auth.h",
    "content": "/**\n * @brief Authentication Helpers\n *\n * This library allows multiple login programs (login, sudo, glogin)\n * to share authentication code by providing a single palce to check\n * passwords against /etc/master.passwd and to set typical login vars.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n\n#pragma once\n\n#include <_cheader.h>\n#include <unistd.h>\n\n_Begin_C_Header\n\n/**\n * toaru_auth_check_pass\n *\n * Returns the uid for the request user on success, -1 on failure.\n */\nextern int toaru_auth_check_pass(char * user, char * pass);\n\n/**\n * toaru_auth_set_vars\n *\n * Sets various environment variables (HOME, USER, SHELL, etc.)\n * for the current user.\n */\nextern void toaru_auth_set_vars(void);\n\n/**\n * Set supplementary groups from /etc/groups\n */\nextern void toaru_auth_set_groups(uid_t uid);\n\n/**\n * Do the above two steps, and setuid, and setgid...\n */\nextern void toaru_set_credentials(uid_t uid);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/button.h",
    "content": "/**\n * @brief Draws buttons.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n\n/* This definition will eventually change with the rest of the widget toolkit */\nstruct TTKButton {\n\tint x;\n\tint y;\n\tint width;\n\tint height;\n\tchar * title;\n\tint hilight;\n};\n\nextern void ttk_button_draw(gfx_context_t * ctx, struct TTKButton * button);\n"
  },
  {
    "path": "base/usr/include/toaru/confreader.h",
    "content": "/**\n * @brief Configuration File Reader\n *\n * Reads an implementation of the INI \"standard\". Note that INI\n * isn't actually a standard. We support the following:\n * - ; comments\n * - foo=bar keyword assignment\n * - [sections]\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n\n#pragma once\n\n#include <_cheader.h>\n#include <toaru/hashmap.h>\n\n_Begin_C_Header\n\n/**\n * A configuration file is represented as a hashmap of sections,\n * which are themselves hashmaps. You may modify these hashmaps\n * to change key values, or add new sections.\n */\ntypedef struct {\n\thashmap_t * sections;\n} confreader_t;\n\n/**\n * confreader_load\n *\n * Open a configuration file and read its contents.\n * Returns NULL if the requested file failed to open.\n */\nextern confreader_t * confreader_load(const char * file);\n\n/**\n * confreader_get\n *\n * Retrieve a string value from the config file.\n * An empty string for `section` represents the default section.\n * If the value is not found, NULL is returned.\n */\nextern char * confreader_get(confreader_t * ctx, char * section, char * value);\n\n/**\n * confreader_getd\n *\n * Retrieve a string value from the config file, falling back\n * to a default value if the requested key is not found.\n */\nextern char * confreader_getd(confreader_t * ctx, char * section, char * value, char * def);\n\n/**\n * confreader_int\n *\n * Retrieve an integer value from the config file.\n *\n * This is a convenience wrapper that calls atoi().\n * If the value is not found, 0 is returned.\n */\nextern int confreader_int(confreader_t * ctx, char * section, char * value);\n\n/**\n * confreader_intd\n *\n * Retrieve an integer value from the config file, falling back\n * to a default if the requested key is not found.\n */\nextern int confreader_intd(confreader_t * ctx, char * section, char * value, int def);\n\n/**\n * confreader_free\n *\n * Free the memory associated with a config file.\n */\nextern void confreader_free(confreader_t * conf);\n\n/**\n * confreader_write\n *\n * Write a config file back out to a file.\n */\nextern int confreader_write(confreader_t * config, const char * file);\n\n/**\n * confreader_create_empty\n *\n * Create an empty configuration file to be modified directly\n * through hashmap values.\n */\nextern confreader_t * confreader_create_empty(void);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/decodeutf8.h",
    "content": "/**\n * @brief Decode UTF8 to codepoints.\n *\n * This is a simple implementation of a UTF-8 decoder with an\n * equivalent API to the older third-party (and much cooler...)\n * version that ToaruOS used to use. Keep feeding it bytes and\n * will eventually set *codep to a codepoint. Should also be able\n * to detect bad UTF-8.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdint.h>\n\n#define UTF8_ACCEPT 0\n#define UTF8_REJECT 1\n\n/**\n * Conceptually similar to its predecessor, this implementation is much\n * less cool, as it uses three separate state tables and more shifts.\n */\nstatic inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {\n\tstatic int state_table[32] = {\n\t\t0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */\n\t\t1,1,1,1,1,1,1,1,                 /* 10xxxxxx */\n\t\t2,2,2,2,                         /* 110xxxxx */\n\t\t3,3,                             /* 1110xxxx */\n\t\t4,                               /* 11110xxx */\n\t\t1                                /* 11111xxx */\n\t};\n\n\tstatic int mask_bytes[32] = {\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n\t\t0x1F,0x1F,0x1F,0x1F,\n\t\t0x0F,0x0F,\n\t\t0x07,\n\t\t0x00\n\t};\n\n\tstatic int next[5] = {\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t2,\n\t\t3\n\t};\n\n\tif (*state == UTF8_ACCEPT) {\n\t\t*codep = byte & mask_bytes[byte >> 3];\n\t\t*state = state_table[byte >> 3];\n\t} else if (*state > 0) {\n\t\t*codep = (byte & 0x3F) | (*codep << 6);\n\t\t*state = next[*state];\n\t}\n\treturn *state;\n}\n"
  },
  {
    "path": "base/usr/include/toaru/decorations.h",
    "content": "/**\n * @brief Client-side Window Decoration library\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n#include <toaru/yutani.h>\n\n_Begin_C_Header\n\n/*\n * Render decorations to a window. A buffer pointer is\n * provided so that you may render in double-buffered mode.\n *\n * Run me at least once for each window, and any time you may need to\n * redraw them.\n */\nextern void render_decorations(yutani_window_t * window, gfx_context_t * ctx, char * title);\n\n/** DEPRECATED */\nextern void render_decorations_inactive(yutani_window_t * window, gfx_context_t * ctx, char * title);\n\n/**\n * Decoration boundaries\n */\nstruct decor_bounds {\n\tint top_height;\n\tint bottom_height;\n\tint left_width;\n\tint right_width;\n\n\t/* Convenience */\n\tint width;\n\tint height;\n};\n\n/*\n * Used by decoration libraries to set callbacks\n */\nextern void (*decor_render_decorations)(yutani_window_t *, gfx_context_t *, char *, int);\nextern int  (*decor_check_button_press)(yutani_window_t *, int x, int y);\nextern int  (*decor_get_bounds)(yutani_window_t *, struct decor_bounds *);\nextern int decor_hover_button;\nextern yutani_window_t * decor_hover_window;\n\n/*\n * Run me once to set things up\n */\nextern void init_decorations();\n\nextern int decor_handle_event(yutani_t * yctx, yutani_msg_t * m);\n\n/* Callbacks for handle_event */\nextern void decor_set_close_callback(void (*callback)(yutani_window_t *));\nextern void decor_set_resize_callback(void (*callback)(yutani_window_t *));\nextern void decor_set_maximize_callback(void (*callback)(yutani_window_t *));\nextern yutani_window_t * decor_show_default_menu(yutani_window_t * window, int y, int x);\n\n/* Responses from handle_event */\n#define DECOR_OTHER     1 /* Clicked on title bar but otherwise unimportant */\n#define DECOR_CLOSE     2 /* Clicked on close button */\n#define DECOR_RESIZE    3 /* Resize button */\n#define DECOR_MAXIMIZE  4\n#define DECOR_RIGHT     5\n#define DECOR_MINIMIZE  6\n#define DECOR_REDRAW    7\n\n#define DECOR_ACTIVE   0\n#define DECOR_INACTIVE 1\n\n#define DECOR_FLAG_DECORATED   (1 << 0)\n#define DECOR_FLAG_NO_MAXIMIZE (1 << 1)\n#define DECOR_FLAG_TILED       (0xF << 2)\n#define DECOR_FLAG_TILE_LEFT   (0x1 << 2)\n#define DECOR_FLAG_TILE_RIGHT  (0x2 << 2)\n#define DECOR_FLAG_TILE_UP     (0x4 << 2)\n#define DECOR_FLAG_TILE_DOWN   (0x8 << 2)\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/drawstring.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n\n_Begin_C_Header\n\nvoid draw_string(gfx_context_t * ctx, int x, int y, uint32_t _fg, char * str);\nint draw_string_width(char * str);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/graphics.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <stddef.h>\n\n_Begin_C_Header\n\n#define GFX_W(ctx)  ((ctx)->width)\t\t\t/* Display width */\n#define GFX_H(ctx)  ((ctx)->height)\t\t\t/* Display height */\n#define GFX_B(ctx)  ((ctx)->depth / 8)\t\t/* Display byte depth */\n#define GFX_S(ctx)  ((ctx)->stride)\t\t\t/* Stride */\n\n#define _RED(color) ((color & 0x00FF0000) / 0x10000)\n#define _GRE(color) ((color & 0x0000FF00) / 0x100)\n#define _BLU(color) ((color & 0x000000FF) / 0x1)\n#define _ALP(color) ((color & 0xFF000000) / 0x1000000)\n\n/*\n * Macros make verything easier.\n */\n#define GFX(ctx,x,y) *((uint32_t *)&((ctx)->backbuffer)[(GFX_S(ctx) * (y) + (x) * GFX_B(ctx))])\n#define GFXR(ctx,x,y) *((uint32_t *)&((ctx)->buffer)[(GFX_S(ctx) * (y) + (x) * GFX_B(ctx))])\n#define SPRITE(sprite,x,y) sprite->bitmap[sprite->width * (y) + (x)]\n#define SMASKS(sprite,x,y) sprite->masks[sprite->width * (y) + (x)]\n\ntypedef struct sprite {\n\tuint16_t width;\n\tuint16_t height;\n\tuint32_t * bitmap;\n\tuint32_t * masks;\n\tuint32_t blank;\n\tuint8_t  alpha;\n} sprite_t;\n\ntypedef struct context {\n\tuint16_t width;\n\tuint16_t height;\n\tuint16_t depth;\n\tuint32_t size;\n\tchar *   buffer;\n\tchar *   backbuffer;\n\tchar *   clips;\n\tint32_t  clips_size;\n\tuint32_t stride;\n\n\tuint32_t _true_stride;\n} gfx_context_t;\n\nextern gfx_context_t * init_graphics_fullscreen();\nextern gfx_context_t * init_graphics_fullscreen_double_buffer();\nextern void reinit_graphics_fullscreen(gfx_context_t * ctx);\n\n#define ALPHA_OPAQUE   0\n#define ALPHA_MASK     1\n#define ALPHA_EMBEDDED 2\n#define ALPHA_INDEXED  3\n#define ALPHA_FORCE_SLOW_EMBEDDED 4\n\nextern uint32_t rgb(uint8_t r, uint8_t g, uint8_t b);\nextern uint32_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a);\nextern uint32_t alpha_blend(uint32_t bottom, uint32_t top, uint32_t mask);\nextern uint32_t alpha_blend_rgba(uint32_t bottom, uint32_t top);\nextern uint32_t framebuffer_stride(void);\n\nextern void flip(gfx_context_t * ctx);\nvoid clear_buffer(gfx_context_t * ctx);\n\nextern gfx_context_t * init_graphics_sprite(sprite_t * sprite);\nextern sprite_t * create_sprite(size_t width, size_t height, int alpha);\n\nextern void blur_context(gfx_context_t * _dst, gfx_context_t * _src, double amount);\nextern void blur_context_no_vignette(gfx_context_t * _dst, gfx_context_t * _src, double amount);\nextern void blur_context_box(gfx_context_t * _src, int radius);\nextern void sprite_free(sprite_t * sprite);\n\nextern void draw_line(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color);\nextern void draw_line_thick(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color, char thickness);\nextern void draw_fill(gfx_context_t * ctx, uint32_t color);\n\ntypedef double gfx_matrix_t[2][3];\n\nextern int load_sprite(sprite_t * sprite, const char * filename);\nextern int load_sprite_bmp(sprite_t * sprite, const char * filename);\nextern void draw_sprite(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y);\nextern void draw_sprite_scaled(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height);\nextern void draw_sprite_scaled_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height, float alpha);\nextern void draw_sprite_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha);\nextern void draw_sprite_alpha_paint(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha, uint32_t c);\nextern void draw_sprite_rotate(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float rotation, float alpha);\nextern void draw_sprite_transform(gfx_context_t * ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha);\n\n//extern void context_to_png(FILE * file, gfx_context_t * ctx);\n\nextern uint32_t premultiply(uint32_t color);\n\nextern void gfx_add_clip(gfx_context_t * ctx, int32_t x, int32_t y, int32_t w, int32_t h);\nextern void gfx_clear_clip(gfx_context_t * ctx);\nextern void gfx_no_clip(gfx_context_t * ctx);\n\nextern uint32_t interp_colors(uint32_t bottom, uint32_t top, uint8_t interp);\nextern void draw_rounded_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, int radius, uint32_t color);\nextern void draw_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color);\nextern void draw_rectangle_solid(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color);\nextern void draw_rounded_rectangle_pattern(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, int radius, uint32_t (*pattern)(int32_t x, int32_t y, double alpha, void * extra), void * extra);\n\nstruct gfx_point {\n\tfloat x;\n\tfloat y;\n};\n\nextern float gfx_point_distance(const struct gfx_point * a, const struct gfx_point * b);\nextern float gfx_point_distance_squared(const struct gfx_point * a, const struct gfx_point * b);\nextern float gfx_point_dot(const struct gfx_point * a, const struct gfx_point * b);\nextern struct gfx_point gfx_point_sub(const struct gfx_point * a, const struct gfx_point * b);\nextern struct gfx_point gfx_point_add(const struct gfx_point * a, const struct gfx_point * b);\nextern float gfx_line_distance(const struct gfx_point * p, const struct gfx_point * v, const struct gfx_point * w);\nextern void draw_line_aa_points(gfx_context_t * ctx, struct gfx_point *v, struct gfx_point *w, uint32_t color, float thickness);\nextern void draw_line_aa(gfx_context_t * ctx, int x_1, int x_2, int y_1, int y_2, uint32_t color, float thickness);\n\nstruct gradient_definition {\n\tint height;\n\tint y;\n\tuint32_t top;\n\tuint32_t bottom;\n};\n\nextern uint32_t gfx_vertical_gradient_pattern(int32_t x, int32_t y, double alpha, void * extra);\n\nextern gfx_context_t * init_graphics_subregion(gfx_context_t * base, int x, int y, int width, int height);\n\nextern void gfx_matrix_identity(gfx_matrix_t);\nextern void gfx_matrix_scale(gfx_matrix_t, double x, double y);\nextern void gfx_matrix_translate(gfx_matrix_t, double x, double y);\nextern void gfx_matrix_rotate(gfx_matrix_t, double rotation);\nextern void gfx_matrix_shear(gfx_matrix_t matrix, double x, double y);\nextern int gfx_matrix_invert(gfx_matrix_t m, gfx_matrix_t inverse);\nextern void gfx_apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y);\n\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/hashmap.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n#ifdef _KERNEL_\n#\tinclude <kernel/system.h>\n#else\n#\tinclude <string.h>\n#\tinclude <stddef.h>\n#\tinclude <stdlib.h>\n#endif\n\n#include <toaru/list.h>\n\n_Begin_C_Header\n\ntypedef unsigned int (*hashmap_hash_t) (const void * key);\ntypedef int (*hashmap_comp_t) (const void * a, const void * b);\ntypedef void (*hashmap_free_t) (void *);\ntypedef void * (*hashmap_dupe_t) (const void *);\n\ntypedef struct hashmap_entry {\n\tchar * key;\n\tvoid * value;\n\tstruct hashmap_entry * next;\n} hashmap_entry_t;\n\ntypedef struct hashmap {\n\thashmap_hash_t hash_func;\n\thashmap_comp_t hash_comp;\n\thashmap_dupe_t hash_key_dup;\n\thashmap_free_t hash_key_free;\n\thashmap_free_t hash_val_free;\n\tsize_t         size;\n\thashmap_entry_t ** entries;\n} hashmap_t;\n\nextern hashmap_t * hashmap_create(int size);\nextern hashmap_t * hashmap_create_int(int size);\nextern void * hashmap_set(hashmap_t * map, const void * key, void * value);\nextern void * hashmap_get(hashmap_t * map, const void * key);\nextern void * hashmap_remove(hashmap_t * map, const void * key);\nextern int hashmap_has(hashmap_t * map, const void * key);\nextern list_t * hashmap_keys(hashmap_t * map);\nextern list_t * hashmap_values(hashmap_t * map);\nextern void hashmap_free(hashmap_t * map);\n\nextern unsigned int hashmap_string_hash(const void * key);\nextern int hashmap_string_comp(const void * a, const void * b);\nextern void * hashmap_string_dupe(const void * key);\nextern int hashmap_is_empty(hashmap_t * map);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/icon_cache.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n\n_Begin_C_Header\n\nextern sprite_t * icon_get_16(const char * name);\nextern sprite_t * icon_get_48(const char * name);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/toaru/inflate.h",
    "content": "/**\n * @brief DEFLATE inflater\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020 K. Lange\n */\n\n#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\nstruct huff_ring;\n\nstruct inflate_context {\n\n\t/* Consumer-private pointers for input/output storage (eg. FILE *) */\n\tvoid * input_priv;\n\tvoid * output_priv;\n\n\t/* Methods for reading / writing from the input /output */\n\tuint8_t (*get_input)(struct inflate_context * ctx);\n\tvoid (*write_output)(struct inflate_context * ctx, unsigned int sym);\n\n\t/* Bit buffer, which holds at most 8 bits from the input */\n\tint bit_buffer;\n\tint buffer_size;\n\n\t/* Output ringbuffer for backwards lookups */\n\tstruct huff_ring * ring;\n};\n\nint deflate_decompress(struct inflate_context * ctx);\nint gzip_decompress(struct inflate_context * ctx);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/jpeg.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n\n_Begin_C_Header\n\nextern int load_sprite_jpg(sprite_t * sprite, char * filename);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/json.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n\n_Begin_C_Header\n\n#define JSON_TYPE_OBJECT 0\n#define JSON_TYPE_ARRAY  1\n#define JSON_TYPE_STRING 2\n#define JSON_TYPE_NUMBER 3\n#define JSON_TYPE_BOOL   4\n#define JSON_TYPE_NULL   5\n\nstruct JSON_Value {\n\tint type;\n\tunion {\n\t\tchar * string;\n\t\tdouble number;\n\t\tlist_t * array;\n\t\thashmap_t * object;\n\t\tint boolean;\n\t};\n};\n\n#define JSON_KEY(v,k) ((struct JSON_Value *)(hashmap_get(v->object,k)))\n#define JSON_IND(v,i) ((struct JSON_Value *)(list_index(v->array,i)))\n\n/**\n * json_free\n *\n * Free a struct JSON_Value, and its contents recursively if it's an array,\n * object, string, etc.\n */\nextern void json_free(struct JSON_Value *);\n\n/**\n * json_parse\n *\n * Parse a string into a JSON_Value\n */\nextern struct JSON_Value * json_parse(const char *);\n\n/**\n * json_parse_file\n *\n * Open a file path and parse its contents as JSON\n * (Convenience function)\n */\nextern struct JSON_Value * json_parse_file(const char * filename);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/kbd.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\n#define KEY_NONE        0\n#define KEY_BACKSPACE   8\n#define KEY_CTRL_A      1\n#define KEY_CTRL_B      2\n#define KEY_CTRL_C      3\n#define KEY_CTRL_D      4\n#define KEY_CTRL_E      5\n#define KEY_CTRL_F      6\n#define KEY_CTRL_G      7\n#define KEY_CTRL_H      8\n#define KEY_CTRL_I      9\n#define KEY_CTRL_J      10\n#define KEY_CTRL_K      11\n#define KEY_CTRL_L      12\n#define KEY_CTRL_M      13\n#define KEY_CTRL_N      14\n#define KEY_CTRL_O      15\n#define KEY_CTRL_P      16\n#define KEY_CTRL_Q      17\n#define KEY_CTRL_R      18\n#define KEY_CTRL_S      19\n#define KEY_CTRL_T      20\n#define KEY_CTRL_U      21\n#define KEY_CTRL_V      22\n#define KEY_CTRL_W      23\n#define KEY_CTRL_X      24\n#define KEY_CTRL_Y      25\n#define KEY_CTRL_Z      26\n#define KEY_ESCAPE      27\n#define KEY_NORMAL_MAX  256\n#define KEY_ARROW_UP    257\n#define KEY_ARROW_DOWN  258\n#define KEY_ARROW_RIGHT 259\n#define KEY_ARROW_LEFT  260\n\n#define KEY_LEFT_CTRL   1001\n#define KEY_LEFT_SHIFT  1002\n#define KEY_LEFT_ALT    1003\n#define KEY_LEFT_SUPER  1004\n\n#define KEY_RIGHT_CTRL  1011\n#define KEY_RIGHT_SHIFT 1012\n#define KEY_RIGHT_ALT   1013\n#define KEY_RIGHT_SUPER 1014\n\n#define KEY_F1          2001\n#define KEY_F2          2002\n#define KEY_F3          2003\n#define KEY_F4          2004\n#define KEY_F5          2005\n#define KEY_F6          2006\n#define KEY_F7          2007\n#define KEY_F8          2008\n#define KEY_F9          2009\n#define KEY_F10         2010\n#define KEY_F11         2011\n#define KEY_F12         2012\n\n#define KEY_PAGE_DOWN   2013\n#define KEY_PAGE_UP     2014\n\n#define KEY_HOME        2015\n#define KEY_END         2016\n#define KEY_DEL         2017\n#define KEY_INSERT      2018\n#define KEY_PAUSE       2019\n#define KEY_SCROLL_LOCK 2020\n#define KEY_PRINT_SCREEN 2021\n#define KEY_APP         2022\n\n#define KEY_NUM_0       2500\n#define KEY_NUM_1       2501\n#define KEY_NUM_2       2502\n#define KEY_NUM_3       2503\n#define KEY_NUM_4       2504\n#define KEY_NUM_5       2505\n#define KEY_NUM_6       2506\n#define KEY_NUM_7       2507\n#define KEY_NUM_8       2508\n#define KEY_NUM_9       2509\n#define KEY_NUM_DOT     2510\n#define KEY_NUM_DIV     2511\n#define KEY_NUM_STAR    2512\n#define KEY_NUM_MINUS   2513\n#define KEY_NUM_PLUS    2514\n#define KEY_NUM_ENTER   2515\n\n#define KEY_MOD_LEFT_CTRL   0x01\n#define KEY_MOD_LEFT_SHIFT  0x02\n#define KEY_MOD_LEFT_ALT    0x04\n#define KEY_MOD_LEFT_SUPER  0x08\n\n#define KEY_MOD_RIGHT_CTRL  0x10\n#define KEY_MOD_RIGHT_SHIFT 0x20\n#define KEY_MOD_RIGHT_ALT   0x40\n#define KEY_MOD_RIGHT_SUPER 0x80\n\n#define KEY_ACTION_DOWN     0x01\n#define KEY_ACTION_UP       0x02\n\ntypedef unsigned int  kbd_key_t;\ntypedef unsigned int  kbd_mod_t;\ntypedef unsigned char kbd_act_t;\n\ntypedef struct {\n\tkbd_key_t keycode;\n\tkbd_mod_t modifiers;\n\tkbd_act_t action;\n\n\tunsigned char key; /* Key as a raw code, ready for reading, or \\0 if it's not a good down strike / was a modifier change / etc/. */\n} key_event_t;\n\ntypedef struct {\n\tint kbd_state;\n\tint kbd_s_state;\n\n\tint k_ctrl;\n\tint k_shift;\n\tint k_alt;\n\tint k_super;\n\n\tint kl_ctrl;\n\tint kl_shift;\n\tint kl_alt;\n\tint kl_super;\n\n\tint kr_ctrl;\n\tint kr_shift;\n\tint kr_alt;\n\tint kr_super;\n\n\tint kbd_esc_buf;\n} key_event_state_t;\n\nextern int kbd_scancode(key_event_state_t * state, unsigned char c, key_event_t * event);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/list.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n#ifdef _KERNEL_\n#\tinclude <kernel/types.h>\n#else\n#\tinclude <stdint.h>\n#\tinclude <stddef.h>\n#\tinclude <assert.h>\n#endif\n\n_Begin_C_Header\n\ntypedef struct node {\n\tstruct node * next;\n\tstruct node * prev;\n\tvoid * value;\n\tvoid * owner;\n} __attribute__((packed)) node_t;\n\ntypedef struct {\n\tnode_t * head;\n\tnode_t * tail;\n\tsize_t length;\n} __attribute__((packed)) list_t;\n\nextern void list_destroy(list_t * list);\nextern void list_free(list_t * list);\nextern void list_append(list_t * list, node_t * item);\nextern node_t * list_insert(list_t * list, void * item);\nextern list_t * list_create(void);\nextern node_t * list_find(list_t * list, void * value);\nextern int list_index_of(list_t * list, void * value);\nextern void list_remove(list_t * list, size_t index);\nextern void list_delete(list_t * list, node_t * node);\nextern node_t * list_pop(list_t * list);\nextern node_t * list_dequeue(list_t * list);\nextern list_t * list_copy(list_t * original);\nextern void list_merge(list_t * target, list_t * source);\nextern void * list_index(list_t * list, int index);\n\nextern void list_append_after(list_t * list, node_t * before, node_t * node);\nextern node_t * list_insert_after(list_t * list, node_t * before, void * item);\n\nextern void list_append_before(list_t * list, node_t * after, node_t * node);\nextern node_t * list_insert_before(list_t * list, node_t * after, void * item);\n\n/* Known to conflict with some popular third-party libraries. */\n#ifndef TOARU_LIST_NO_FOREACH\n#  define foreach(i, list) for (node_t * i = (list)->head; i != NULL; i = i->next)\n#  define foreachr(i, list) for (node_t * i = (list)->tail; i != NULL; i = i->prev)\n#endif\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/markup.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/hashmap.h>\n\n_Begin_C_Header\n\nstruct markup_tag {\n\tchar * name;\n\thashmap_t * options;\n};\n\nstruct markup_state;\ntypedef int (*markup_callback_tag_open)(struct markup_state * self, void * user, struct markup_tag * tag);\ntypedef int (*markup_callback_tag_close)(struct markup_state * self, void * user, char * tag_name);\ntypedef int (*markup_callback_data)(struct markup_state * self, void * user, char * data);\n\nextern struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data);\nextern int markup_free_tag(struct markup_tag * tag);\nextern int markup_parse(struct markup_state * state, char c);\nextern int markup_finish(struct markup_state * state);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/markup_text.h",
    "content": "#pragma once\n#include <_cheader.h>\n#include <toaru/graphics.h>\n\n_Begin_C_Header\n\nint markup_string_width(const char * str);\nint markup_string_height(const char * str);\nint markup_draw_string(gfx_context_t * ctx, int x, int y, const char * str, uint32_t color);\nvoid markup_text_init(void);\nstruct MarkupState * markup_setup_renderer(gfx_context_t * ctx, int x, int y, uint32_t color, int dryrun);\nvoid markup_set_base_font_size(struct MarkupState * state, int size);\nvoid markup_set_base_state(struct MarkupState * state, int mode);\nint markup_push_string(struct MarkupState * state, const char * str);\nint markup_push_raw_string(struct MarkupState * state, const char * str);\nint markup_finish_renderer(struct MarkupState * state);\n\n#define MARKUP_TEXT_STATE_BOLD     (1 << 0)\n#define MARKUP_TEXT_STATE_OBLIQUE  (1 << 1)\n#define MARKUP_TEXT_STATE_HEADING  (1 << 2)\n#define MARKUP_TEXT_STATE_SMALL    (1 << 3)\n#define MARKUP_TEXT_STATE_MONO     (1 << 4)\n\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/menu.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n#include <toaru/yutani.h>\n\n_Begin_C_Header\n\nenum MenuEntry_Type {\n\tMenuEntry_Unknown,\n\tMenuEntry_Normal,\n\tMenuEntry_Submenu,\n\tMenuEntry_Separator,\n\tMenuEntry_Toggle,\n};\n\nstruct MenuList;\nstruct MenuEntry;\n\nstruct MenuEntryVTable {\n\tsize_t methods;\n\tvoid (*renderer)(gfx_context_t *, struct MenuEntry *, int);\n\tvoid (*focus_change)(struct MenuEntry *, int);\n\tvoid (*activate)(struct MenuEntry *, int);\n\tint (*mouse_event)(struct MenuEntry *, struct yutani_msg_window_mouse_event *);\n};\n\nstruct MenuEntry {\n\tenum MenuEntry_Type _type;\n\tstruct MenuList * _owner;\n\tvoid * _private;\n\n\tint height; /* All must have a height, so put it here. */\n\tint width; /* Actual width */\n\tint rwidth; /* Requested width */\n\tint hilight; /* Is currently hilighted */\n\tint offset; /* Our offset when we were rendered */\n\n\tstruct MenuEntryVTable * vtable;\n\tvoid (*callback)(struct MenuEntry *);\n};\n\nstruct MenuEntry_Normal {\n\tstruct MenuEntry; /* dependent on plan9 extensions */\n\tchar * icon;\n\tchar * title;\n\tchar * action;\n\tunsigned long flags;\n};\n\nstruct MenuEntry_Toggle {\n\tstruct MenuEntry_Normal;\n\tint set;\n};\n\nstruct MenuEntry_Submenu {\n\tstruct MenuEntry_Normal;\n\tstruct MenuList * _my_child;\n};\n\nstruct MenuEntry_Separator {\n\tstruct MenuEntry;\n};\n\nstruct MenuList {\n\tlist_t * entries;\n\tgfx_context_t * ctx;\n\tyutani_window_t * window;\n\tstruct MenuSet * set;\n\tstruct MenuList * child;\n\tstruct MenuList * parent;\n\tstruct menu_bar * _bar;\n\tint closed;\n\tint flags;\n\tint tail_offset;\n\tyutani_window_t * main_window;\n};\n\nstruct MenuSet {\n\thashmap_t * _menus;\n};\n\nextern struct MenuEntry * menu_create_normal(const char * icon, const char * action, const char * title, void (*callback)(struct MenuEntry *));\nextern struct MenuEntry * menu_create_toggle(const char * action, const char * title, int set, void (*callback)(struct MenuEntry *));\nextern struct MenuEntry * menu_create_submenu(const char * icon, const char * action, const char * title);\nextern struct MenuEntry * menu_create_separator(void);\nextern struct MenuList * menu_create(void);\nextern struct MenuSet * menu_set_from_description(const char * path, void (*callback)(struct MenuEntry *));\n\nextern void menu_insert(struct MenuList * menu, struct MenuEntry * entry);\nextern void menu_prepare(struct MenuList * menu, yutani_t * yctx);\nextern void menu_show_at(struct MenuList * menu, yutani_window_t * parent, int x, int y);\nextern int menu_process_event(yutani_t * yctx, yutani_msg_t * m);\nextern struct MenuList * menu_set_get_root(struct MenuSet * menu);\nextern struct MenuList * menu_set_get_menu(struct MenuSet * menu, char * submenu);\nextern void menu_calculate_dimensions(struct MenuList * menu, int * height, int * width);\n\nextern void menu_free_entry(struct MenuEntry * ptr);\nextern void menu_free_menu(struct MenuList * ptr);\nextern void menu_free_set(struct MenuSet * ptr);\n\nextern hashmap_t * menu_get_windows_hash(void);\nextern int menu_definitely_close(struct MenuList * menu);\nextern struct MenuSet * menu_set_create(void);\nextern void menu_set_insert(struct MenuSet * set, char * action, struct MenuList * menu);\nextern void menu_update_title(struct MenuEntry * self, char * new_title);\nextern void menu_force_redraw(struct MenuList * menu);\nextern void menu_update_icon(struct MenuEntry * self, char * newIcon);\nextern void menu_update_toggle_state(struct MenuEntry * self, int state);\nextern void menu_update_enabled(struct MenuEntry * self, int state);\n\n#define MENU_FLAG_BUBBLE_CENTER (1 << 0)\n#define MENU_FLAG_BUBBLE_LEFT   (1 << 1)\n#define MENU_FLAG_BUBBLE_RIGHT  (1 << 2)\n#define MENU_FLAG_BUBBLE (MENU_FLAG_BUBBLE_LEFT | MENU_FLAG_BUBBLE_RIGHT | MENU_FLAG_BUBBLE_CENTER)\n#define MENU_FLAG_TAIL_POSITION (1 << 3)\n\n#define MENU_BAR_HEIGHT 24\n\nstruct menu_bar_entries {\n\tchar * title;\n\tchar * action;\n};\n\nstruct menu_bar {\n\tint x;\n\tint y;\n\tint width;\n\n\tstruct menu_bar_entries * entries;\n\n\tstruct MenuSet * set;\n\n\tstruct menu_bar_entries * active_entry;\n\tstruct MenuList * active_menu;\n\tint active_menu_wid;\n\tint active_entry_idx;\n\tyutani_window_t * window;\n\n\tint num_entries;\n\n\tvoid * _private;\n\tvoid (*redraw_callback)(struct menu_bar *);\n};\n\nextern void menu_bar_render(struct menu_bar * self, gfx_context_t * ctx);\nextern int menu_bar_mouse_event(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, struct yutani_msg_window_mouse_event * me, int x, int y);\nextern void menu_bar_show_menu(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, int offset, struct menu_bar_entries * _entries);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/mouse.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\ntypedef enum {\n\tLEFT_CLICK   = 0x01,\n\tRIGHT_CLICK  = 0x02,\n\tMIDDLE_CLICK = 0x04,\n\n\tMOUSE_SCROLL_UP = 0x10,\n\tMOUSE_SCROLL_DOWN = 0x20,\n} mouse_click_t;\n\ntypedef struct {\n\tuint32_t magic;\n\tint32_t x_difference;\n\tint32_t y_difference;\n\tmouse_click_t buttons;\n} mouse_device_packet_t;\n\n#define MOUSE_MAGIC 0xFEED1234\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/panel.h",
    "content": "/**\n * @brief Panel extensions header\n *\n * Exposed API for the panel\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n\n#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <toaru/yutani.h>\n#include <toaru/text.h>\n\n_Begin_C_Header\n\nstruct PanelContext {\n\tuint32_t color_text_normal;\n\tuint32_t color_text_hilighted;\n\tuint32_t color_text_focused;\n\tuint32_t color_icon_normal;\n\tuint32_t color_special;\n\n\tint font_size_default;\n\n\tyutani_window_t * basewindow;\n\n\tstruct TT_Font * font;\n\tstruct TT_Font * font_bold;\n\tstruct TT_Font * font_mono;\n\tstruct TT_Font * font_mono_bold;\n\n\tint extra_widget_spacing;\n};\n\nstruct PanelWidget {\n\tstruct PanelContext * pctx;\n\tint highlighted;\n\tint left;\n\tint width;\n\tint fill;\n\n\tint (*click)(struct PanelWidget *, struct yutani_msg_window_mouse_event *);\n\tint (*right_click)(struct PanelWidget *, struct yutani_msg_window_mouse_event *);\n\tint (*leave)(struct PanelWidget *, struct yutani_msg_window_mouse_event *);\n\tint (*enter)(struct PanelWidget *, struct yutani_msg_window_mouse_event *);\n\tint (*move)(struct PanelWidget *, struct yutani_msg_window_mouse_event *);\n\tint (*draw)(struct PanelWidget *, gfx_context_t * ctx);\n\tint (*update)(struct PanelWidget *, int *force_updates);\n\tint (*onkey)(struct PanelWidget *, struct yutani_msg_key_event *);\n};\n\nextern yutani_t * yctx;\nextern list_t * widgets_enabled;\nextern struct PanelWidget * widget_new(void);\n\nextern void launch_application_menu(struct MenuEntry * self);\n\nstruct window_ad {\n\tyutani_wid_t wid;\n\tuint32_t flags;\n\tchar * name;\n\tchar * icon;\n\tchar * strings;\n\tint left;\n\tuint32_t bufid;\n\tuint32_t width;\n\tuint32_t height;\n};\n\nextern struct window_ad * ads_by_z[];\nextern list_t * window_list;\nextern void redraw(void);\nextern int panel_menu_show(struct PanelWidget * this, struct MenuList * menu);\nextern int panel_menu_show_at(struct MenuList * menu, int x);\nextern void panel_highlight_widget(struct PanelWidget * this, gfx_context_t * ctx, int active);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/pex.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <stdio.h>\n\n_Begin_C_Header\n\ntypedef struct pex_packet {\n\tuintptr_t source;\n\tsize_t      size;\n\tuint8_t     data[];\n} pex_packet_t;\n#define MAX_PACKET_SIZE 1024\n#define PACKET_SIZE (sizeof(pex_packet_t) + MAX_PACKET_SIZE)\n\ntypedef struct pex_header {\n\tuintptr_t target;\n\tuint8_t data[];\n} pex_header_t;\n\nextern size_t pex_send(FILE * sock, uintptr_t rcpt, size_t size, char * blob);\nextern size_t pex_broadcast(FILE * sock, size_t size, char * blob);\nextern size_t pex_listen(FILE * sock, pex_packet_t * packet);\n\nextern size_t pex_reply(FILE * sock, size_t size, char * blob);\nextern size_t pex_recv(FILE * sock, char * blob);\nextern size_t pex_query(FILE * sock);\n\nextern FILE * pex_bind(char * target);\nextern FILE * pex_connect(char * target);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/png.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/graphics.h>\n\n_Begin_C_Header\n\nextern int load_sprite_png(sprite_t * sprite, char * filename);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/toaru/rline.h",
    "content": "#pragma once\n\nstruct rline_callback;\n\ntypedef struct {\n\tchar *  buffer;\n\tstruct rline_callback * callbacks;\n\tint     collected;\n\tint     requested;\n\tint     newline;\n\tint     cancel;\n\tint     offset;\n\tint     tabbed;\n\tint     quiet;\n} rline_context_t;\n\ntypedef void (*rline_callback_t)(rline_context_t * context);\n\ntypedef struct rline_callback {\n\trline_callback_t tab_complete;\n\trline_callback_t redraw_prompt;\n\trline_callback_t special_key;\n\trline_callback_t key_up;\n\trline_callback_t key_down;\n\trline_callback_t key_left;\n\trline_callback_t key_right;\n\trline_callback_t rev_search;\n} rline_callbacks_t;\n\ntypedef enum {\n\t/* Base colors */\n\tRLINE_STYLE_MAIN,\n\tRLINE_STYLE_ALT,\n\t/* Syntax flags */\n\tRLINE_STYLE_KEYWORD,\n\tRLINE_STYLE_STRING,\n\tRLINE_STYLE_COMMENT,\n\tRLINE_STYLE_TYPE,\n\tRLINE_STYLE_PRAGMA,\n\tRLINE_STYLE_NUMERAL,\n} rline_style_t;\n\nextern int rline(char * buffer, int buf_size);\nextern int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width);\nextern int rline_exp_set_shell_commands(char ** cmds, int len);\nextern int rline_exp_set_tab_complete_func(rline_callback_t func);\nextern int rline_exp_set_syntax(char * name);\nextern void rline_history_insert(char * str);\nextern void rline_history_append_line(char * str);\nextern char * rline_history_get(int item);\nextern char * rline_history_prev(int item);\nextern void rline_place_cursor(void);\nextern void rline_set_colors(rline_style_t style);\nextern void rline_redraw(rline_context_t * context);\nextern void rline_insert(rline_context_t * context, const char * what);\nextern int rline_terminal_width;\n\n#define RLINE_HISTORY_ENTRIES 128\nextern char * rline_history[RLINE_HISTORY_ENTRIES];\nextern int rline_history_count;\nextern int rline_history_offset;\nextern int rline_scroll;\nextern char * rline_exit_string;\nextern char * rline_preload;\n"
  },
  {
    "path": "base/usr/include/toaru/spinlock.h",
    "content": "#pragma once\n\n#ifndef spin_lock\nstatic void spin_lock(int volatile * lock) {\n\twhile(__sync_lock_test_and_set(lock, 0x01)) {\n\t\tsyscall_yield();\n\t}\n}\n\nstatic void spin_unlock(int volatile * lock) {\n\t__sync_lock_release(lock);\n}\n#endif\n\n"
  },
  {
    "path": "base/usr/include/toaru/termemu.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n\n_Begin_C_Header\n\n#define TERM_BUF_LEN 128\n\n/* A terminal cell represents a single character on screen */\ntypedef struct {\n\tuint32_t c;     /* codepoint */\n\tuint32_t fg;    /* background indexed color */\n\tuint32_t bg;    /* foreground indexed color */\n\tuint32_t flags; /* other flags */\n} term_cell_t;\n\ntypedef struct {\n\tvoid (*writer)(char);\n\tvoid (*set_color)(uint32_t, uint32_t);\n\tvoid (*set_csr)(int,int);\n\tint  (*get_csr_x)(void);\n\tint  (*get_csr_y)(void);\n\tvoid (*set_cell)(int,int,uint32_t);\n\tvoid (*cls)(int);\n\tvoid (*scroll)(int);\n\tvoid (*redraw_cursor)(void);\n\tvoid (*input_buffer_stuff)(char *);\n\tvoid (*set_title)(char *);\n\tvoid (*set_cell_contents)(int,int,char *);\n\tint  (*get_cell_width)(void);\n\tint  (*get_cell_height)(void);\n\tvoid (*set_csr_on)(int);\n\tvoid (*switch_buffer)(int);\n\tvoid (*insert_delete_lines)(int);\n\tvoid (*full_reset)(void);\n} term_callbacks_t;\n\ntypedef struct {\n\tuint16_t x;       /* Current cursor location */\n\tuint16_t y;       /*    \"      \"       \"     */\n\tuint16_t save_x;  /* Last cursor save */\n\tuint16_t save_y;\n\tuint32_t width;   /* Terminal width */\n\tuint32_t height;  /*     \"    height */\n\tuint32_t fg;      /* Current foreground color */\n\tuint32_t bg;      /* Current background color */\n\tuint8_t  flags;   /* Bright, etc. */\n\tuint8_t  escape;  /* Escape status */\n\tuint8_t  box;\n\tuint8_t  buflen;  /* Buffer Length */\n\tchar     buffer[TERM_BUF_LEN];  /* Previous buffer */\n\tterm_callbacks_t * callbacks;\n\tint volatile lock;\n\tuint8_t  mouse_on;\n\tuint32_t img_collected;\n\tuint32_t img_size;\n\tchar *   img_data;\n\tuint8_t  paste_mode;\n} term_state_t;\n\n/* Triggers escape mode. */\n#define ANSI_ESCAPE  27\n/* Escape verify */\n#define ANSI_BRACKET '['\n#define ANSI_BRACKET_RIGHT ']'\n#define ANSI_OPEN_PAREN '('\n/* Anything in this range (should) exit escape mode. */\n#define ANSI_LOW    'A'\n#define ANSI_HIGH   'z'\n/* Escape commands */\n#define ANSI_CUU    'A' /* CUrsor Up                  */\n#define ANSI_CUD    'B' /* CUrsor Down                */\n#define ANSI_CUF    'C' /* CUrsor Forward             */\n#define ANSI_CUB    'D' /* CUrsor Back                */\n#define ANSI_CNL    'E' /* Cursor Next Line           */\n#define ANSI_CPL    'F' /* Cursor Previous Line       */\n#define ANSI_CHA    'G' /* Cursor Horizontal Absolute */\n#define ANSI_CUP    'H' /* CUrsor Position            */\n#define ANSI_ED     'J' /* Erase Data                 */\n#define ANSI_EL     'K' /* Erase in Line              */\n#define ANSI_SU     'S' /* Scroll Up                  */\n#define ANSI_SD     'T' /* Scroll Down                */\n#define ANSI_HVP    'f' /* Horizontal & Vertical Pos. */\n#define ANSI_SGR    'm' /* Select Graphic Rendition   */\n#define ANSI_DSR    'n' /* Device Status Report       */\n#define ANSI_SCP    's' /* Save Cursor Position       */\n#define ANSI_RCP    'u' /* Restore Cursor Position    */\n#define ANSI_HIDE   'l' /* DECTCEM - Hide Cursor      */\n#define ANSI_SHOW   'h' /* DECTCEM - Show Cursor      */\n#define ANSI_IL     'L' /* Insert Line(s)             */\n#define ANSI_DL     'M' /* Delete Line(s)             */\n/* Display flags */\n#define ANSI_BOLD      0x01\n#define ANSI_UNDERLINE 0x02\n#define ANSI_ITALIC    0x04\n#define ANSI_ALTFONT   0x08 /* Character should use alternate font */\n#define ANSI_SPECBG    0x10\n#define ANSI_BORDER    0x20\n#define ANSI_WIDE      0x40 /* Character is double width */\n#define ANSI_CROSS     0x80 /* And that's all I'm going to support (for now) */\n#define ANSI_EXT_IMG   0x100 /* Cell is actually an image, use fg color as pointer */\n\n#define ANSI_EXT_IOCTL 'z'  /* These are special escapes only we support */\n\n/* Default color settings */\n#define TERM_DEFAULT_FG     0x07 /* Index of default foreground */\n#define TERM_DEFAULT_BG     0x10 /* Index of default background */\n#define TERM_DEFAULT_FLAGS  0x00 /* Default flags for a cell */\n#define TERM_DEFAULT_OPAC   0xF2 /* For background, default transparency */\n\n#define TERMEMU_MOUSE_ENABLE  0x01\n#define TERMEMU_MOUSE_DRAG    0x02\n#define TERMEMU_MOUSE_SGR     0x04\n/* TODO: _MOUSE_UTF8          0x08 */\n/* TODO: _MOUSE_URXVT         0x10 */\n\nextern term_state_t * ansi_init(term_state_t * s, int w, int y, term_callbacks_t * callbacks_in);\nextern void ansi_put(term_state_t * s, char c);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/toaru/text.h",
    "content": "#pragma once\n/**\n * @file toaru/text.h\n * @brief TrueType glyph rasterizer.\n *\n * Exposed API for the TrueType renderer.\n */\n#include <stdint.h>\n\n/* Methods for loading fonts */\nextern struct TT_Font * tt_font_from_file(const char * fileName);\nextern struct TT_Font * tt_font_from_shm(const char * identifier);\n\n/* Methods for changing font sizes */\nextern void tt_set_size(struct TT_Font * font, float sizeInEm);\nextern void tt_set_size_px(struct TT_Font * font, float sizeInPx);\n\n/* Methods for dealing with glyphs */\nextern void tt_draw_glyph(gfx_context_t * ctx, struct TT_Font * font, int x_offset, int y_offset, unsigned int glyph, uint32_t color);\nextern int tt_glyph_for_codepoint(struct TT_Font * font, unsigned int codepoint);\nextern int tt_xadvance_for_glyph(struct TT_Font * font, unsigned int ind);\nextern float tt_glyph_width(struct TT_Font * font, unsigned int glyph);\nextern sprite_t * tt_bake_glyph(struct TT_Font * font, unsigned int glyph, uint32_t color, int *_x, int *_y, float xadjust);\n\n/* Convenience functions for dealing with whole strings */\nextern int tt_string_width(struct TT_Font * font, const char * s);\nextern int tt_string_width_int(struct TT_Font * font, const char * s);\nextern int tt_draw_string(gfx_context_t * ctx, struct TT_Font * font, int x, int y, const char * s, uint32_t color);\nextern void tt_draw_string_shadow(gfx_context_t * ctx, struct TT_Font * font, char * string, int font_size, int left, int top, uint32_t text_color, uint32_t shadow_color, int blur);\n\nstruct TT_FontMetrics {\n\tfloat ascender;\n\tfloat descender;\n\tfloat lineGap;\n};\n\nextern int tt_measure_font(struct TT_Font * font, struct TT_FontMetrics * metrics);\n\n/* Vector rasterizer engine */\nextern struct TT_Contour * tt_contour_start(float x, float y);\nextern struct TT_Shape * tt_contour_finish(const struct TT_Contour * in);\nextern struct TT_Contour * tt_contour_stroke_contour(const struct TT_Contour * in, float width);\nextern struct TT_Shape * tt_contour_stroke_shape(const struct TT_Contour * in, float width);\nextern struct TT_Contour * tt_contour_line_to(struct TT_Contour * shape, float x, float y);\nextern struct TT_Contour * tt_contour_move_to(struct TT_Contour * shape, float x, float y);\nextern void tt_path_paint(gfx_context_t * ctx, const struct TT_Shape * shape, uint32_t color);\nextern void tt_contour_transform(struct TT_Contour * cnt, gfx_matrix_t matrix);\n\n/* Internal methods to draw paths into vector contours */\nextern struct TT_Contour * tt_draw_glyph_into(struct TT_Contour * contour, struct TT_Font * font, float x_offset, float y_offset, unsigned int glyph);\nextern struct TT_Contour * tt_prepare_string(struct TT_Font * font, float x, float y, const char * s, float * out_width);\nextern struct TT_Contour * tt_prepare_string_into(struct TT_Contour * contour, struct TT_Font * font, float x, float y, const char * s, float * out_width);\n\n/* Draw with texture from sprite */\nextern void tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix);\nextern void tt_path_paint_sprite_options(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix, int filter, int wrap);\n\n/* Truncate text with an ellipsis to fit a requested width. */\nextern char * tt_ellipsify(const char * input, int font_size, struct TT_Font * font, int max_width, int * out_width);\n\n/* Path painting options */\n#define TT_PATH_FILTER_BILINEAR 0 /* Bilinear filter. Good quality, slow. Default. */\n#define TT_PATH_FILTER_NEAREST  1 /* Nearest neighbor. Low quality, fast. */\n\n#define TT_PATH_WRAP_REPEAT     0 /* Repeat the sprite texture, simple modulo. Default. */\n#define TT_PATH_WRAP_NONE       1 /* Do not repeat the sprite texture. Space outside of the texture is treated as empty. */\n#define TT_PATH_WRAP_PAD        2 /* Extend the edges of the texture linearly. */\n"
  },
  {
    "path": "base/usr/include/toaru/trace.h",
    "content": "#pragma once\n\n#include <stdio.h>\n#include <time.h>\n#include <sys/time.h>\n\n#ifndef TRACE\n#define TRACE(msg,...) do { \\\n\tstruct timeval t; gettimeofday(&t, NULL); \\\n\tfprintf(stderr, \"%06d.%06d [\" TRACE_APP_NAME \"] %s:%05d - \" msg \"\\n\", t.tv_sec, t.tv_usec, __FILE__, __LINE__, ##__VA_ARGS__); \\\n} while (0)\n#endif\n"
  },
  {
    "path": "base/usr/include/toaru/tree.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/list.h>\n\n_Begin_C_Header\n\ntypedef struct tree_node {\n\tvoid * value;\n\tlist_t * children;\n\tstruct tree_node * parent;\n} tree_node_t;\n\ntypedef struct {\n\tsize_t nodes;\n\ttree_node_t * root;\n} tree_t;\n\ntypedef uint8_t (*tree_comparator_t) (void *, void *);\n\nextern tree_t * tree_create(void);\nextern void tree_set_root(tree_t * tree, void * value);\nextern void tree_node_destroy(tree_node_t * node);\nextern void tree_destroy(tree_t * tree);\nextern void tree_free(tree_t * tree);\nextern tree_node_t * tree_node_create(void * value);\nextern void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node);\nextern tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value);\nextern tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle);\nextern void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node);\nextern void tree_node_remove(tree_t * tree, tree_node_t * node);\nextern void tree_remove(tree_t * tree, tree_node_t * node);\nextern tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator);\nextern void tree_break_off(tree_t * tree, tree_node_t * node);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/yutani-internal.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <toaru/yutani.h>\n\n_Begin_C_Header\n\n#define YUTANI_SHMKEY(server_ident,buf,sz,win) sprintf(buf, \"sys.%s.%d\", server_ident, win->bufid);\n#define YUTANI_SHMKEY_EXP(server_ident,buf,sz,bufid) sprintf(buf, \"sys.%s.%d\", server_ident, bufid);\n\n#define yutani_msg_buildx_hello_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_flip_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_welcome_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_welcome)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_new_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_new_flags_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new_flags)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_init_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_init)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_close_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_close)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_key_event_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_event)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_mouse_event_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_mouse_event)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_move_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_move_relative_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move_relative)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_set_parent_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_set_parent)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_stack_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_stack)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_focus_change_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus_change)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_mouse_event_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_mouse_event)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_flip_region_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip_region)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_resize_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_advertise_alloc(out, length) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_advertise) + length]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_subscribe_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_unsubscribe_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_query_windows_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_notify_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_session_end_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_focus_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_key_bind_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_bind)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_drag_start_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_drag_start)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_update_shape_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_update_shape)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_warp_mouse_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_warp_mouse)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_show_mouse_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_show_mouse)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_resize_start_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize_start)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_special_request_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_special_request)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_clipboard_alloc(out, length) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_clipboard)+length]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n#define yutani_msg_buildx_window_panel_size_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_panel_size)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE;\n\nextern void yutani_msg_buildx_hello(yutani_msg_t * msg);\nextern void yutani_msg_buildx_flip(yutani_msg_t * msg, yutani_wid_t wid);\nextern void yutani_msg_buildx_welcome(yutani_msg_t * msg, uint32_t width, uint32_t height);\nextern void yutani_msg_buildx_window_new(yutani_msg_t * msg, uint32_t width, uint32_t height);\nextern void yutani_msg_buildx_window_new_flags(yutani_msg_t * msg, uint32_t width, uint32_t height, uint32_t flags, yutani_wid_t parent_wid);\nextern void yutani_msg_buildx_window_init(yutani_msg_t * msg, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid);\nextern void yutani_msg_buildx_window_close(yutani_msg_t * msg, yutani_wid_t wid);\nextern void yutani_msg_buildx_key_event(yutani_msg_t * msg, yutani_wid_t wid, key_event_t * event, key_event_state_t * state);\nextern void yutani_msg_buildx_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, mouse_device_packet_t * event, int32_t type);\nextern void yutani_msg_buildx_window_move(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y);\nextern void yutani_msg_buildx_window_move_relative(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2, int32_t x, int32_t y);\nextern void yutani_msg_buildx_window_set_parent(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2);\nextern void yutani_msg_buildx_window_stack(yutani_msg_t * msg, yutani_wid_t wid, int z);\nextern void yutani_msg_buildx_window_focus_change(yutani_msg_t * msg, yutani_wid_t wid, int focused);\nextern void yutani_msg_buildx_window_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, int32_t new_x, int32_t new_y, int32_t old_x, int32_t old_y, uint8_t buttons, uint8_t command, uint8_t modifiers);\nextern void yutani_msg_buildx_flip_region(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y, int32_t width, int32_t height);\nextern void yutani_msg_buildx_window_resize(yutani_msg_t * msg, uint32_t type, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid, uint32_t flags);\nextern void yutani_msg_buildx_window_advertise(yutani_msg_t * msg, yutani_wid_t wid, uint32_t flags, uint32_t icon, uint32_t bufid, uint32_t width, uint32_t height, size_t length, char * data);\nextern void yutani_msg_buildx_subscribe(yutani_msg_t * msg);\nextern void yutani_msg_buildx_unsubscribe(yutani_msg_t * msg);\nextern void yutani_msg_buildx_query_windows(yutani_msg_t * msg);\nextern void yutani_msg_buildx_notify(yutani_msg_t * msg);\nextern void yutani_msg_buildx_session_end(yutani_msg_t * msg);\nextern void yutani_msg_buildx_window_focus(yutani_msg_t * msg, yutani_wid_t wid);\nextern void yutani_msg_buildx_key_bind(yutani_msg_t * msg, kbd_key_t key, kbd_mod_t mod, int response);\nextern void yutani_msg_buildx_window_drag_start(yutani_msg_t * msg, yutani_wid_t wid);\nextern void yutani_msg_buildx_window_update_shape(yutani_msg_t * msg, yutani_wid_t wid, int set_shape);\nextern void yutani_msg_buildx_window_warp_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y);\nextern void yutani_msg_buildx_window_show_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t show_mouse);\nextern void yutani_msg_buildx_window_resize_start(yutani_msg_t * msg, yutani_wid_t wid, yutani_scale_direction_t direction);\nextern void yutani_msg_buildx_special_request(yutani_msg_t * msg, yutani_wid_t wid, uint32_t request);\nextern void yutani_msg_buildx_clipboard(yutani_msg_t * msg, char * content);\nextern void yutani_msg_buildx_window_panel_size(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/yutani-server.h",
    "content": "/**\n * @brief Internal definitions used by the Yutani compositor.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#pragma once\n\n#include <_cheader.h>\n#include <stdint.h>\n#include <sys/time.h>\n#include <toaru/yutani.h>\n\n_Begin_C_Header\n\n/* Mouse resolution scaling */\n#define MOUSE_SCALE 3\n#define YUTANI_INCOMING_MOUSE_SCALE * 3\n\n/* Mouse cursor hotspot */\n#define MOUSE_OFFSET_X 26\n#define MOUSE_OFFSET_Y 26\n\n/* Mouse cursor size */\n#define MOUSE_WIDTH 64\n#define MOUSE_HEIGHT 64\n\n/* How much the mouse needs to move to break off a tiled window */\n#define UNTILE_SENSITIVITY (MOUSE_SCALE * 5)\n\n/* Screenshot modes */\n#define YUTANI_SCREENSHOT_FULL 1\n#define YUTANI_SCREENSHOT_WINDOW 2\n\n/*\n * Animation effect types.\n * XXX: Should this be in the client library?\n */\ntypedef enum {\n\tYUTANI_EFFECT_NONE,\n\n\t/* Basic animations */\n\tYUTANI_EFFECT_FADE_IN,\n\tYUTANI_EFFECT_FADE_OUT,\n\n\t/* XXX: Are these used? */\n\tYUTANI_EFFECT_MINIMIZE,\n\tYUTANI_EFFECT_UNMINIMIZE,\n\n\t/* Dialog animations, faster than the fades */\n\tYUTANI_EFFECT_SQUEEZE_IN,\n\tYUTANI_EFFECT_SQUEEZE_OUT,\n\n\tYUTANI_EFFECT_DISAPPEAR,\n} yutani_effect;\n\n/* Animation lengths */\nstatic int yutani_animation_lengths[] = {\n\t0,   /* None */\n\t200, /* Fade In */\n\t200, /* Fade Out */\n\t200,   /* Minimize */\n\t200,   /* Unminimized */\n\t100, /* Squeeze in */\n\t100, /* Squeeze out */\n\t10,  /* Disappear */\n};\n\nstatic int yutani_is_closing_animation[] = {\n\t0,\n\t0,\n\t1,\n\t0,\n\t0,\n\t0,\n\t1,\n\t1,\n};\n\nstatic int yutani_is_minimizing_animation[] = {\n\t0,\n\t0,\n\t0,\n\t1,\n\t0,\n\t0,\n\t0,\n\t0\n};\n\n/* Debug Options */\n#define YUTANI_DEBUG_WINDOW_BOUNDS 1\n#define YUTANI_DEBUG_WINDOW_SHAPES 1\n\n/* Command line flag values */\nstruct {\n\tint nested;\n\tint nest_width;\n\tint nest_height;\n} yutani_options = {\n\t.nested = 0,\n\t.nest_width = 640,\n\t.nest_height = 480,\n};\n\n/*\n * Server window definitions\n */\ntypedef struct YutaniServerWindow {\n\t/* Window identifier number */\n\tyutani_wid_t wid;\n\n\t/* Window location */\n\tsigned long x;\n\tsigned long y;\n\n\t/* Stack order */\n\tunsigned short z;\n\n\t/* Window size */\n\tint32_t width;\n\tint32_t height;\n\n\t/* Canvas buffer */\n\tuint8_t * buffer;\n\tuint32_t bufid;\n\tuint32_t newbufid;\n\tuint8_t * newbuffer;\n\n\t/* Connection that owns this window */\n\tuintptr_t owner;\n\n\t/* Rotation of windows XXX */\n\tint16_t  rotation;\n\n\t/* Client advertisements */\n\tuint32_t client_flags;\n\tuint32_t client_icon;\n\tuint32_t client_length;\n\tchar *   client_strings;\n\n\t/* Window animations */\n\tuint64_t anim_mode;\n\tuint64_t anim_start;\n\n\t/* Alpha shaping threshold */\n\tint alpha_threshold;\n\n\t/*\n\t * Mouse cursor selection\n\t * Originally, this specified whether the mouse was\n\t * hidden, but it plays double duty since client\n\t * control over mouse cursors was added.\n\t */\n\tint show_mouse;\n\tint default_mouse;\n\n\t/* Tiling / untiling information */\n\tint tiled;\n\tint32_t untiled_width;\n\tint32_t untiled_height;\n\tint32_t untiled_left;\n\tint32_t untiled_top;\n\n\t/* Client-configurable server behavior flags */\n\tuint32_t server_flags;\n\n\t/* Window opacity */\n\tint opacity;\n\n\t/* Window is hidden? */\n\tint hidden;\n\tint minimized;\n\n\tint32_t icon_x, icon_y, icon_w, icon_h;\n\n\tyutani_wid_t parent;\n} yutani_server_window_t;\n\ntypedef struct YutaniGlobals {\n\t/* Display resolution */\n\tunsigned int width;\n\tunsigned int height;\n\tuint32_t stride;\n\n\t/* TODO: What about multiple screens?\n\t *\n\t * Obviously this is the whole canvas size,\n\t * but we need to be able to track different\n\t * monitors if/when we ever get support for that.\n\t */\n\n\t/* Core graphics context */\n\tvoid * backend_framebuffer;\n\tgfx_context_t * backend_ctx;\n\n\t/* Mouse location */\n\tsigned int mouse_x;\n\tsigned int mouse_y;\n\n\t/*\n\t * Previous mouse location, so that events can have\n\t * both the new and old mouse location together\n\t */\n\tsigned int last_mouse_x;\n\tsigned int last_mouse_y;\n\n\t/* List of all windows */\n\tlist_t * windows;\n\n\t/* Hash of window IDs to their objects */\n\thashmap_t * wids_to_windows;\n\n\t/*\n\t * Window stacking information\n\t * TODO: Support multiple top and bottom windows.\n\t */\n\tyutani_server_window_t * bottom_z;\n\tlist_t * mid_zs;\n\tlist_t * menu_zs;\n\tlist_t * overlay_zs;\n\tyutani_server_window_t * top_z;\n\n\t/* Damage region list */\n\tlist_t * update_list;\n\n\t/* Mouse cursors */\n\tsprite_t mouse_sprite;\n\tsprite_t mouse_sprite_drag;\n\tsprite_t mouse_sprite_resize_v;\n\tsprite_t mouse_sprite_resize_h;\n\tsprite_t mouse_sprite_resize_da;\n\tsprite_t mouse_sprite_resize_db;\n\tsprite_t mouse_sprite_point;\n\tsprite_t mouse_sprite_ibeam;\n\tint current_cursor;\n\n\t/* Server backend communication identifier */\n\tchar * server_ident;\n\tFILE * server;\n\n\t/* Pointer to focused window */\n\tyutani_server_window_t * focused_window;\n\n\t/* Mouse movement state */\n\tint mouse_state;\n\n\t/* Pointer to window being manipulated by mouse actions */\n\tyutani_server_window_t * mouse_window;\n\n\t/* Buffered information on mouse-moved window */\n\tint mouse_win_x;\n\tint mouse_win_y;\n\tint mouse_init_x;\n\tint mouse_init_y;\n\tint mouse_init_r;\n\n\tint32_t mouse_click_x_orig;\n\tint32_t mouse_click_y_orig;\n\n\tint mouse_drag_button;\n\tint mouse_moved;\n\n\tint32_t mouse_click_x;\n\tint32_t mouse_click_y;\n\n\t/* Pointer to window being resized */\n\tyutani_server_window_t * resizing_window;\n\tint32_t resizing_w;\n\tint32_t resizing_h;\n\tyutani_scale_direction_t resizing_direction;\n\tint32_t resizing_offset_x;\n\tint32_t resizing_offset_y;\n\tint resizing_button;\n\n\t/* List of clients subscribing to window information events */\n\tlist_t * window_subscribers;\n\n\t/* When the server started, used for timing functions */\n\ttime_t start_time;\n\tsuseconds_t start_subtime;\n\n\t/* Pointer to last hovered window to allow exit events */\n\tyutani_server_window_t * old_hover_window;\n\n\t/* Key bindings */\n\thashmap_t * key_binds;\n\n\t/* Windows to remove after the end of the rendering pass */\n\tlist_t * windows_to_remove;\n\n\t/* For nested mode, the host Yutani context and window */\n\tyutani_t * host_context;\n\tyutani_window_t * host_window;\n\n\t/* Map of clients to their windows */\n\thashmap_t * clients_to_windows;\n\n\t/* Toggles for debugging window locations */\n\tint debug_bounds;\n\tint debug_shapes;\n\n\t/* If the next rendered frame should be saved as a screenshot */\n\tint screenshot_frame;\n\n\t/* Next frame should resize host context */\n\tint resize_on_next;\n\n\t/* Last mouse buttons - used for some specialized mouse drivers */\n\tuint32_t last_mouse_buttons;\n\n\t/* Clipboard buffer */\n\tchar clipboard[512];\n\tint clipboard_size;\n\n\t/* VirtualBox Seamless mode support information */\n\tint vbox_rects;\n\tint vbox_pointer;\n\n\t/* Renderer plugin context */\n\tvoid * renderer_ctx;\n\n\tint reload_renderer;\n\tuint8_t active_modifiers;\n\n\tuint64_t resize_release_time;\n\tint32_t resizing_init_w;\n\tint32_t resizing_init_h;\n\n\tlist_t * windows_to_minimize;\n\tlist_t * minimized_zs;\n} yutani_globals_t;\n\nstruct key_bind {\n\tuintptr_t owner;\n\tint response;\n};\n\n/* Exported functions for plugins */\nextern int yutani_window_is_top(yutani_globals_t * yg, yutani_server_window_t * window);\nextern int yutani_window_is_bottom(yutani_globals_t * yg, yutani_server_window_t * window);\nextern uint64_t yutani_time_since(yutani_globals_t * yg, uint64_t start_time);\nextern void yutani_window_to_device(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y);\nextern void yutani_device_to_window(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y);\nextern uint32_t yutani_color_for_wid(yutani_wid_t wid);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/toaru/yutani.h",
    "content": "/**\n * @brief Yutani Client Library\n *\n * Client library for the compositing window system.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#pragma once\n\n#include <_cheader.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#include <toaru/hashmap.h>\n#include <toaru/graphics.h>\n#include <toaru/kbd.h>\n#include <toaru/mouse.h>\n#include <toaru/list.h>\n\n_Begin_C_Header\n\ntypedef unsigned int yutani_wid_t;\n\n/*\n * Server connection context.\n */\ntypedef struct yutani_context {\n\tFILE * sock;\n\n\t/* server display size */\n\tsize_t display_width;\n\tsize_t display_height;\n\n\t/* Hash of window IDs to window objects */\n\thashmap_t * windows;\n\n\t/* queued events */\n\tlist_t * queued;\n\n\t/* server identifier string */\n\tchar * server_ident;\n} yutani_t;\n\ntypedef struct yutani_window {\n\t/* Server window identifier, unique to each window */\n\tyutani_wid_t wid;\n\n\t/* Window size */\n\tuint32_t width;\n\tuint32_t height;\n\n\t/* Window backing buffer */\n\tchar * buffer;\n\t/*\n\t * Because the buffer can change during resizing,\n\t * buffers are indexed to ensure we are using\n\t * the one the server expects.\n\t */\n\tuint32_t bufid;\n\n\t/* Window focused flag */\n\tuint8_t focused;\n\n\t/* Old buffer ID */\n\tuint32_t oldbufid;\n\n\t/* Generic pointer for client use */\n\tvoid * user_data;\n\n\t/* Window position in the server; automatically updated */\n\tint32_t x;\n\tint32_t y;\n\n\t/* Flags for the decorator library to use */\n\tuint32_t decorator_flags;\n\n\t/* Server context that owns this window */\n\tyutani_t * ctx;\n\n\tint32_t mouse_state;\n} yutani_window_t;\n\ntypedef struct yutani_message {\n\tuint32_t magic;\n\tuint32_t type;\n\tuint32_t size;\n\tchar data[];\n} yutani_msg_t;\n\nstruct yutani_msg_welcome {\n\tuint32_t display_width;\n\tuint32_t display_height;\n};\n\nstruct yutani_msg_flip {\n\tyutani_wid_t wid;\n};\n\nstruct yutani_msg_window_close {\n\tyutani_wid_t wid;\n};\n\nstruct yutani_msg_window_new {\n\tuint32_t width;\n\tuint32_t height;\n};\n\nstruct yutani_msg_window_new_flags {\n\tuint32_t width;\n\tuint32_t height;\n\tuint32_t flags;\n\tyutani_wid_t parent_wid;\n};\n\nstruct yutani_msg_window_init {\n\tyutani_wid_t wid;\n\tuint32_t width;\n\tuint32_t height;\n\tuint32_t bufid;\n};\n\nstruct yutani_msg_window_move {\n\tyutani_wid_t wid;\n\tint32_t x;\n\tint32_t y;\n};\n\nstruct yutani_msg_window_move_relative {\n\tyutani_wid_t wid_to_move;\n\tyutani_wid_t wid_base;\n\tint32_t x;\n\tint32_t y;\n};\n\nstruct yutani_msg_key_event {\n\tyutani_wid_t wid;\n\tkey_event_t event;\n\tkey_event_state_t state;\n};\n\nstruct yutani_msg_window_stack {\n\tyutani_wid_t wid;\n\tint z;\n};\n\nstruct yutani_msg_window_focus_change {\n\tyutani_wid_t wid;\n\tint focused;\n};\n\nstruct yutani_msg_window_mouse_event {\n\tyutani_wid_t wid;\n\tint32_t new_x;\n\tint32_t new_y;\n\tint32_t old_x;\n\tint32_t old_y;\n\tuint8_t buttons;\n\tuint8_t command;\n\tuint8_t modifiers;\n};\n\nstruct yutani_msg_mouse_event {\n\tyutani_wid_t wid;\n\tmouse_device_packet_t event;\n\tint32_t type;\n};\n\nstruct yutani_msg_flip_region {\n\tyutani_wid_t wid;\n\tint32_t x;\n\tint32_t y;\n\tint32_t width;\n\tint32_t height;\n};\n\nstruct yutani_msg_window_resize {\n\tyutani_wid_t wid;\n\tuint32_t width;\n\tuint32_t height;\n\tuint32_t bufid;\n\tuint32_t flags;\n};\n\nstruct yutani_msg_window_advertise {\n\tyutani_wid_t wid;\n\tuint32_t flags; /* Types, focused, etc. */\n\tuint32_t icon;  /* Icon offset in strings[] */\n\tuint32_t bufid;\n\tuint32_t width;\n\tuint32_t height;\n\tuint32_t size;\n\tchar strings[];\n};\n\nstruct yutani_msg_window_focus {\n\tyutani_wid_t wid;\n};\n\nstruct yutani_msg_key_bind {\n\tkbd_key_t key;\n\tkbd_mod_t modifiers;\n\tint response;\n};\n\nstruct yutani_msg_window_drag_start {\n\tyutani_wid_t wid;\n};\n\nstruct yutani_msg_window_update_shape {\n\tyutani_wid_t wid;\n\tint set_shape;\n};\n\nstruct yutani_msg_window_warp_mouse {\n\tyutani_wid_t wid;\n\tint32_t x;\n\tint32_t y;\n};\n\nstruct yutani_msg_window_show_mouse {\n\tyutani_wid_t wid;\n\tint32_t show_mouse;\n};\n\ntypedef enum {\n\tSCALE_AUTO,\n\n\tSCALE_UP,\n\tSCALE_DOWN,\n\tSCALE_LEFT,\n\tSCALE_RIGHT,\n\n\tSCALE_UP_LEFT,\n\tSCALE_UP_RIGHT,\n\tSCALE_DOWN_LEFT,\n\tSCALE_DOWN_RIGHT,\n\n\tSCALE_NONE,\n} yutani_scale_direction_t;\n\nstruct yutani_msg_window_resize_start {\n\tyutani_wid_t wid;\n\tyutani_scale_direction_t direction;\n};\n\nstruct yutani_msg_special_request {\n\tyutani_wid_t wid;\n\tuint32_t request;\n};\n\nstruct yutani_msg_clipboard {\n\tuint32_t size;\n\tchar content[];\n};\n\nstruct yutani_msg_window_panel_size {\n\tyutani_wid_t wid;\n\tint32_t x;\n\tint32_t y;\n\tint32_t w;\n\tint32_t h;\n};\n\nstruct yutani_msg_window_set_parent {\n\tyutani_wid_t wid;\n\tyutani_wid_t parent_wid;\n};\n\n/* Magic value */\n#define YUTANI_MSG__MAGIC 0xABAD1DEA\n\n/* Client messages */\n#define YUTANI_MSG_HELLO               0x00000001\n#define YUTANI_MSG_WINDOW_NEW          0x00000002\n#define YUTANI_MSG_FLIP                0x00000003\n#define YUTANI_MSG_KEY_EVENT           0x00000004\n#define YUTANI_MSG_MOUSE_EVENT         0x00000005\n#define YUTANI_MSG_WINDOW_MOVE         0x00000006\n#define YUTANI_MSG_WINDOW_CLOSE        0x00000007\n#define YUTANI_MSG_WINDOW_SHOW         0x00000008\n#define YUTANI_MSG_WINDOW_HIDE         0x00000009\n#define YUTANI_MSG_WINDOW_STACK        0x0000000A\n#define YUTANI_MSG_WINDOW_FOCUS_CHANGE 0x0000000B\n#define YUTANI_MSG_WINDOW_MOUSE_EVENT  0x0000000C\n#define YUTANI_MSG_FLIP_REGION         0x0000000D\n#define YUTANI_MSG_WINDOW_NEW_FLAGS    0x0000000E\n\n#define YUTANI_MSG_RESIZE_REQUEST      0x00000010\n#define YUTANI_MSG_RESIZE_OFFER        0x00000011\n#define YUTANI_MSG_RESIZE_ACCEPT       0x00000012\n#define YUTANI_MSG_RESIZE_BUFID        0x00000013\n#define YUTANI_MSG_RESIZE_DONE         0x00000014\n\n#define YUTANI_MSG_WINDOW_MOVE_RELATIVE 0x00000015\n#define YUTANI_MSG_WINDOW_SET_PARENT    0x00000016\n\n/* Some session management / de stuff */\n#define YUTANI_MSG_WINDOW_ADVERTISE    0x00000020\n#define YUTANI_MSG_SUBSCRIBE           0x00000021\n#define YUTANI_MSG_UNSUBSCRIBE         0x00000022\n#define YUTANI_MSG_NOTIFY              0x00000023\n#define YUTANI_MSG_QUERY_WINDOWS       0x00000024\n#define YUTANI_MSG_WINDOW_FOCUS        0x00000025\n#define YUTANI_MSG_WINDOW_DRAG_START   0x00000026\n#define YUTANI_MSG_WINDOW_WARP_MOUSE   0x00000027\n#define YUTANI_MSG_WINDOW_SHOW_MOUSE   0x00000028\n#define YUTANI_MSG_WINDOW_RESIZE_START 0x00000029\n#define YUTANI_MSG_WINDOW_PANEL_SIZE   0x0000002a\n\n#define YUTANI_MSG_SESSION_END         0x00000030\n\n#define YUTANI_MSG_KEY_BIND            0x00000040\n\n#define YUTANI_MSG_WINDOW_UPDATE_SHAPE 0x00000050\n\n#define YUTANI_MSG_CLIPBOARD           0x00000060\n\n#define YUTANI_MSG_GOODBYE             0x000000F0\n\n/* Special request (eg. one-off single-shot requests like \"please maximize me\" */\n#define YUTANI_MSG_SPECIAL_REQUEST     0x00000100\n\n/* Server responses */\n#define YUTANI_MSG_WELCOME             0x00010001\n#define YUTANI_MSG_WINDOW_INIT         0x00010002\n\n/*\n * YUTANI_ZORDER\n *\n * Specifies which stack set a window should appear in.\n */\n#define YUTANI_ZORDER_MAX     0xFFFF\n#define YUTANI_ZORDER_TOP     0xFFFF\n#define YUTANI_ZORDER_MENU    0xFFFE\n#define YUTANI_ZORDER_OVERLAY 0xFFED\n#define YUTANI_ZORDER_BOTTOM  0x0000\n\n/*\n * YUTANI_MOUSE_BUTTON\n *\n * Button specifiers. Multiple specifiers may be set.\n */\n#define YUTANI_MOUSE_BUTTON_LEFT   0x01\n#define YUTANI_MOUSE_BUTTON_RIGHT  0x02\n#define YUTANI_MOUSE_BUTTON_MIDDLE 0x04\n#define YUTANI_MOUSE_SCROLL_UP     0x10\n#define YUTANI_MOUSE_SCROLL_DOWN   0x20\n\n/*\n * YUTANI_MOUSE_STATE\n *\n * The mouse has for effective states internally:\n *\n * NORMAL: The mouse is performing normally.\n * MOVING: The mouse is engaged in moving a window.\n * DRAGGING: The mouse is down and sending drag events.\n * RESIZING: The mouse is engaged in resizing a window.\n */\n#define YUTANI_MOUSE_STATE_NORMAL     0\n#define YUTANI_MOUSE_STATE_MOVING     1\n#define YUTANI_MOUSE_STATE_DRAGGING   2\n#define YUTANI_MOUSE_STATE_RESIZING   3\n#define YUTANI_MOUSE_STATE_ROTATING   4\n\n/*\n * YUTANI_MOUSE_EVENT\n *\n * Mouse events have different types.\n *\n * Most of these should be self-explanatory.\n *\n * CLICK: A down-up click has occured.\n * DRAG: The mouse is down and moving.\n * RAISE: A mouse button was released.\n * DOWN: A mouse button has been pressed.\n * MOVE: The mouse has moved without a mouse button pressed.\n * LEAVE: The mouse has left the given window.\n * ENTER: The mouse has entered the given window.\n */\n#define YUTANI_MOUSE_EVENT_CLICK 0\n#define YUTANI_MOUSE_EVENT_DRAG  1\n#define YUTANI_MOUSE_EVENT_RAISE 2\n#define YUTANI_MOUSE_EVENT_DOWN  3\n#define YUTANI_MOUSE_EVENT_MOVE  4\n#define YUTANI_MOUSE_EVENT_LEAVE 5\n#define YUTANI_MOUSE_EVENT_ENTER 6\n\n/*\n * YUTANI_MOUSE_EVENT_TYPE\n *\n * (For mouse drivers)\n *\n * RELATIVE: Mouse positions are relative to the previous reported location.\n * ABSOLUTE: Mouse positions are in absolute coordinates.\n */\n#define YUTANI_MOUSE_EVENT_TYPE_RELATIVE 0\n#define YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE 1\n\n/*\n * YUTANI_KEY_MODIFIER\n *\n * These are sent with mouse events. The LEFT and RIGHT\n * version are specific to those keys. The non-LEFT/RIGHT\n * versions are masks that can match either key.\n *\n * Must match with the <toaru/kbd.h> definitions.\n */\n#define YUTANI_KEY_MODIFIER_LEFT_CTRL    0x01\n#define YUTANI_KEY_MODIFIER_LEFT_SHIFT   0x02\n#define YUTANI_KEY_MODIFIER_LEFT_ALT     0x04\n#define YUTANI_KEY_MODIFIER_LEFT_SUPER   0x08\n#define YUTANI_KEY_MODIFIER_RIGHT_CTRL   0x10\n#define YUTANI_KEY_MODIFIER_RIGHT_SHIFT  0x20\n#define YUTANI_KEY_MODIFIER_RIGHT_ALT    0x40\n#define YUTANI_KEY_MODIFIER_RIGHT_SUPER  0x80\n#define YUTANI_KEY_MODIFIER_CTRL         0x11\n#define YUTANI_KEY_MODIFIER_SHIFT        0x22\n#define YUTANI_KEY_MODIFIER_ALT          0x44\n#define YUTANI_KEY_MODIFIER_SUPER        0x88\n\n/*\n * YUTANI_BIND\n *\n * Used to control keyboard binding modes.\n *\n * PASSTHROUGH: The key event will continue to the window that would have normally received.\n * STEAL: The key event will not be passed to the next window and is stolen by the bound window.\n */\n#define YUTANI_BIND_PASSTHROUGH 0\n#define YUTANI_BIND_STEAL       1\n\n/*\n * YUTANI_SHAPE_THRESHOLD\n *\n * Used with yutani_window_update_shape to set the alpha threshold for window shaping.\n * All windows are shaped based on their transparency (alpha channel). The default\n * mode is NONE - meaning the alpha channel is ignored.\n *\n * NONE:  The window is always clickable, regardless of alpha transparency.\n * CLEAR: Only completely clear (alpha = 0) regions will pass through.\n * HALF:  Threshold of 50% - alpha values below 127 will pass through. Good for most cases.\n * ANY:   Any amount of alpha transparency will pass through - only fully opaque regions are kept.\n * PASSTHROUGH: All clicks pass through. Useful for tooltips / overlays.\n */\n#define YUTANI_SHAPE_THRESHOLD_NONE        0\n#define YUTANI_SHAPE_THRESHOLD_CLEAR       1\n#define YUTANI_SHAPE_THRESHOLD_HALF        127\n#define YUTANI_SHAPE_THRESHOLD_ANY         255\n#define YUTANI_SHAPE_THRESHOLD_PASSTHROUGH 256\n\n/*\n * YUTANI_CURSOR_TYPE\n *\n * Used with SHOW_MOUSE to set the cursor type for this window.\n * Note that modifications made to the cursor will only display\n * while it the current window is active and that cursor settings\n * are per-window, not per-application.\n *\n * HIDE:              Disable the mouse cursor. Useful for games.\n * NORMAL:            The normal arrow cursor.\n * DRAG:              A 4-directional arrow.\n * RESIZE_VERTICAL:   An up-down arrow / resize indicator.\n * RESIZE_HORIZONTAL: A left-right arrow / resize indicator.\n * RESIZE_UP_DOWN:    A diagonal ＼-shaped arrow.\n * RESIZE_DOWN_UP:    A diagonal ／-shaped arrow.\n *\n * RESET: If the cursor was previously hidden, hide it again.\n *        Otherwise, show the normal cursor. Allows for decorator\n *        to set resize cursors without having to know if a window\n *        had set the default mode to HIDE.\n */\n#define YUTANI_CURSOR_TYPE_RESET            -1\n#define YUTANI_CURSOR_TYPE_HIDE              0\n#define YUTANI_CURSOR_TYPE_NORMAL            1\n#define YUTANI_CURSOR_TYPE_DRAG              2\n#define YUTANI_CURSOR_TYPE_RESIZE_VERTICAL   3\n#define YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL 4\n#define YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN    5\n#define YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP    6\n#define YUTANI_CURSOR_TYPE_POINT             7\n#define YUTANI_CURSOR_TYPE_IBEAM             8\n\n/*\n * YUTANI_WINDOW_FLAG\n *\n * Flags for new windows describing how the window\n * should be created.\n */\n#define YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS   (1 << 0)\n#define YUTANI_WINDOW_FLAG_DISALLOW_DRAG    (1 << 1)\n#define YUTANI_WINDOW_FLAG_DISALLOW_RESIZE  (1 << 2)\n#define YUTANI_WINDOW_FLAG_ALT_ANIMATION    (1 << 3)\n#define YUTANI_WINDOW_FLAG_DIALOG_ANIMATION (1 << 4)\n#define YUTANI_WINDOW_FLAG_NO_ANIMATION     (1 << 5)\n#define YUTANI_WINDOW_FLAG_BLUR_BEHIND      (1 << 8)\n#define YUTANI_WINDOW_FLAG_PARENT_WID       (1 << 9)\n\n/* YUTANI_SPECIAL_REQUEST\n *\n * Special one-off single-shot request messages.\n */\n#define YUTANI_SPECIAL_REQUEST_MAXIMIZE     1\n#define YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE 2\n#define YUTANI_SPECIAL_REQUEST_MINIMIZE     3\n\n#define YUTANI_SPECIAL_REQUEST_CLIPBOARD    10\n\n#define YUTANI_SPECIAL_REQUEST_RELOAD       20\n\n/*\n * YUTANI_RESIZE\n *\n * Flags provided in resize offers describing the window state.\n */\n#define YUTANI_RESIZE_NORMAL 0x00000000\n#define YUTANI_RESIZE_TILED  0x0000000f\n\n#define YUTANI_RESIZE_TILE_LEFT  0x00000001\n#define YUTANI_RESIZE_TILE_RIGHT 0x00000002\n#define YUTANI_RESIZE_TILE_UP    0x00000004\n#define YUTANI_RESIZE_TILE_DOWN  0x00000008\n\ntypedef struct {\n\tint x;\n\tint y;\n\tunsigned int width;\n\tunsigned int height;\n} yutani_damage_rect_t;\n\nextern yutani_msg_t * yutani_wait_for(yutani_t * y, uint32_t type);\nextern yutani_msg_t * yutani_poll(yutani_t * y);\nextern yutani_msg_t * yutani_poll_async(yutani_t * y);\nextern size_t yutani_query(yutani_t * y);\n\nextern int yutani_msg_send(yutani_t * y, yutani_msg_t * msg);\nextern yutani_t * yutani_context_create(FILE * socket);\nextern yutani_t * yutani_init(void);\nextern yutani_window_t * yutani_window_create(yutani_t * y, int width, int height);\nextern yutani_window_t * yutani_window_create_flags(yutani_t * y, int width, int height, uint32_t flags, ...);\nextern void yutani_flip(yutani_t * y, yutani_window_t * win);\nextern void yutani_window_move(yutani_t * yctx, yutani_window_t * window, int x, int y);\nextern void yutani_window_move_relative(yutani_t * yctx, yutani_window_t * window, yutani_window_t * base, int x, int y);\nextern void yutani_window_set_parent(yutani_t * yctx, yutani_window_t * window, yutani_window_t * parent);\nextern void yutani_close(yutani_t * y, yutani_window_t * win);\nextern void yutani_set_stack(yutani_t *, yutani_window_t *, int);\nextern void yutani_flip_region(yutani_t *, yutani_window_t * win, int32_t x, int32_t y, int32_t width, int32_t height);\nextern void yutani_window_resize(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height);\nextern void yutani_window_resize_offer(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height);\nextern void yutani_window_resize_accept(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height);\nextern void yutani_window_resize_done(yutani_t * yctx, yutani_window_t * window);\nextern void yutani_window_advertise(yutani_t * yctx, yutani_window_t * window, char * name);\nextern void yutani_window_advertise_icon(yutani_t * yctx, yutani_window_t * window, char * name, char * icon);\nextern void yutani_window_panel_size(yutani_t * yctx, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h);\nextern void yutani_subscribe_windows(yutani_t * y);\nextern void yutani_unsubscribe_windows(yutani_t * y);\nextern void yutani_query_windows(yutani_t * y);\nextern void yutani_session_end(yutani_t * y);\nextern void yutani_focus_window(yutani_t * y, yutani_wid_t wid);\nextern void yutani_key_bind(yutani_t * yctx, kbd_key_t key, kbd_mod_t mod, int response);\nextern void yutani_window_drag_start(yutani_t * yctx, yutani_window_t * window);\nextern void yutani_window_drag_start_wid(yutani_t * yctx, yutani_wid_t wid);\nextern void yutani_window_update_shape(yutani_t * yctx, yutani_window_t * window, int set_shape);\nextern void yutani_window_warp_mouse(yutani_t * yctx, yutani_window_t * window, int32_t x, int32_t y);\nextern void yutani_window_show_mouse(yutani_t * yctx, yutani_window_t * window, int32_t show_mouse);\nextern void yutani_window_resize_start(yutani_t * yctx, yutani_window_t * window, yutani_scale_direction_t direction);\nextern void yutani_special_request(yutani_t * yctx, yutani_window_t * window, uint32_t request);\nextern void yutani_special_request_wid(yutani_t * yctx, yutani_wid_t wid, uint32_t request);\nextern void yutani_set_clipboard(yutani_t * yctx, char * content);\nextern FILE * yutani_open_clipboard(yutani_t * yctx);\n\nextern gfx_context_t * init_graphics_yutani(yutani_window_t * window);\nextern gfx_context_t *  init_graphics_yutani_double_buffer(yutani_window_t * window);\nextern void reinit_graphics_yutani(gfx_context_t * out, yutani_window_t * window);\nextern void release_graphics_yutani(gfx_context_t * gfx);\nextern void yutani_internal_refocus(yutani_t * yctx, yutani_window_t * window);\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/unistd.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\n#define _POSIX_VERSION 200809L\n\nextern char **environ;\n\nextern pid_t getpid(void);\nextern pid_t getppid(void);\n\nextern int close(int fd);\n\nextern pid_t fork(void);\n\nextern int execl(const char *path, const char *arg, ...);\nextern int execlp(const char *file, const char *arg, ...);\nextern int execle(const char *path, const char *arg, ...);\nextern int execv(const char *path, char *const argv[]);\nextern int execvp(const char *file, char *const argv[]);\nextern int execvpe(const char *file, char *const argv[], char *const envp[]);\nextern int execve(const char *name, char * const argv[], char * const envp[]);\nextern void _exit(int status);\n\nextern int setuid(uid_t uid);\nextern int setgid(gid_t gid);\n\nextern uid_t getuid(void);\nextern uid_t geteuid(void);\nextern gid_t getgid(void);\nextern gid_t getegid(void);\nextern char * getcwd(char *buf, size_t size);\nextern int pipe(int pipefd[2]);\nextern int dup(int oldfd);\nextern int dup2(int oldfd, int newfd);\n\nextern pid_t tcgetpgrp(int fd);\nextern int tcsetpgrp(int fd, pid_t pgrp);\n\nextern ssize_t write(int fd, const void * buf, size_t count);\nextern ssize_t read(int fd, void * buf, size_t count);\n\nextern int symlink(const char *target, const char *linkpath);\nextern ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);\n\nextern int chdir(const char *path);\n//extern int fchdir(int fd);\nextern int isatty(int fd);\n\nextern unsigned int sleep(unsigned int seconds);\nextern int usleep(useconds_t usec);\nextern off_t lseek(int fd, off_t offset, int whence);\n\nextern int access(const char * pathname, int mode);\n\nextern int getopt(int argc, char * const argv[], const char * optstring);\n\nextern char * optarg;\nextern int optind, opterr, optopt;\n\nextern int unlink(const char * pathname);\n\n/* Unimplemented stubs */\nextern int rmdir(const char *pathname); /* TODO  rm probably just works */\nextern int chown(const char * pathname, uid_t owner, gid_t group);\nextern int fchown(int fd, uid_t owner, gid_t group);\n\nextern char * getlogin(void);\nextern char * ttyname(int fd);\nextern int ttyname_r(int fd, char * buf, size_t buflen);\n\n#define STDIN_FILENO 0\n#define STDOUT_FILENO 1\n#define STDERR_FILENO 2\n\n#define SEEK_SET 0\n#define SEEK_CUR 1\n#define SEEK_END 2\n\n#define F_OK 0\n#define R_OK 4\n#define W_OK 2\n#define X_OK 1\n\nextern int gethostname(char * name, size_t len);\nextern int sethostname(const char * name, size_t len);\n\nextern pid_t setsid(void);\nextern int setpgid(pid_t, pid_t);\nextern pid_t getpgid(pid_t);\nextern pid_t getpgrp(void);\n\nextern unsigned int alarm(unsigned int seconds);\n\n#ifndef intptr_t\n# if defined(__PTRDIFF_TYPE__)\ntypedef signed __PTRDIFF_TYPE__ intptr_t;\n# else\ntypedef signed long intptr_t;\n# endif\n#endif\nextern void *sbrk(intptr_t increment);\n\nextern void sync(void);\nextern int truncate(const char *, off_t);\nextern int ftruncate(int, off_t);\n\n#define _PC_PATH_MAX 1\nextern long pathconf(const char *path, int name);\n\nextern int getgroups(int size, gid_t list[]);\n\nssize_t pread(int fd, void *buf, size_t count, off_t offset);\nssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/utime.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <sys/types.h>\n\n_Begin_C_Header\n\nstruct utimbuf {\n    time_t actime;\n    time_t modtime;\n};\n\nextern int utime(const char *filename, const struct utimbuf *times);\n\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/va_list.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\n\ntypedef __builtin_va_list va_list;\n#define va_start(ap,last) __builtin_va_start(ap, last)\n#define va_end(ap) __builtin_va_end(ap)\n#define va_arg(ap,type) __builtin_va_arg(ap,type)\n#define va_copy(dest, src) __builtin_va_copy(dest,src)\n\n_End_C_Header\n\n"
  },
  {
    "path": "base/usr/include/wait.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n\n_Begin_C_Header\nint waitpid(int pid, int *status, int options);\nint wait(int *status);\n_End_C_Header\n"
  },
  {
    "path": "base/usr/include/wchar.h",
    "content": "#pragma once\n\n#include <_cheader.h>\n#include <stddef.h>\n\n_Begin_C_Header\nextern int wcwidth(wchar_t c);\nextern wchar_t * wcsncpy(wchar_t * dest, const wchar_t * src, size_t n);\nextern size_t wcslen(const wchar_t * s);\nextern int wcscmp(const wchar_t *s1, const wchar_t *s2);\nextern wchar_t * wcscat(wchar_t *dest, const wchar_t *src);\nextern wchar_t * wcstok(wchar_t * str, const wchar_t * delim, wchar_t ** saveptr);\nextern size_t wcsspn(const wchar_t * wcs, const wchar_t * accept);\nextern wchar_t *wcspbrk(const wchar_t *wcs, const wchar_t *accept);\nextern wchar_t * wcschr(const wchar_t *wcs, wchar_t wc);\nextern wchar_t * wcsrchr(const wchar_t *wcs, wchar_t wc);\nextern wchar_t * wcsncat(wchar_t *dest, const wchar_t * src, size_t n);\nextern int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n);\nextern wchar_t * wcscpy(wchar_t * dest, const wchar_t * src);\nextern unsigned long int wcstoul(const wchar_t *nptr, wchar_t **endptr, int base);\nextern unsigned long long int wcstoull(const char *nptr, wchar_t **endptr, int base);\nextern long int wcstol(const wchar_t *nptr, wchar_t **endptr, int base);\nextern long long int wcstoll(const wchar_t *nptr, wchar_t **endptr, int base);\n\ntypedef unsigned int wint_t;\n_End_C_Header\n"
  },
  {
    "path": "base/usr/share/bim/site/__init__.krk",
    "content": "import kuroko\n\nkuroko.module_paths.append('/lib/kuroko/')\n"
  },
  {
    "path": "base/usr/share/bim/syntax/__init__.krk",
    "content": "from bim import SyntaxState, bindHighlighter\n\ndef bind(cls):\n    bindHighlighter(cls)\n    return cls\n\nclass Highlighter(SyntaxState):\n\n    def matchPrefix(prefix):\n        let i = 0\n        while True:\n            if i == len(prefix): return True\n            if prefix[i] != self[i]: return False\n            if not self[i]: return False\n            i++\n\n    def paintSingleString():\n        self.paint(1, self.FLAG_STRING)\n        while self[0]:\n            if self[0] == '\\\\' and self[1] == \"'\":\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                return\n            else if self[0] == '\\\\':\n                self.paint(2, self.FLAG_ESCAPE)\n            else:\n                self.paint(1, self.FLAG_STRING)\n\n    def paintSimpleString():\n        self.paint(1, self.FLAG_STRING)\n        while self[0]:\n            if self[0] == '\\\\' and self[1] == '\"':\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == '\"':\n                self.paint(1, self.FLAG_STRING)\n                return\n            else if self[0] == '\\\\':\n                self.paint(2, self.FLAG_ESCAPE)\n            else:\n                self.paint(1, self.FLAG_STRING)\n\n    @staticmethod\n    def isalpha(c):\n        c = c if isinstance(c,int) else ord(c) if c else -1\n        return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z'))\n\n    @staticmethod\n    def isalnum(c):\n        c = c if isinstance(c,int) else ord(c) if c else -1\n        return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) or (c >= ord('0') and c <= ord('9'))\n"
  },
  {
    "path": "base/usr/share/bim/syntax/bash.krk",
    "content": "from syntax import Highlighter, bind\n\nclass BashHighlighter(Highlighter):\n    name = \"bash\"\n    extensions = ('.sh','.bash','.bashrc')\n\n    keywords = [\n        'if','then','else','elif','fi','case','esac','for','coproc',\n        'select','while','until','do','done','in','function','time',\n        'exit','return','source','export','alias','complete','shopt',\n        'local','eval','echo','cd','pushd','popd','printf','sed',\n        'rm','mv'\n    ]\n\n    def popState(self, state):\n        let newState = state // 100\n        return newState * 10\n\n    def pushState(self, state, newState):\n        return state * 10 + newState\n\n    def paintTick(self, outState):\n        let last = None\n        while self[0] != None:\n            if self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                return self.popState(outState)\n            else if last == '\\\\':\n                self.paint(1, self.FLAG_STRING)\n                last = None\n            else if self[0] != None:\n                last = self[0]\n                self.paint(1, self.FLAG_STRING)\n        return outState\n\n    def paintBracedVariable(self):\n        while self[0] != None:\n            if self[0] == '}':\n                self.paint(1, self.FLAG_NUMERAL)\n                return 0\n            self.paint(1, self.FLAG_NUMERAL)\n        return 0\n\n    def specialVariable(self, c):\n        return c == '@' or c == '?'\n\n    def paintString(self, term, outState, color):\n        let last = None\n        while self[0] != None:\n            if last != '\\\\' and self[0] == term:\n                self.paint(1, color)\n                return self.popState(outState)\n            else if last == '\\\\':\n                self.paint(1, color)\n                last = None\n            else if term != '`' and self[0] == '`':\n                self.paint(1, self.FLAG_ESCAPE)\n                outState = self.paintString('`', self.pushState(outState, 20), self.FLAG_ESCAPE)\n            else if term != ')' and self[0] == '$' and self[1] == '(':\n                self.paint(2, self.FLAG_TYPE)\n                outState = self.paintString(')', self.pushState(outState, 30), self.FLAG_TYPE)\n            else if self[0] == '$' and self[1] == '{':\n                self.paint(2, self.FLAG_NUMERAL)\n                self.paintBracedVariable()\n            else if self[0] == '$':\n                self.paint(1, self.FLAG_NUMERAL)\n                if self.specialVariable(self[0]):\n                    self.paint(1, self.FLAG_NUMERAL)\n                    continue\n                while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL)\n            else if term != '\"' and self[0] == '\"':\n                self.paint(1, self.FLAG_STRING)\n                outState = self.paintString('\"', self.pushState(outState, 40), self.FLAG_STRING)\n            else if term != '\"' and self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                outState = self.paintTick(outState)\n            else if self[0] != -1:\n                last = self[0]\n                self.paint(1, color)\n        return outState\n\n    def calculate(self):\n        if self.state < 1:\n            if self[0] == '#' and self[-1] != '\\\\':\n                while self[0] != None:\n                    if self.commentBuzzwords(): continue\n                    self.paint(1, self.FLAG_COMMENT)\n                return None\n            else if self[0] == \"'\" and self[-1] != '\\\\':\n                self.paint(1, self.FLAG_STRING)\n                return self.paintTick(10)\n            else if self[0] == '`' and self[-1] != '\\\\':\n                self.paint(1, self.FLAG_ESCAPE)\n                return self.paintString('`', 20, self.FLAG_ESCAPE)\n            else if self[0] == '$' and self[1] == '(' and self[-1] != '\\\\':\n                self.paint(2, self.FLAG_TYPE)\n                return self.paintString(')', 30, self.FLAG_TYPE)\n            else if self[0] == '\"' and self[-1] != '\\\\':\n                self.paint(1, self.FLAG_STRING)\n                return self.paintString('\"', 40, self.FLAG_STRING)\n            else if self[0] == '$' and self[1] == '{' and self[-1] != '\\\\':\n                self.paint(2, self.FLAG_NUMERAL)\n                self.paintBracedVariable()\n                return 0\n            else if self[0] == '$'and self[-1] != '\\\\':\n                self.paint(1, self.FLAG_NUMERAL)\n                if self.specialVariable(self[0]):\n                    self.paint(1, self.FLAG_NUMERAL)\n                    return 0\n                while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                return 0\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self[0] == ';':\n                self.paint(1, self.FLAG_KEYWORD)\n                return 0\n            else if self.cKeywordQualifier(self[0]):\n                for i = 0; self[i] != None; i++:\n                    if self[i] == ' ': break\n                    if self[i] == '=':\n                        self.paint(i, self.FLAG_TYPE)\n                        self.skip()\n                        return 0\n                for i = 0; self[i] != None; i++:\n                    if self[i] == '(':\n                        self.paint(i, self.FLAG_TYPE)\n                        return 0\n                    if not self.cKeywordQualifier(self[i]) and self[i] != '-' and self[i] != ' ':\n                        break\n                self.skip()\n                return 0\n            else if self[0] != None:\n                self.skip()\n                return 0\n        else if self.state >= 10:\n            let outState = self.state\n            while self[0] != None:\n                let s = (outState // 10) % 10\n                if s == 1: outState = self.paintString(\"'\", outState, self.FLAG_STRING)\n                else if s == 2: outState = self.paintString('`', outState, self.FLAG_ESCAPE)\n                else if s == 3: outState = self.paintString(')', outState, self.FLAG_TYPE)\n                else if s == 4: outState = self.paintString('\"', outState, self.FLAG_STRING)\n                else if not s: return None\n            return outState\n        return None\n\nbind(BashHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/biminfo.krk",
    "content": "from syntax import Highlighter, bind\n\nclass BiminfoHighlighter(Highlighter):\n    name = 'biminfo'\n    extensions = ('.biminfo',)\n    def calculate(self):\n        if self.i == 0:\n            if self[0] == '#':\n                self.paint(-1, self.FLAG_COMMENT)\n            else if self[0] == '>':\n                self.paint(1, self.FLAG_KEYWORD)\n                while self[0] != ' ':\n                    self.paint(1, self.FLAG_TYPE)\n                self.skip()\n                self.paint(-1, self.FLAG_NUMERAL)\n        return None\n\nbind(BiminfoHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/c.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.doxygen import tryDoxygenComment\n\ndef makeit():\n    let FLAG_NONE = Highlighter.FLAG_NONE\n    let FLAG_KEYWORD = Highlighter.FLAG_KEYWORD\n    let FLAG_STRING = Highlighter.FLAG_STRING\n    let FLAG_COMMENT = Highlighter.FLAG_COMMENT\n    let FLAG_TYPE = Highlighter.FLAG_TYPE\n    let FLAG_PRAGMA = Highlighter.FLAG_PRAGMA\n    let FLAG_NUMERAL = Highlighter.FLAG_NUMERAL\n    let FLAG_ERROR = Highlighter.FLAG_ERROR\n    let FLAG_DIFFPLUS = Highlighter.FLAG_DIFFPLUS\n    let FLAG_DIFFMINUS = Highlighter.FLAG_DIFFMINUS\n    let FLAG_NOTICE = Highlighter.FLAG_NOTICE\n    let FLAG_BOLD = Highlighter.FLAG_BOLD\n    let FLAG_LINK = Highlighter.FLAG_LINK\n    let FLAG_ESCAPE = Highlighter.FLAG_ESCAPE\n    let FLAG_EXTRA = Highlighter.FLAG_EXTRA\n    let FLAG_SPECIAL = Highlighter.FLAG_SPECIAL\n    let FLAG_UNDERLINE = Highlighter.FLAG_UNDERLINE\n\n    class CHighlighter(Highlighter):\n        name = 'c'\n        extensions = ('.c','.h','.cpp','.hpp','.c++','.h++','.cc','.hh')\n\n        doxygenDocstrings = False\n\n        keywords = [\n            \"while\",\"if\",\"for\",\"continue\",\"return\",\"break\",\"switch\",\"case\",\"sizeof\",\n            \"struct\",\"union\",\"typedef\",\"do\",\"default\",\"else\",\"goto\",\n            \"alignas\",\"alignof\",\"offsetof\",\"asm\",\"__asm__\",\n            \"public\",\"private\",\"class\",\"using\",\"namespace\",\"virtual\",\"override\",\"protected\",\n            \"template\",\"typename\",\"static_cast\",\"throw\"\n        ]\n\n        types = [\n            \"static\",\"int\",\"char\",\"short\",\"float\",\"double\",\"void\",\"unsigned\",\"volatile\",\"const\",\n            \"register\",\"long\",\"inline\",\"restrict\",\"enum\",\"auto\",\"extern\",\"bool\",\"complex\",\n            # stdint stuff\n            \"uint8_t\",\"uint16_t\",\"uint32_t\",\"uint64_t\",\n            \"int8_t\",\"int16_t\",\"int32_t\",\"int64_t\",\n            \"ssize_t\",\"size_t\",\"uintptr_t\",\"intptr_t\",\n            # Extra stuff\n            \"constexpr\",\"FILE\",\"__volatile__\",\n            # sys/types stuff\n            \"gid_t\",\"uid_t\",\"dev_t\",\"ino_t\",\"mode_t\",\"caddr_t\",\"off_t\",\"time_t\",\"pid_t\",\n        ]\n\n        special = [\n            \"NULL\",\n            \"stdin\",\"stdout\",\"stderr\",\n            \"STDIN_FILENO\",\"STDOUT_FILENO\",\"STDERR_FILENO\"\n        ]\n\n        preprocessor_base_state = 5\n\n        def paintCString(self):\n            let last = None\n            while None not in self:\n                if last != '\\\\' and '\"' in self:\n                    self[1] = FLAG_STRING\n                    return 0\n                else if '\\\\' in self and not self[1]:\n                    self[1] = FLAG_ESCAPE\n                    return 4\n                else if '\\\\' in self and self[1] in 'abfnrtv?\\\\':\n                    self[2] = FLAG_ESCAPE\n                    last = None\n                else if '\\\\'  in self and self[1] in '01234567':\n                    self[2] = FLAG_ESCAPE\n                    if '01234567' in self:\n                        self[1] = FLAG_ESCAPE\n                        if '01234567' in self:\n                            self[1] = FLAG_ESCAPE\n                    last = None\n                else if '%' in self:\n                    self[1] = FLAG_ESCAPE\n                    if '%' in self:\n                        self[1] = FLAG_ESCAPE\n                    else:\n                        while '-#*0+' in self: self[1] = FLAG_ESCAPE\n                        while self.isdigit(self[0]): self[1] = FLAG_ESCAPE\n                        if '.' in self:\n                            self[1] = FLAG_ESCAPE\n                            if '%'  in self: self[1] = FLAG_ESCAPE\n                            else: while self.isdigit(self[0]): self[1] = FLAG_ESCAPE\n                        while 'lz' in self: self[1] = FLAG_ESCAPE\n                        if '\"\\\\' in self: continue\n                        self[1] = FLAG_ESCAPE\n                else if '\\\\' in self and self[1] == 'x':\n                    self[2] = FLAG_ESCAPE\n                    while self.isxdigit(self[0]): self[1] = FLAG_ESCAPE\n                else if self.doxygenDocstrings and tryDoxygenComment(self, FLAG_STRING):\n                    continue\n                else:\n                    last = self[0]\n                    self[1] = FLAG_STRING\n            return 0\n\n        def paintCChar(self):\n            self[1] = FLAG_NUMERAL\n            let last = None\n            while None not in self:\n                if last != '\\\\' and \"'\" in self:\n                    self[1] = FLAG_NUMERAL\n                    return\n                else if last == '\\\\' and '\\\\' in self:\n                    self[1] = FLAG_NUMERAL\n                    last = None\n                else:\n                    last = self[0]\n                    self[1] = FLAG_NUMERAL\n\n        def paintCComment(self):\n            let last = None\n            while None not in self:\n                if self.commentBuzzwords(): continue\n                if tryDoxygenComment(self): continue\n                else if last == '*' and '/' in self:\n                    self[1] = FLAG_COMMENT\n                    return 0\n                else:\n                    last = self[0]\n                    self[1] = FLAG_COMMENT\n            return 1\n\n        def paintCPragma(self):\n            while None not in self:\n                if '\"' in self:\n                    self[1] = FLAG_STRING\n                    let result = self.paintCString()\n                    if result != 0: return result\n                else if \"'\" in self:\n                    self.paintCChar()\n                else if '\\\\' in self and self[1] == None:\n                    self[1] = FLAG_PRAGMA\n                    return 2\n                else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier):\n                    continue\n                else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier):\n                    continue\n                else if '/' in self and self[1] == '/':\n                    self.paintComment()\n                    return None\n                else if '/' in self and self[1] == '*':\n                    if self.paintCComment() == 1: return 3\n                    continue\n                else:\n                    self[1] = FLAG_PRAGMA\n            return 0\n\n        def paintCNumeral(self):\n            if '0' in self and (self[1] == 'x' or self[1] == 'X'):\n                self[2] = FLAG_NUMERAL\n                while self.isxdigit(self[0]): self[1] = FLAG_NUMERAL\n            else if '0' in self and self[1] == '.':\n                self[2] = FLAG_NUMERAL\n                while self.isdigit(self[0]): self[1] = FLAG_NUMERAL\n                if 'f' in self: self[1] = FLAG_NUMERAL\n                return 0\n            else if '0' in self:\n                self[1] = FLAG_NUMERAL\n                while '01234567' in self: self[1] = FLAG_NUMERAL\n            else:\n                while self.isdigit(self[0]): self[1] = FLAG_NUMERAL\n                if '.' in self:\n                    self[1] = FLAG_NUMERAL\n                    while self.isdigit(self[0]): self[1] = FLAG_NUMERAL\n                    if 'f' in self: self[1] = FLAG_NUMERAL\n                    return 0\n            while 'uUlL' in self: self[1] = FLAG_NUMERAL\n            return 0\n\n        def calculate(self):\n            let cond = self % 0\n            if cond <= 0:\n                while None not in self:\n                    if '#' in self:\n                        if any(self[-i-1] != ' ' and self[-i-1] != '\\t' for i in range(self.i)):\n                            self.skip()\n                            continue\n                        self[1] = FLAG_PRAGMA\n                        while ' ' in self:\n                            self[1] = FLAG_PRAGMA\n                        if self.matchAndPaint(\"include\", FLAG_PRAGMA, self.cKeywordQualifier):\n                            while ' ' in self:\n                                self[1] = FLAG_PRAGMA\n                            if '<' in self:\n                                self[1] = FLAG_STRING\n                                while '>' not in self and None not in self:\n                                    self[1] = FLAG_STRING\n                                if None not in self:\n                                    self[1] = FLAG_STRING\n                        else if self.matchAndPaint(\"if\", FLAG_PRAGMA, self.cKeywordQualifier):\n                            if ' ' in self and self[1] == '0' and self[2] == None:\n                                self[2] = FLAG_COMMENT\n                                self.rewind(6)\n                                self[-1] = FLAG_COMMENT\n                                return self.preprocessor_base_state\n                        else if self.matchAndPaint(\"else\", FLAG_PRAGMA, self.cKeywordQualifier):\n                            # Do nothing?\n                        return self.paintCPragma()\n                    else if '/' in self and self[1] == '/':\n                        self.paintComment()\n                    else if '/' in self and self[1] == '*':\n                        self[2] = FLAG_COMMENT\n                        return self.paintCComment()\n                    else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier):\n                        continue\n                    else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier):\n                        continue\n                    else if self.findKeywords(self.special, FLAG_NUMERAL, self.cKeywordQualifier):\n                        continue\n                    else if '\"' in self:\n                        self[1] = FLAG_STRING\n                        return self.paintCString()\n                    else if \"'\" in self:\n                        self.paintCChar()\n                    else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                        self.paintCNumeral()\n                    else:\n                        self.skip()\n            else if cond == 1:\n                return self.paintCComment()\n            else if cond == 2:\n                return self.paintCPragma()\n            else if cond == 3:\n                if self.paintCComment() == 1:\n                    return 3\n                return self.paintCPragma()\n            else if cond == 4:\n                return self.paintCString()\n            else:\n                while ' ' in self or '\\t' in self: self[1] = FLAG_COMMENT\n                if '#' in self:\n                    self[1] = FLAG_COMMENT\n                    while ' ' in self or '\\t' in self: self[1] = FLAG_COMMENT\n                    if self.findKeywords([\"if\",\"ifdef\",\"ifndef\"], FLAG_COMMENT, self.cKeywordQualifier):\n                        self[-1] = FLAG_COMMENT\n                        return self.state + 1\n                    else if self.findKeywords([\"else\",\"elif\"], FLAG_COMMENT, self.cKeywordQualifier):\n                        self[-1] = FLAG_COMMENT\n                        if self.state == self.preprocessor_base_state: return 0\n                        return self.state\n                    else if self.matchAndPaint(\"endif\", FLAG_COMMENT, self.cKeywordQualifier):\n                        self[-1] = FLAG_COMMENT\n                        if self.state == self.preprocessor_base_state: return 0\n                        return self.state - 1\n                    else:\n                        self[-1] = FLAG_COMMENT\n                        return self.state\n                else:\n                    self[-1] = FLAG_COMMENT\n                    return self.state\n            return None\n    return CHighlighter\n\nlet CHighlighter = makeit()\nbind(CHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/conf.krk",
    "content": "from syntax import Highlighter, bind\n\nclass ConfigHighlighter(Highlighter):\n    name = \"conf\"\n    extensions = ('.conf','.ini','.git/config','.cfg','.properties')\n    spaces = True\n\n    def calculate(self):\n        if self.i == 0:\n            if self[0] == ';':\n                self.paintComment()\n            else if self[0] == '#':\n                self.paintComment()\n            else if self[0] == '[':\n                self.paint(1, self.FLAG_KEYWORD)\n                while self[0] and self[0] != ']':\n                    self.paint(1, self.FLAG_KEYWORD)\n                if self[0] == ']':\n                    self.paint(1, self.FLAG_KEYWORD)\n            else:\n                while self[0] and self[0] != '=':\n                    self.paint(1, self.FLAG_TYPE)\n        return None\n\nbind(ConfigHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/css.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass CSSHighlighter(Highlighter):\n    name = 'css'\n    extensions = ('.css', '.scss')\n    elements = [\n        \"a\",\"abbr\",\"address\",\"area\",\"article\",\"aside\",\"audio\",\n        \"b\",\"base\",\"bdi\",\"bdo\",\"blockquote\",\"body\",\"br\",\"button\",\n        \"canvas\",\"cite\",\"code\",\"col\",\"colgroup\",\"data\",\"datalist\",\n        \"dd\",\"del\",\"details\",\"dfn\",\"dialog\",\"div\",\"dl\",\"dt\",\"em\",\n        \"embed\",\"fieldset\",\"figcaption\",\"figure\",\"footer\",\"form\",\n        \"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\",\"head\",\"header\",\"hr\",\"html\",\n        \"i\",\"iframe\",\"img\",\"input\",\"ins\",\"kbd\",\"label\",\"legend\",\n        \"li\",\"link\",\"main\",\"map\",\"mark\",\"meta\",\"meter\",\"nav\",\n        \"noscript\",\"object\",\"ol\",\"optgroup\",\"option\",\"output\",\n        \"p\",\"param\",\"picture\",\"pre\",\"progress\",\"q\",\"rp\",\"rt\",\n        \"ruby\",\"s\",\"samp\",\"script\",\"section\",\"select\",\"small\",\n        \"source\",\"span\",\"strong\",\"style\",\"sub\",\"summary\",\"sup\",\n        \"svg\",\"table\",\"tbody\",\"td\",\"template\",\"textarea\",\"tfoot\",\n        \"th\",\"thead\",\"time\",\"title\",\"tr\",\"track\",\"u\",\"ul\",\"var\",\n        \"video\",\"wbr\",\"hgroup\",\"*\",\n    ]\n    properties = [\n        \"align-content\",\"align-items\",\"align-self\",\"all\",\"animation\",\n        \"animation-delay\",\"animation-direction\",\"animation-duration\",\n        \"animation-fill-mode\",\"animation-iteration-count\",\"animation-name\",\n        \"animation-play-state\",\"animation-timing-function\",\"backface-visibility\",\n        \"background\",\"background-attachment\",\"background-blend-mode\",\"background-clip\",\n        \"background-color\",\"background-image\",\"background-origin\",\"background-position\",\n        \"background-repeat\",\"background-size\",\"border\",\"border-bottom\",\"border-bottom-color\",\n        \"border-bottom-left-radius\",\"border-bottom-right-radius\",\"border-bottom-style\",\n        \"border-bottom-width\",\"border-collapse\",\"border-color\",\"border-image\",\"border-image-outset\",\n        \"border-image-repeat\",\"border-image-slice\",\"border-image-source\",\"border-image-width\",\n        \"border-left\",\"border-left-color\",\"border-left-style\",\"border-left-width\",\n        \"border-radius\",\"border-right\",\"border-right-color\",\"border-right-style\",\"border-right-width\",\n        \"border-spacing\",\"border-style\",\"border-top\",\"border-top-color\",\"border-top-left-radius\",\n        \"border-top-right-radius\",\"border-top-style\",\"border-top-width\",\"border-width\",\n        \"bottom\",\"box-decoration-break\",\"box-shadow\",\"box-sizing\",\"break-after\",\n        \"break-before\",\"break-inside\",\"caption-side\",\"caret-color\",\"@charset\",\n        \"clear\",\"clip\",\"color\",\"column-count\",\"column-fill\",\"column-gap\",\"column-rule\",\"column-rule-color\",\n        \"column-rule-style\",\"column-rule-width\",\"column-span\",\"column-width\",\"columns\",\"content\",\n        \"counter-increment\",\"counter-reset\",\"cursor\",\"direction\",\"display\",\"empty-cells\",\n        \"filter\",\"flex\",\"flex-basis\",\"flex-direction\",\"flex-flow\",\"flex-grow\",\"flex-shrink\",\n        \"flex-wrap\",\"float\",\"font\",\"@font-face\",\"font-family\",\"font-feature-settings\",\"@font-feature-values\",\n        \"font-kerning\",\"font-language-override\",\"font-size\",\"font-size-adjust\",\"font-stretch\",\"font-style\",\n        \"font-synthesis\",\"font-variant\",\"font-variant-alternates\",\"font-variant-caps\",\"font-variant-east-asian\",\n        \"font-variant-ligatures\",\"font-variant-numeric\",\"font-variant-position\",\"font-weight\",\n        \"grid\",\"grid-area\",\"grid-auto-columns\",\"grid-auto-flow\",\"grid-auto-rows\",\"grid-column\",\n        \"grid-column-end\",\"grid-column-gap\",\"grid-column-start\",\"grid-gap\",\"grid-row\",\"grid-row-end\",\n        \"grid-row-gap\",\"grid-row-start\",\"grid-template\",\"grid-template-areas\",\"grid-template-columns\",\n        \"grid-template-rows\",\"hanging-punctuation\",\"height\",\"hyphens\",\"image-rendering\",\"@import\",\n        \"isolation\",\"justify-content\",\"@keyframes\",\"left\",\"letter-spacing\",\"line-break\",\"line-height\",\n        \"list-style\",\"list-style-image\",\"list-style-position\",\"list-style-type\",\"margin\",\"margin-bottom\",\n        \"margin-left\",\"margin-right\",\"margin-top\",\"max-height\",\"max-width\",\"@media\",\"min-height\",\n        \"min-width\",\"mix-blend-mode\",\"object-fit\",\"object-position\",\"opacity\",\"order\",\"orphans\",\n        \"outline\",\"outline-color\",\"outline-offset\",\"outline-style\",\"outline-width\",\"overflow\",\n        \"overflow-wrap\",\"overflow-x\",\"overflow-y\",\"padding\",\"padding-bottom\",\"padding-left\",\"padding-right\",\n        \"padding-top\",\"page-break-after\",\"page-break-before\",\"page-break-inside\",\"perspective\",\n        \"perspective-origin\",\"pointer-events\",\"position\",\"quotes\",\"resize\",\"right\",\"scroll-behavior\",\n        \"tab-size\",\"table-layout\",\"text-align\",\"text-align-last\",\"text-combine-upright\",\"text-decoration\",\n        \"text-decoration-color\",\"text-decoration-line\",\"text-decoration-style\",\"text-indent\",\"text-justify\",\n        \"text-orientation\",\"text-overflow\",\"text-shadow\",\"text-transform\",\"text-underline-position\",\n        \"top\",\"transform\",\"transform-origin\",\"transform-style\",\"transition\",\"transition-delay\",\n        \"transition-duration\",\"transition-property\",\"transition-timing-function\",\"unicode-bidi\",\n        \"user-select\",\"vertical-align\",\"visibility\",\"white-space\",\"widows\",\"width\",\"word-break\",\n        \"word-spacing\",\"word-wrap\",\"writing-mode\",\"font-decoration\",\n    ]\n    values = [\n        \"inline\",\"block\",\"inline-block\",\"none\",\"oblique\",\n        \"transparent\",\"thin\",\"dotted\",\"sans-serif\",\n        \"rgb\",\"rgba\",\"bold\",\"italic\",\"underline\",\"context-box\",\n        \"monospace\",\"serif\",\"sans-serif\",\"pre-wrap\",\n        \"relative\",\"baseline\",\"hidden\",\"solid\",\"inherit\",\"normal\",\n        \"button\",\"pointer\",\"border-box\",\"default\",\"textfield\",\n        \"collapse\",\"top\",\"bottom\",\"avoid\",\"table-header-group\",\n        \"middle\",\"absolute\",\"rect\",\"left\",\"center\",\"right\",\n        \"ellipsis\",\"nowrap\",\"table\",\"both\",\"uppercase\",\"lowercase\",\"help\",\n        \"static\",\"table-cell\",\"table-column\",\"scroll\",\"touch\",\"auto\",\n        \"not-allowed\",\"inset\",\"url\",\"fixed\",\"translate\",\"alpha\",\"fixed\",\"device-width\",\n        \"table-row\",\n    ]\n    states = [\n        \"focus\",\"active\",\"hover\",\"link\",\"visited\",\"before\",\"after\",\n        \"left\",\"right\",\"root\",\"empty\",\"target\",\"enabled\",\"disabled\",\"checked\",\"invalid\",\n        \"first-child\",\"nth-child\",\"not\",\"last-child\",\n    ]\n    def propertyQualifier(c):\n        if isinstance(c,int):\n            if c <= 0: return False\n            c = chr(c)\n        return self.isalnum(c) or c in '-@*!'\n    suffixes = [\n        'pt','px','pc','em','cm','mm',\n        'ex','in','vw','vh','ch','rem',\n        'vmin','vmax','s'\n    ]\n    def matchSuffixes(suffixes):\n        for s in suffixes:\n            if self.matchPrefix(s):\n                self.paint(len(s), self.FLAG_NUMERAL)\n                return\n    def calculate():\n        if self.state < 1:\n            if self[0] == '/' and self[1] == '*':\n                if CHighlighter.paintCComment(self) == 1: return 1\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                return 0\n            else if self[-1] != '.' and self.findKeywords(self.elements, self.FLAG_KEYWORD, self.propertyQualifier):\n                return 0\n            else if self[-1] != '.' and self.findKeywords(self.properties, self.FLAG_TYPE, self.propertyQualifier):\n                return 0\n            else if self.matchPrefix('-moz-'):\n                self.paint(5, self.FLAG_ESCAPE)\n                while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            else if self.matchPrefix('-webkit-'):\n                self.paint(8, self.FLAG_ESCAPE)\n                while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            else if self.matchPrefix('-ms-'):\n                self.paint(4, self.FLAG_ESCAPE)\n                while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            else if self.matchPrefix('-o-'):\n                self.paint(3, self.FLAG_ESCAPE)\n                while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            else if self[0] == ':':\n                self.skip()\n                if self.findKeywords(self.states, self.FLAG_PRAGMA, self.propertyQualifier): return 0\n                while self[0] and self[0] != ';':\n                    if self.findKeywords(self.values, self.FLAG_NUMERAL, self.propertyQualifier):\n                        continue\n                    else if self[0] == '\"':\n                        self.paintSimpleString()\n                        continue\n                    else if self[0] == \"'\":\n                        self.paintSingleString()\n                        continue\n                    else if self[0] == '{':\n                        self.skip()\n                        return 0\n                    else if self[0] == '#':\n                        self.paint(1, self.FLAG_NUMERAL)\n                        while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                    else if self.isdigit(self[0]):\n                        while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                        if self[0] == '.':\n                            self.paint(1, self.FLAG_NUMERAL)\n                            while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                        if self[0] == '%': self.paint(1, self.FLAG_NUMERAL)\n                        else: self.matchSuffixes(self.suffixes)\n                    else if self.matchAndPaint(\"!important\", self.FLAG_PRAGMA, self.propertyQualifier):\n                        continue\n                    else if self[0]:\n                        self.skip()\n                return 0\n            else if not self[0]:\n                return None\n            else:\n                self.skip()\n            return 0\n        else if self.state == 1:\n            if CHighlighter.paintCComment(self) == 1: return 1\n            return 0\n        return None\n\nbind(CSSHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/ctags.krk",
    "content": "from syntax import Highlighter, bind\n\nclass CtagsHighlighter(Highlighter):\n    name = 'ctags'\n    extensions = ('tags',)\n    def calculate():\n        if self.i == 0:\n            if self[0] == '!':\n                return self.paintComment()\n            while self[0] and self[0] != '\\t': self.paint(1, self.FLAG_TYPE)\n            if self[0] == '\\t': self.skip()\n            while self[0] and self[0] != '\\t': self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == '\\t': self.skip()\n            while self[0] and not (self[0] == ';' and self[1] == '\"'): self.paint(1, self.FLAG_KEYWORD)\n        return None\n\nbind(CtagsHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/diff.krk",
    "content": "from syntax import Highlighter, bind\n\nclass DiffHighlighter(Highlighter):\n    name = 'diff'\n    extensions = ('.patch','.diff')\n    mapping = {\n        '+': Highlighter.FLAG_DIFFPLUS,\n        '-': Highlighter.FLAG_DIFFMINUS,\n        '@': Highlighter.FLAG_TYPE,\n        ' ': Highlighter.FLAG_KEYWORD,\n    }\n    def calculate():\n        if self.i == 0:\n            let flag = self.mapping[self[0]] if self[0] in self.mapping else 0\n            self.paint(-1, flag)\n        return None\n\nbind(DiffHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/dirent.krk",
    "content": "'''\n    For highlighting bim's built-in directory browser.\n'''\nfrom syntax import Highlighter, bind\n\nclass DirentHighlighter(Highlighter):\n    name = 'dirent'\n    extensions = ()\n    def calculate():\n        if self.i == 0:\n            if self[0] == '#':\n                return self.paintComment()\n            else if self[0] == 'd':\n                self.paint(1, self.FLAG_COMMENT)\n                self.paint(-1, self.FLAG_KEYWORD)\n            else if self[0] == 'f':\n                self.paint(1, self.FLAG_COMMENT)\n                self.paint(-1, self.FLAG_NONE)\n        return None\n\nbind(DirentHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/dlang.krk",
    "content": "from syntax import bind\nfrom syntax.c import CHighlighter\n\n@bind\nclass DHighlighter(CHighlighter):\n    name = 'dlang'\n    extensions = ('.d',)\n    keywords = CHighlighter.keywords + [\n        'abstract', 'alias', 'align', 'assert', 'cast', 'dchar',\n        'debug', 'delegate', 'deprecated', 'export',\n        # Why aren't these in the C highlighter for C++?\n        'try', 'catch', 'finally',\n        'foreach', 'foreach_reverse', 'function', 'interface',\n        'mixin', 'module', 'macro', 'with',\n        'scope','unittest','package','typeof','typeid',\n        'pragma','pure','override','import',\n    ]\n    types = CHighlighter.types + [\n        'inout', 'nothrow', 'shared', 'synchronized', 'string',\n        '__gshared', '__traits', '__vector', '__paramaters',\n        'ubyte','uint','ulong','ushort','real','ref','out','in',\n    ]\n    special = [\n        'null','true','false',\n        '__FILE__','__FILE_FULL_PATH__','__MODULE__','__LINE__',\n        '__FUNCTION__','__PRETTY_FUNCTION__',\n    ]\n"
  },
  {
    "path": "base/usr/share/bim/syntax/docker.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.bash import BashHighlighter\n\n@bind\nclass DockerfileHighlighter(BashHighlighter):\n    name = \"docker\"\n    extensions = ('Dockerfile',)\n    spaces = True\n\n    keywords = [\n        'FROM','AS','MAINTAINER','RUN','CMD','COPY','EXPOSE','ADD',\n        'ENTRYPOINT','VOLUME','USER','WORKDIR','ONBUILD','LABEL','ARG',\n        'HEALTHCHECK','SHELL','STOPSIGNAL',\n    ]\n\n    def calculate(self):\n        if self.state < 1 and self.i == 0:\n            if self[0] == '#':\n                self.paintComment()\n            else if self.matchAndPaint('RUN', self.FLAG_KEYWORD, self.cKeywordQualifier):\n                let out = super().calculate()\n                if out == None and self[-1] == '\\\\':\n                    return 1\n                return out\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else:\n                self.skip(1)\n        else:\n            if self.state == 1:\n                self << 0\n            let out = super().calculate()\n            if out == None and self[-1] == '\\\\':\n                return 1\n            return out\n        return None\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/doxygen.krk",
    "content": "\ndef _make_dox():\n    from syntax import Highlighter\n\n    let doxygen_keywords_at = [\n        \"@author\",\"@brief\",\"@class\",\"@short\",\"@retval\",\n        \"@since\",\"@return\",\"@returns\",\"@throws\",\"@bug\",\n        \"@version\",\"@deprecated\",\"@attention\",\"@note\",\n        \"@protected\",\"@copyright\",\"@date\",\"@bug\",\"@pre\",\n        \"@details\",\n    ]\n\n    let doxygen_word_commands = {\n        '@param':      Highlighter.FLAG_TYPE,\n        '@exception':  Highlighter.FLAG_PRAGMA,\n        '@def':        Highlighter.FLAG_TYPE,\n        '@see':        Highlighter.FLAG_LINK,\n        '@p':          Highlighter.FLAG_TYPE,\n        '@c':          Highlighter.FLAG_NONE,\n        '@file':       Highlighter.FLAG_LINK,\n        '@memberof':   Highlighter.FLAG_TYPE,\n        '@extends':    Highlighter.FLAG_TYPE,\n        '@mainpage':   Highlighter.FLAG_STRING,\n        '@b':          Highlighter.FLAG_BOLD,\n        '@section':    Highlighter.FLAG_BOLD,\n        '@subsection': Highlighter.FLAG_BOLD,\n        '@package':    Highlighter.FLAG_TYPE,\n        '@ref':        Highlighter.FLAG_LINK,\n        '@typedef':    Highlighter.FLAG_TYPE,\n        '@struct':     Highlighter.FLAG_TYPE,\n    }\n\n    let doxygen_block_commands = {\n        '@warning':    Highlighter.FLAG_NOTICE,\n    }\n\n    let doxygen_emphasis_commands = [\n        '@a', '@e', '@em',\n    ]\n\n    def doxygen_qualifier(c):\n        if isinstance(c,int):\n            if c > 0: c = chr(c)\n            else: return False\n        return Highlighter.isalnum(c) or c in '_@'\n\n    def tryDoxygenComment(b,defaultflag=b.FLAG_COMMENT):\n        if b[0] == '@':\n            if not b.findKeywords(doxygen_keywords_at, b.FLAG_ESCAPE, doxygen_qualifier):\n                for keyword, flag in doxygen_word_commands.items():\n                    if b.matchAndPaint(keyword, b.FLAG_ESCAPE, doxygen_qualifier):\n                        while b[0] == ' ': b.skip()\n                        while b[0] and b[0] != ' ':\n                            if b[0] == '.' and b[1] in (None,' '):\n                                return True\n                            b.paint(1, flag)\n                        return True\n                for keyword, flag in doxygen_block_commands.items():\n                    if b.matchAndPaint(keyword, b.FLAG_ESCAPE, doxygen_qualifier):\n                        while b[0] == ' ': b.skip()\n                        while b[0]: b.paint(1, flag)\n                        return True\n                if b.findKeywords(doxygen_emphasis_commands, b.FLAG_ESCAPE, doxygen_qualifier):\n                    while b[0] == ' ': b.skip()\n                    while b[0] and b[0] != ' ':\n                        b.paint(1, defaultflag | b.FLAG_UNDERLINE)\n                    return True\n                b.paint(1, defaultflag)\n            return True\n        return False\n\n    return tryDoxygenComment\n\nlet tryDoxygenComment = _make_dox()\n"
  },
  {
    "path": "base/usr/share/bim/syntax/esh.krk",
    "content": "'''\nShell highlighter for ToaruOS's 'experimental shell'.\n'''\nfrom syntax import Highlighter, bind\n\nclass EshHighlighter(Highlighter):\n    name = 'esh'\n    extensions = ('.eshrc','.yutanirc')\n    keywords = [\n        \"cd\",\"exit\",\"export\",\"help\",\"history\",\"if\",\"empty?\",\n        \"equals?\",\"return\",\"export-cmd\",\"source\",\"exec\",\"not\",\"while\",\n        \"then\",\"else\",\"echo\",\n    ]\n    def variableQualifier(c):\n        c = c if isinstance(c,int) else ord(c) if c else -1\n        return self.isalnum(c) or c == ord('_')\n    def keywordQualifier(c):\n        c = c if isinstance(c,int) else ord(c) if c else -1\n        return self.isalnum(c) or c > 0 and chr(c) in '?_-'\n    def paintVariable():\n        if self[0] == '{':\n            self.paint(1, self.FLAG_TYPE)\n            while self[0] != '}' and self[0]:\n                self.paint(1, self.FLAG_TYPE)\n            if self[0] == '}':\n                self.paint(1, self.FLAG_TYPE)\n        else:\n            if self[0] in '?$#':\n                self.paint(1, self.FLAG_TYPE)\n            else:\n                while self.variableQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n    def paintString():\n        let last = None\n        while self[0]:\n            if last != '\\\\' and self[0] == '\"':\n                self.paint(1, self.FLAG_STRING)\n                return 0\n            else if self[0] == '$':\n                self.paint(1, self.FLAG_TYPE)\n                self.paintVariable()\n                last = None\n            else if self[0]:\n                last = self[0]\n                self.paint(1, self.FLAG_STRING)\n        return 2\n    def paintSingle():\n        let last = None\n        while self[0]:\n            if last != '\\\\' and self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                return 0\n            else if self[0]:\n                last = self[0]\n                self.paint(1, self.FLAG_STRING)\n        return 1\n    def calculate():\n        if self.state == 1:\n            return self.paintSingle()\n        else if self.state == 2:\n            return self.paintString()\n        if self[0] == '#':\n            return self.paintComment()\n        else if self[0] == '$':\n            self.paint(1, self.FLAG_TYPE)\n            self.paintVariable()\n            return 0\n        else if self[0] == \"'\":\n            self.paint(1, self.FLAG_STRING)\n            return self.paintSingle()\n        else if self[0] == '\"':\n            self.paint(1, self.FLAG_STRING)\n            return self.paintString()\n        else if self.matchAndPaint('export', self.FLAG_KEYWORD, self.keywordQualifier):\n            while self[0] == ' ': self.skip()\n            while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            return 0\n        else if self.matchAndPaint('export-cmd', self.FLAG_KEYWORD, self.keywordQualifier):\n            while self[0] == ' ': self.skip()\n            while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n            return 0\n        else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier):\n            return 0\n        else if self.isdigit(self[0]):\n            while self.isdigit(self[0]):\n                self.paint(1, self.FLAG_NUMERAL)\n            return 0\n        else if self[0]:\n            self.skip()\n            return 0\n        return None\n\ntry:\n    import os\n    if os.uname['sysname'] == 'toaru':\n        EshHighlighter.extensions = ('.eshrc','.yutanirc','.sh')\n\nbind(EshHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/gas.krk",
    "content": "from syntax import Highlighter, bind\n\n@bind\nclass GasHighlighter(Highlighter):\n    name = 'gas'\n    extensions = ('.S',)\n    spaces = True\n\n    directives = ['.' + x for x in [\n        'abort','align','balignw','balignl','bundle_align_mode','bundle_lock','bundle_unlock',\n        'bss','cfi_startproc','cfi_sections','cfi_endproc','cfi_personality','cfi_lsda',\n        'cfi_def_cfa','cfi_def_cfa_register','cfi_def_cfa_offset','cfi_adjust_cfa_offset',\n        'cfi_offset','cfi_rel_offset','cfi_register','cfi_restore','cfi_undefined',\n        'cfi_same_value','cfi_remember_state','cfi_return_column','cfi_signal_frame',\n        'cfi_window_save','cfi_escape','cfi_val_encoded_addr','data','def','desc',\n        'dim','eject','else','elseif','endef','endif','endr','qeu','equiv','eqv','err',\n        'error','exitm','extern','fail','file','fill','global','globl','gnu_attribute',\n        'hidden','ident','if','incbin','include','internal','irp','irpc','lcomm','lflags',\n        'line','linkonce','list','ln','loc','loc_mark_labels','local','mri','nolist','org',\n        'p2alignw','p2align1','popsection','previous','print','protected','psize','purgem',\n        'pushsection','reloc','rept','sbttl','scl','section','set','single','size','skip',\n        'sleb128','stabd','stabn','stabs','struct','subsection','symver','tag','text','title',\n        'type','uleb128','val','version','vtable_entry','vtable_inherit','warning','weak',\n        'weakref',\n        'code16','code32','code64',\n    ]]\n\n    types = ['.' + x for x in [\n        'byte','hword','word','int','long','double','short','float','quad','octa',\n        'string','string8','string16','ascii','asciz','comm','space'\n    ]]\n\n    registers = ['%' + x for x in [\n        'r8','r9','r10','r11','r12','r13','r14','r15',\n        'rax','rbx','rcx','rdx','rdi','rsi','rsp','rbp',\n        'eax','ebx','ecx','edx','ax','bx','cx','dx','ah','al','bh','bl','ch','cl','dh','dl',\n        'edi','esi','esp','ebp','di','si','sp','bp','sph','spl','bph','bpl',\n        'cs','ds','es','fs','gs','ss','ip','eip','rip','eflags',\n        'cr0','cr1','cr2','cr3','cr4',\n    ]]\n\n    instructions = [\n        'aaa',\n        'aad','aadb','aadw','aadl','aadq',\n        'aam','aamb','aamw','aaml','aamq',\n        'aas',\n        'adc','adcb','adcw','adcl','adcq',\n        'add','addb','addw','addl','addq',\n        'and','andb','andw','andl','andq',\n        'arpl',\n        'bb0_reset',\n        'bb1_reset',\n        'bound','boundb','boundw','boundl','boundq',\n        'bsf',\n        'bsr',\n        'bswap',\n        'bt','btb','btw','btl','btq',\n        'btc','btcb','btcw','btcl','btcq',\n        'btr','btrb','btrw','btrl','btrq',\n        'bts','btsb','btsw','btsl','btsq',\n        'call','callb','callw','calll','callq',\n        'cbw',\n        'cdq',\n        'cdqe',\n        'clc',\n        'cld',\n        'clgi',\n        'cli',\n        'clts',\n        'cmc',\n        'cmp','cmpb','cmpw','cmpl','cmpq',\n        'cmpsb',\n        'cmpsd',\n        'cmpsq',\n        'cmpsw',\n        'cmpxchg',\n        'cmpxchg486',\n        'cmpxchg8b','cmpxchg8bb','cmpxchg8bw','cmpxchg8bl','cmpxchg8bq',\n        'cmpxchg16b','cmpxchg16bb','cmpxchg16bw','cmpxchg16bl','cmpxchg16bq',\n        'cpuid',\n        'cpu_read',\n        'cpu_write',\n        'cqo',\n        'cwd',\n        'cwde',\n        'daa',\n        'das',\n        'dec',\n        'div',\n        'dmint',\n        'emms',\n        'enter','enterb','enterw','enterl','enterq',\n        'equ',\n        'f2xm1',\n        'fabs',\n        'fadd',\n        'faddp',\n        'fbld','fbldb','fbldw','fbldl','fbldq',\n        'fbstp','fbstpb','fbstpw','fbstpl','fbstpq',\n        'fchs',\n        'fclex',\n        'fcmovb',\n        'fcmovbe',\n        'fcmove',\n        'fcmovnb',\n        'fcmovnbe',\n        'fcmovne',\n        'fcmovnu',\n        'fcmovu',\n        'fcom',\n        'fcomi',\n        'fcomip',\n        'fcomp',\n        'fcompp',\n        'fcos',\n        'fdecstp',\n        'fdisi',\n        'fdiv',\n        'fdivp',\n        'fdivr',\n        'fdivrp',\n        'femms',\n        'feni',\n        'ffree',\n        'ffreep',\n        'fiadd','fiaddb','fiaddw','fiaddl','fiaddq',\n        'ficom','ficomb','ficomw','ficoml','ficomq',\n        'ficomp','ficompb','ficompw','ficompl','ficompq',\n        'fidiv','fidivb','fidivw','fidivl','fidivq',\n        'fidivr','fidivrb','fidivrw','fidivrl','fidivrq',\n        'fild','fildb','fildw','fildl','fildq',\n        'fimul','fimulb','fimulw','fimull','fimulq',\n        'fincstp',\n        'finit',\n        'fist','fistb','fistw','fistl','fistq',\n        'fistp','fistpb','fistpw','fistpl','fistpq',\n        'fisttp','fisttpb','fisttpw','fisttpl','fisttpq',\n        'fisub','fisubb','fisubw','fisubl','fisubq',\n        'fisubr','fisubrb','fisubrw','fisubrl','fisubrq',\n        'fld',\n        'fld1',\n        'fldcw','fldcwb','fldcww','fldcwl','fldcwq',\n        'fldenv','fldenvb','fldenvw','fldenvl','fldenvq',\n        'fldl2e',\n        'fldl2t',\n        'fldlg2',\n        'fldln2',\n        'fldpi',\n        'fldz',\n        'fmul',\n        'fmulp',\n        'fnclex',\n        'fndisi',\n        'fneni',\n        'fninit',\n        'fnop',\n        'fnsave','fnsaveb','fnsavew','fnsavel','fnsaveq',\n        'fnstcw','fnstcwb','fnstcww','fnstcwl','fnstcwq',\n        'fnstenv','fnstenvb','fnstenvw','fnstenvl','fnstenvq',\n        'fnstsw',\n        'fpatan',\n        'fprem',\n        'fprem1',\n        'fptan',\n        'frndint',\n        'frstor','frstorb','frstorw','frstorl','frstorq',\n        'fsave','fsaveb','fsavew','fsavel','fsaveq',\n        'fscale',\n        'fsetpm',\n        'fsin',\n        'fsincos',\n        'fsqrt',\n        'fst',\n        'fstcw','fstcwb','fstcww','fstcwl','fstcwq',\n        'fstenv','fstenvb','fstenvw','fstenvl','fstenvq',\n        'fstp',\n        'fstsw',\n        'fsub',\n        'fsubp',\n        'fsubr',\n        'fsubrp',\n        'ftst',\n        'fucom',\n        'fucomi',\n        'fucomip',\n        'fucomp',\n        'fucompp',\n        'fxam',\n        'fxch',\n        'fxtract',\n        'fyl2x',\n        'fyl2xp1',\n        'hlt',\n        'ibts',\n        'icebp',\n        'idiv',\n        'imul','imulb','imulw','imull','imulq',\n        'in',\n        'inc','incb','incw','incl','incq',\n        'incbin',\n        'insb',\n        'insd',\n        'insw',\n        'int','intb','intw','intl','intq',\n        'int01',\n        'int1',\n        'int03',\n        'int3',\n        'into',\n        'invd',\n        'invlpg','invlpgb','invlpgw','invlpgl','invlpgq',\n        'invlpga',\n        'iret',\n        'iretd',\n        'iretq',\n        'iretw',\n        'jcxz','jcxzb','jcxzw','jcxzl','jcxzq',\n        'jecxz','jecxzb','jecxzw','jecxzl','jecxzq',\n        'jrcxz','jrcxzb','jrcxzw','jrcxzl','jrcxzq',\n        'jmp','jmpb','jmpw','jmpl','jmpq',\n        'jmpe',\n        'lahf',\n        'lar',\n        'lds','ldsb','ldsw','ldsl','ldsq',\n        'lea','leab','leaw','leal','leaq',\n        'leave',\n        'les','lesb','lesw','lesl','lesq',\n        'lfence',\n        'lfs','lfsb','lfsw','lfsl','lfsq',\n        'lgdt','lgdtb','lgdtw','lgdtl','lgdtq',\n        'lgs','lgsb','lgsw','lgsl','lgsq',\n        'lidt','lidtb','lidtw','lidtl','lidtq',\n        'lldt',\n        'lmsw',\n        'loadall',\n        'loadall286',\n        'lodsb',\n        'lodsd',\n        'lodsq',\n        'lodsw',\n        'loop','loopb','loopw','loopl','loopq',\n        'loope','loopeb','loopew','loopel','loopeq',\n        'loopne','loopneb','loopnew','loopnel','loopneq',\n        'loopnz','loopnzb','loopnzw','loopnzl','loopnzq',\n        'loopz','loopzb','loopzw','loopzl','loopzq',\n        'lsl',\n        'lss','lssb','lssw','lssl','lssq',\n        'ltr',\n        'mfence',\n        'monitor',\n        'mov','movb','movw','movl','movq',\n        'movd',\n        'movq',\n        'movsb',\n        'movsd',\n        'movsq',\n        'movsw',\n        'movsx',\n        'movsxd',\n        'movsx',\n        'movzx',\n        'movzbl',\n        'mul',\n        'mwait',\n        'neg',\n        'nop',\n        'not',\n        'or','orb','orw','orl','orq',\n        'out',\n        'outsb',\n        'outsd',\n        'outsw',\n        'packssdw','packssdwb','packssdww','packssdwl','packssdwq',\n        'packsswb','packsswbb','packsswbw','packsswbl','packsswbq',\n        'packuswb','packuswbb','packuswbw','packuswbl','packuswbq',\n        'paddb','paddbb','paddbw','paddbl','paddbq',\n        'paddd','padddb','padddw','padddl','padddq',\n        'paddsb','paddsbb','paddsbw','paddsbl','paddsbq',\n        'paddsiw','paddsiwb','paddsiww','paddsiwl','paddsiwq',\n        'paddsw','paddswb','paddsww','paddswl','paddswq',\n        'paddusb','paddusbb','paddusbw','paddusbl','paddusbq',\n        'paddusw','padduswb','paddusww','padduswl','padduswq',\n        'paddw','paddwb','paddww','paddwl','paddwq',\n        'pand','pandb','pandw','pandl','pandq',\n        'pandn','pandnb','pandnw','pandnl','pandnq',\n        'pause',\n        'paveb','pavebb','pavebw','pavebl','pavebq',\n        'pavgusb','pavgusbb','pavgusbw','pavgusbl','pavgusbq',\n        'pcmpeqb','pcmpeqbb','pcmpeqbw','pcmpeqbl','pcmpeqbq',\n        'pcmpeqd','pcmpeqdb','pcmpeqdw','pcmpeqdl','pcmpeqdq',\n        'pcmpeqw','pcmpeqwb','pcmpeqww','pcmpeqwl','pcmpeqwq',\n        'pcmpgtb','pcmpgtbb','pcmpgtbw','pcmpgtbl','pcmpgtbq',\n        'pcmpgtd','pcmpgtdb','pcmpgtdw','pcmpgtdl','pcmpgtdq',\n        'pcmpgtw','pcmpgtwb','pcmpgtww','pcmpgtwl','pcmpgtwq',\n        'pdistib','pdistibb','pdistibw','pdistibl','pdistibq',\n        'pf2id','pf2idb','pf2idw','pf2idl','pf2idq',\n        'pfacc','pfaccb','pfaccw','pfaccl','pfaccq',\n        'pfadd','pfaddb','pfaddw','pfaddl','pfaddq',\n        'pfcmpeq','pfcmpeqb','pfcmpeqw','pfcmpeql','pfcmpeqq',\n        'pfcmpge','pfcmpgeb','pfcmpgew','pfcmpgel','pfcmpgeq',\n        'pfcmpgt','pfcmpgtb','pfcmpgtw','pfcmpgtl','pfcmpgtq',\n        'pfmax','pfmaxb','pfmaxw','pfmaxl','pfmaxq',\n        'pfmin','pfminb','pfminw','pfminl','pfminq',\n        'pfmul','pfmulb','pfmulw','pfmull','pfmulq',\n        'pfrcp','pfrcpb','pfrcpw','pfrcpl','pfrcpq',\n        'pfrcpit1','pfrcpit1b','pfrcpit1w','pfrcpit1l','pfrcpit1q',\n        'pfrcpit2','pfrcpit2b','pfrcpit2w','pfrcpit2l','pfrcpit2q',\n        'pfrsqit1','pfrsqit1b','pfrsqit1w','pfrsqit1l','pfrsqit1q',\n        'pfrsqrt','pfrsqrtb','pfrsqrtw','pfrsqrtl','pfrsqrtq',\n        'pfsub','pfsubb','pfsubw','pfsubl','pfsubq',\n        'pfsubr','pfsubrb','pfsubrw','pfsubrl','pfsubrq',\n        'pi2fd','pi2fdb','pi2fdw','pi2fdl','pi2fdq',\n        'pmachriw','pmachriwb','pmachriww','pmachriwl','pmachriwq',\n        'pmaddwd','pmaddwdb','pmaddwdw','pmaddwdl','pmaddwdq',\n        'pmagw','pmagwb','pmagww','pmagwl','pmagwq',\n        'pmulhriw','pmulhriwb','pmulhriww','pmulhriwl','pmulhriwq',\n        'pmulhrwa','pmulhrwab','pmulhrwaw','pmulhrwal','pmulhrwaq',\n        'pmulhrwc','pmulhrwcb','pmulhrwcw','pmulhrwcl','pmulhrwcq',\n        'pmulhw','pmulhwb','pmulhww','pmulhwl','pmulhwq',\n        'pmullw','pmullwb','pmullww','pmullwl','pmullwq',\n        'pmvgezb','pmvgezbb','pmvgezbw','pmvgezbl','pmvgezbq',\n        'pmvlzb','pmvlzbb','pmvlzbw','pmvlzbl','pmvlzbq',\n        'pmvnzb','pmvnzbb','pmvnzbw','pmvnzbl','pmvnzbq',\n        'pmvzb','pmvzbb','pmvzbw','pmvzbl','pmvzbq',\n        'pop','popb','popw','popl','popq',\n        'popa',\n        'popal',\n        'popaw',\n        'popf',\n        'popfd','popfl',\n        'popfq',\n        'popfw',\n        'por','porb','porw','porl','porq',\n        'prefetch','prefetchb','prefetchw','prefetchl','prefetchq',\n        'prefetchw','prefetchwb','prefetchww','prefetchwl','prefetchwq',\n        'pslld','pslldb','pslldw','pslldl','pslldq',\n        'psllq','psllqb','psllqw','psllql','psllqq',\n        'psllw','psllwb','psllww','psllwl','psllwq',\n        'psrad','psradb','psradw','psradl','psradq',\n        'psraw','psrawb','psraww','psrawl','psrawq',\n        'psrld','psrldb','psrldw','psrldl','psrldq',\n        'psrlq','psrlqb','psrlqw','psrlql','psrlqq',\n        'psrlw','psrlwb','psrlww','psrlwl','psrlwq',\n        'psubb','psubbb','psubbw','psubbl','psubbq',\n        'psubd','psubdb','psubdw','psubdl','psubdq',\n        'psubsb','psubsbb','psubsbw','psubsbl','psubsbq',\n        'psubsiw','psubsiwb','psubsiww','psubsiwl','psubsiwq',\n        'psubsw','psubswb','psubsww','psubswl','psubswq',\n        'psubusb','psubusbb','psubusbw','psubusbl','psubusbq',\n        'psubusw','psubuswb','psubusww','psubuswl','psubuswq',\n        'psubw','psubwb','psubww','psubwl','psubwq',\n        'punpckhbw','punpckhbwb','punpckhbww','punpckhbwl','punpckhbwq',\n        'punpckhdq','punpckhdqb','punpckhdqw','punpckhdql','punpckhdqq',\n        'punpckhwd','punpckhwdb','punpckhwdw','punpckhwdl','punpckhwdq',\n        'punpcklbw','punpcklbwb','punpcklbww','punpcklbwl','punpcklbwq',\n        'punpckldq','punpckldqb','punpckldqw','punpckldql','punpckldqq',\n        'punpcklwd','punpcklwdb','punpcklwdw','punpcklwdl','punpcklwdq',\n        'push','pushb','pushw','pushl','pushq',\n        'pusha',\n        'pushal',\n        'pushaw',\n        'pushf',\n        'pushfd',\n        'pushfq',\n        'pushfw',\n        'pxor','pxorb','pxorw','pxorl','pxorq',\n        'rcl','rclb','rclw','rcll','rclq',\n        'rcr','rcrb','rcrw','rcrl','rcrq',\n        'rdshr',\n        'rdmsr',\n        'rdpmc',\n        'rdtsc',\n        'rdtscp',\n        'ret','retb','retw','retl','retq',\n        'retf','retfb','retfw','retfl','retfq',\n        'retn','retnb','retnw','retnl','retnq',\n        'rol','rolb','rolw','roll','rolq',\n        'ror','rorb','rorw','rorl','rorq',\n        'rdm',\n        'rsdc','rsdcb','rsdcw','rsdcl','rsdcq',\n        'rsldt','rsldtb','rsldtw','rsldtl','rsldtq',\n        'rsm',\n        'rsts','rstsb','rstsw','rstsl','rstsq',\n        'sahf',\n        'sal','salb','salw','sall','salq',\n        'salc',\n        'sar','sarb','sarw','sarl','sarq',\n        'sbb','sbbb','sbbw','sbbl','sbbq',\n        'scasb',\n        'scasd',\n        'scasq',\n        'scasw',\n        'sfence',\n        'sgdt','sgdtb','sgdtw','sgdtl','sgdtq',\n        'shl','shlb','shlw','shll','shlq',\n        'shld',\n        'shr','shrb','shrw','shrl','shrq',\n        'shrd',\n        'sidt','sidtb','sidtw','sidtl','sidtq',\n        'sldt',\n        'skinit',\n        'smi',\n        'smint',\n        'smintold',\n        'smsw',\n        'stc',\n        'std',\n        'stgi',\n        'sti',\n        'stosb',\n        'stosd','stosl',\n        'stosq',\n        'stosw',\n        'str',\n        'sub','subb','subw','subl','subq',\n        'svdc','svdcb','svdcw','svdcl','svdcq',\n        'svldt','svldtb','svldtw','svldtl','svldtq',\n        'svts','svtsb','svtsw','svtsl','svtsq',\n        'swapgs',\n        'syscall',\n        'sysenter',\n        'sysexit',\n        'sysret',\n        'test','testb','testw','testl','testq',\n        'ud0',\n        'ud1',\n        'ud2b',\n        'ud2',\n        'ud2a',\n        'umov',\n        'verr',\n        'verw',\n        'fwait',\n        'wbinvd',\n        'wrshr',\n        'wrmsr',\n        'xadd',\n        'xbts',\n        'xchg',\n        'xlatb',\n        'xlat',\n        'xor','xorb','xorw','xorl','xorq',\n        'cmovcc',\n\n        'je','jne','ja','jae','jb','jbe','jnbe','jg','jge','jng','jnge','jl','jle','jz','jnz','jc','jnc','jd','jnd','jo','jno','jp','jnp','js','jns',\n        'jeb','jneb','jab','jaeb','jbb','jbeb','jnbeb','jgb','jgeb','jngb','jngeb','jlb','jleb','jzb','jnzb','jcb','jncb','jdb','jndb','job','jnob','jpb','jnpb','jsb','jnsb',\n        'jew','jnew','jaw','jaew','jbw','jbew','jnbew','jgw','jgew','jngw','jngew','jlw','jlew','jzw','jnzw','jcw','jncw','jdw','jndw','jow','jnow','jpw','jnpw','jsw','jnsw',\n        'jel','jnel','jal','jael','jbl','jbel','jnbel','jgl','jgel','jngl','jngel','jll','jlel','jzl','jnzl','jcl','jncl','jdl','jndl','jol','jnol','jpl','jnpl','jsl','jnsl',\n        'jeq','jneq','jaq','jaeq','jbq','jbeq','jnbeq','jgq','jgeq','jngq','jngeq','jlq','jleq','jzq','jnzq','jcq','jncq','jdq','jndq','joq','jnoq','jpq','jnpq','jsq','jnsq',\n\n        'ljmp',\n        'rep', 'stos',\n\n        'addr32',\n    ]\n\n    def dqualifier(self, c):\n        return self.cKeywordQualifier(c) or c == '.' or c == ord('.')\n\n    def rqualifier(self, c):\n        return self.cKeywordQualifier(c) or c == '%' or c == ord('%')\n\n    def paintCComment(self):\n        let last = None\n        while self[0] != None:\n            if self.commentBuzzwords(): continue\n            else if last == '*' and self[0] == '/':\n                self.paint(1, self.FLAG_COMMENT)\n                return 0\n            else:\n                last = self[0]\n                self.paint(1, self.FLAG_COMMENT)\n        return 1\n\n    def paintCNumeral(self):\n        if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'):\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and self[1] == '.':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL)\n            return 0\n        else if self[0] == '0':\n            self.paint(1, self.FLAG_NUMERAL)\n            while self[0] in '01234567': self.paint(1, self.FLAG_NUMERAL)\n        else:\n            while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == '.':\n                self.paint(1, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL)\n                return 0\n        while self[0] in 'uUlL': self.paint(1, self.FLAG_NUMERAL)\n        return 0\n\n    def calculate(self):\n        if self.state <= 0:\n            if self[0] == '/' and self[1] == '/':\n                while self[0]: self.paint(1, self.FLAG_COMMENT)\n            else if self[0] == '/' and self[1] == '*':\n                self.paint(2, self.FLAG_COMMENT)\n                return self.paintCComment()\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                self.paintCNumeral()\n                return 0\n            else if self[0] == '.':\n                if self.findKeywords(self.directives, self.FLAG_KEYWORD, self.dqualifier):\n                    return 0\n                else if self.findKeywords(self.types, self.FLAG_PRAGMA, self.dqualifier):\n                    return 0\n                else:\n                    self.skip()\n                    return 0\n            else if self[0] == '%':\n                if self.findKeywords(self.registers, self.FLAG_ESCAPE, self.rqualifier):\n                    return 0\n                else:\n                    self.skip()\n                    return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isalpha(self[0]):\n                if self.findKeywords(self.instructions, self.FLAG_TYPE, self.cKeywordQualifier):\n                    return 0\n                self.skip()\n                return 0\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                return 0\n            else if self[0] == \"'\":\n                self.paintSingleString()\n                return 0\n            else if self[0] != None:\n                self.skip()\n                return 0\n        else if self.state == 1:\n            return self.paintCComment()\n        return None\n\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/git.krk",
    "content": "from syntax import Highlighter, bind\n\nclass GitcommitHighlighter(Highlighter):\n    name = 'gitcommit'\n    extensions = ('COMMIT_EDITMSG',)\n    def calculate():\n        if self.i == 0 and self[0] == '#':\n            return self.paintComment()\n        else if self.lineno == 0:\n            while self[0] and self.i < 50:\n                self.paint(1, self.FLAG_KEYWORD)\n            self.paint(-1, self.FLAG_DIFFMINUS)\n        else if self.lineno == 1:\n            self.paint(-1, self.FLAG_DIFFMINUS)\n        else if self[0]:\n            self.paint(-1, self.FLAG_NONE)\n        return None\n\nbind(GitcommitHighlighter)\n\nclass GitrebaseHighlighter(Highlighter):\n    name = 'gitrebase'\n    extensions = ('git-rebase-todo',)\n    commands = [\n        \"p\",\"r\",\"e\",\"s\",\"f\",\"x\",\"d\",\n        \"pick\",\"reword\",\"edit\",\"squash\",\"fixup\",\n        \"exec\",\"drop\"\n    ]\n    def calculate():\n        if self.i == 0 and self[0] == '#':\n            return self.paintComment()\n        else if self.i == 0 and self.findKeywords(self.commands, self.FLAG_KEYWORD, self.cKeywordQualifier):\n            while self[0] == ' ': self.skip()\n            while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n            return None\n        return None\n\nbind(GitrebaseHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/graphql.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass GraphqlHighlighter(Highlighter):\n    name = 'graphql'\n    extensions = ('.graphql','.gql','.graphqls')\n    keywords = [\n        'repeatable','on','implements','extend',\n        'enum','scalar','type','union','input','interface','subscription',\n        'query','mutation','fragment','directive','schema',\n    ]\n    constants = [\n        'true','false','null',\n        '__schema','__type','__typename',\n    ]\n    primitives = [\n    ]\n    def paintTriple():\n        while self[0]:\n            if self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                if self[0] == \"'\" and self[1] == \"'\":\n                    self.paint(2, self.FLAG_STRING)\n                    return 0\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 2\n    def paintPyTriple(quote):\n        while self[0]:\n            if self[0] == quote:\n                self.paint(1, self.FLAG_STRING)\n                if self[0] == quote and self[1] == quote:\n                    self.paint(2, self.FLAG_STRING)\n                    return 0\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 1 if quote == '\"' else 2\n    def calculate():\n        if self.state <= 0:\n            if self[0] == '#':\n                self.paintComment()\n            else if self[0] == '\"':\n                if self[1] == '\"' and self[2] == '\"':\n                    self.paint(3, self.FLAG_STRING)\n                    return self.paintPyTriple('\"')\n                self.paintSimpleString()\n                return 0\n            else if self[0] == \"'\":\n                self.paintSingleString()\n                return 0\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.primitives, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                CHighlighter.paintCNumeral(self)\n                return 0\n            else if self[0] == '@':\n                self.paint(1, self.FLAG_TYPE)\n                while self.cKeywordQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':\n                while self.cKeywordQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n            return None\n        else if self.state == 1:\n            return self.paintPyTriple('\"')\n        return None\n\nbind(GraphqlHighlighter)\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/groovy.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass GroovyHighlighter(Highlighter):\n    name = 'groovy'\n    extensions = ('.groovy','.jenkinsfile','.gradle')\n    keywords = [\n        \"as\",\"assert\",\"break\",\"case\",\n        \"catch\",\"class\",\"const\",\"continue\",\n        \"def\",\"default\",\"do\",\"else\",\"enum\",\n        \"extends\",\"finally\",\"for\",\n        \"goto\",\"if\",\"implements\",\"import\",\n        \"in\",\"instanceof\",\"interface\",\"new\",\n        \"package\",\"return\",\"super\",\n        \"switch\",\"throw\",\"throws\",\n        \"trait\",\"try\",\"while\",\"final\",\n    ]\n    constants = [\n        'true','false','null','this',\n    ]\n    primitives = [\n        'byte','char','short','int','long','BigInteger'\n    ]\n    def paintTriple():\n        while self[0]:\n            if self[0] == \"'\":\n                self.paint(1, self.FLAG_STRING)\n                if self[0] == \"'\" and self[1] == \"'\":\n                    self.paint(2, self.FLAG_STRING)\n                    return 0\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 2\n    def calculate():\n        if self.state <= 0:\n            if self.lineno == 0 and self.i == 0 and self[0] == '#':\n                self.paintComment()\n                return None\n            else if self[0] == '/' and self[1] == '/':\n                self.paintComment()\n                return None\n            else if self[0] == '/' and self[1] == '*':\n                if CHighlighter.paintCComment(self) == 1: return 1\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                return 0\n            else if self[0] == \"'\":\n                self.paintSingleString()\n                return 0\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.primitives, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                CHighlighter.paintCNumeral(self)\n                return 0\n            else if self[0] == '@':\n                self.paint(1, self.FLAG_TYPE)\n                while self.cKeywordQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':\n                while self.cKeywordQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n            return None\n        else if self.state == 1:\n            if CHighlighter.paintCComment(self) == 1: return 1\n            return 0\n        else if self.state == 2:\n            return self.paintTriple()\n        return None\n\nbind(GroovyHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/hosts.krk",
    "content": "from syntax import Highlighter, bind\n\nclass HostsHighlighter(Highlighter):\n    name = 'hosts'\n    extensions = ('hosts',)\n\n    def calculate():\n        if self.i == 0:\n            if self[0] == '#':\n                return self.paintComment()\n            while self[0] != '\\t' and self[0] != ' ' and self[0]:\n                self.paint(1, self.FLAG_NUMERAL)\n            while self[0]:\n                self.paint(1, self.FLAG_TYPE)\n        return None\n\nbind(HostsHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/issue.krk",
    "content": "from syntax import Highlighter, bind\n\nclass EtcIssueHighlighter(Highlighter):\n    name = \"issue\"\n    extensions = ('/etc/issue',)\n    spaces = True\n    e_types = [\n        ('bold', Highlighter.FLAG_BOLD),\n        ('reset', Highlighter.FLAG_NONE),\n        ('blue', Highlighter.FLAG_KEYWORD),\n        ('red', Highlighter.FLAG_DIFFMINUS),\n        ('black', Highlighter.FLAG_COMMENT),\n        ('brown', Highlighter.FLAG_TYPE),\n        ('cyan', Highlighter.FLAG_KEYWORD),\n        ('darkgray', Highlighter.FLAG_COMMENT),\n        ('gray', Highlighter.FLAG_COMMENT),\n        ('green', Highlighter.FLAG_DIFFPLUS),\n        ('lightblue', Highlighter.FLAG_KEYWORD),\n        ('lightcyan', Highlighter.FLAG_KEYWORD),\n        ('lightgray', Highlighter.FLAG_COMMENT),\n        ('lightgreen', Highlighter.FLAG_DIFFPLUS),\n        ('lightmagenta', Highlighter.FLAG_NUMERAL),\n        ('lightred', Highlighter.FLAG_DIFFMINUS),\n        ('magenta', Highlighter.FLAG_NUMERAL),\n        ('reverse', Highlighter.FLAG_UNDERLINE),\n        ('yellow', Highlighter.FLAG_NOTICE),\n    ]\n\n    def calculate(self):\n        if self[0] == '\\\\':\n            if self[1] in ('\\\\','n','s','r','m','v','l','d','t'):\n                self.paint(2, self.FLAG_ESCAPE)\n                return 0\n            else if not self[1]:\n                self.paint(1, self.FLAG_ESCAPE)\n                return None\n            else if self[1] == 'e':\n                if self[2] == '{':\n                    self.paint(3, self.FLAG_ESCAPE)\n                    for tname, tcolor in self.e_types:\n                        if self.matchAndPaint(tname, tcolor, self.cKeywordQualifier):\n                            break\n                    if not self[0] == '}':\n                        self.paint(1, self.FLAG_ERROR)\n                    else:\n                        self.paint(1, self.FLAG_ESCAPE)\n                    return 0\n                else:\n                    self.paint(2, self.FLAG_ESCAPE)\n                    return 0\n            else if self[1] == '4':\n                if self[2] == '{':\n                    self.paint(3, self.FLAG_ESCAPE)\n                    while self[0] and self[0] != '}':\n                        self.paint(1, self.FLAG_KEYWORD)\n                    if self[0] == '}':\n                        self.paint(1, self.FLAG_ESCAPE)\n                    return 0\n                else:\n                    self.paint(2, self.FLAG_ESCAPE)\n                    return 0\n            else:\n                self.skip()\n                return 0\n        else if self[0]:\n            self.skip()\n            return 0\n        else:\n            return None\n\nbind(EtcIssueHighlighter)\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/java.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass JavaHighlighter(Highlighter):\n    name = 'java'\n    extensions = ('.java',)\n    keywords = [\n        \"assert\",\"break\",\"case\",\"catch\",\"class\",\"continue\",\n        \"default\",\"do\",\"else\",\"enum\",\"exports\",\"extends\",\"finally\",\n        \"for\",\"if\",\"implements\",\"instanceof\",\"interface\",\"module\",\"native\",\n        \"new\",\"requires\",\"return\",\"throws\",\n        \"strictfp\",\"super\",\"switch\",\"synchronized\",\"this\",\"throw\",\"try\",\"while\",\n    ]\n    types = [\n        \"var\",\"boolean\",\"void\",\"short\",\"long\",\"int\",\"double\",\"float\",\"enum\",\"char\",\n        \"private\",\"protected\",\"public\",\"static\",\"final\",\"transient\",\"volatile\",\"abstract\",\n    ]\n    special = [\n        \"true\",\"false\",\"import\",\"package\",\"null\",\n    ]\n    at_comments = [\n        \"@author\",\"@see\",\"@since\",\"@return\",\"@throws\",\n        \"@version\",\"@exception\",\"@deprecated\",\n    ]\n    brace_comments = [\n        \"{@docRoot\",\"{@inheritDoc\",\"{@link\",\"{@linkplain\",\n        \"{@value\",\"{@code\",\"{@literal\",\"{@serial\",\n        \"{@serialData\",\"{@serialField\",\n    ]\n    def atKeywordQualifier(c):\n        if isinstance(c,int) and c > 0: c = chr(c)\n        else if isinstance(c,int) and c <= 0: return False\n        return self.isalnum(c) or c in '_@'\n    def braceKeywordQualifier(c):\n        if isinstance(c,int) and c > 0: c = chr(c)\n        else if isinstance(c,int) and c <= 0: return False\n        return self.isalnum(c) or c in '{@_'\n    def keywordQualifier(c):\n        return self.cKeywordQualifier(c)\n    def paintJavaComment():\n        let last\n        while self[0]:\n            if self.commentBuzzwords(): continue\n            if self[0] == '@':\n                if not self.findKeywords(self.at_comments, self.FLAG_ESCAPE, self.atKeywordQualifier):\n                    if self.matchAndPaint('@param', self.FLAG_ESCAPE, self.atKeywordQualifier):\n                        while self[0] == ' ': self.skip()\n                        while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE)\n                    else:\n                        self.paint(1, self.FLAG_COMMENT)\n            else if self[0] == '{':\n                if self.findKeywords(self.brace_comments, self.FLAG_ESCAPE, self.braceKeywordQualifier):\n                    while self[0] != '}' and self[0]:\n                        self.paint(1, self.FLAG_ESCAPE)\n                    if self[0] == '}': self.paint(1, self.FLAG_ESCAPE)\n                else:\n                    self.paint(1, self.FLAG_COMMENT)\n            else if self[0] == '<':\n                let isTag = False\n                for i = 1; self[i]; i++:\n                    if self[i] == '>':\n                        isTag = True\n                        break\n                    if not self.isalnum(self[i]) and self[i] != '/':\n                        isTag = 0\n                        break\n                if isTag:\n                    self.paint(1, self.FLAG_TYPE)\n                    while self[0] and self[0] != '>':\n                        if self[0] == '/': self.paint(1, self.FLAG_TYPE)\n                        else: self.paint(1, self.FLAG_KEYWORD)\n                    if self[0] == '>': self.paint(1, self.FLAG_TYPE)\n                else:\n                    self.paint(1, self.FLAG_COMMENT)\n            else if last == '*' and self[0] == '/':\n                self.paint(1, self.FLAG_COMMENT)\n                return 0\n            else:\n                last = self[0]\n                self.paint(1, self.FLAG_COMMENT)\n        return 1\n    def calculate():\n        if self.state <= 0:\n            while self[0] is not None:\n                if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                    CHighlighter.paintCNumeral(self)\n                else if self[0] == '/' and self[1] == '/':\n                    self.paintComment()\n                else if self[0] == '/' and self[1] == '*':\n                    if self.paintJavaComment() == 1: return 1\n                else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier):\n                    pass\n                else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                    pass\n                else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                    pass\n                else if self[0] == '\"':\n                    self.paintSimpleString()\n                else if self[0] == \"'\":\n                    CHighlighter.paintCChar(self)\n                else if self[0] == '@':\n                    self.paint(1, self.FLAG_PRAGMA)\n                    while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA)\n                else if self[0]:\n                    self.skip()\n        else if self.state == 1:\n            while self[0] is not None:\n                if self.paintJavaComment() == 1: return 1\n        return None\n\nbind(JavaHighlighter)\n\nclass KotlinHighlighter(JavaHighlighter):\n    name = 'kotlin'\n    extensions = ('.kt',)\n    keywords = [\n        \"as\",\"as?\",\"break\",\"class\",\"continue\",\"do\",\"else\",\"false\",\"for\",\n        \"fun\",\"if\",\"in\",\"!in\",\"interface\",\"is\",\"!is\",\"null\",\"object\",\n        \"package\",\"return\",\"super\",\"this\",\"throw\",\"true\",\"try\",\"typealias\",\n        \"typeof\",\"val\",\"var\",\"when\",\"while\",\n        \"by\",\"catch\",\"constructor\",\"delegate\",\"dynamic\",\"field\",\"file\",\n        \"finally\",\"get\",\"import\",\"init\",\"param\",\"property\",\"receiver\",\n        \"set\",\"setparam\",\"where\",\n        \"actual\",\"abstract\",\"annotation\",\"companion\",\"const\",\n        \"crossinline\",\"data\",\"enum\",\"expect\",\"external\",\"final\",\n        \"infix\",\"inner\",\"internal\",\"lateinit\",\"noinline\",\"open\",\n        \"operator\",\"out\",\"override\",\"private\",\"protected\",\"public\",\n        \"reified\",\"sealed\",\"suspend\",\"tailrec\",\"vararg\",\n        \"field\",\"it\",\"inline\",\n    ]\n    types = [\n        \"Byte\",\"Short\",\"Int\",\"Long\",\n        \"Float\",\"Double\",\"String\",\n    ]\n    specials = []\n    def keywordQualifier(c):\n        if isinstance(c,int) and c > 0: c = chr(c)\n        else if isinstance(c,int) and c <= 0: return False\n        return self.isalnum(c) or c in '?!_'\n\n    def paintTriple(quote):\n        while self[0]:\n            if self[0] == quote:\n                self.paint(1, self.FLAG_STRING)\n                if self[0] == quote and self[1] == quote:\n                    self.paint(2, self.FLAG_STRING)\n                    return 0\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 2\n    def calculate():\n        if self.state <= 0:\n            while self[0] is not None:\n                if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                    CHighlighter.paintCNumeral(self)\n                else if self[0] == '/' and self[1] == '/':\n                    self.paintComment()\n                else if self[0] == '/' and self[1] == '*':\n                    if self.paintJavaComment() == 1: return 1\n                else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier):\n                    pass\n                else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                    pass\n                else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                    pass\n                else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHJIKLMNOPQRSTUVWXYZ':\n                    while self.cKeywordQualifier(self[0]):\n                        self.paint(1, self.FLAG_TYPE)\n                else if self[0] == '\"':\n                    if self[1] == '\"' and self[2] == '\"':\n                        self.paint(3, self.FLAG_STRING)\n                        return self.paintTriple('\"')\n                    self.paintSimpleString()\n                else if self[0] == \"'\":\n                    CHighlighter.paintCChar(self)\n                else if self[0] == '@':\n                    self.paint(1, self.FLAG_PRAGMA)\n                    while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA)\n                else if self[0]:\n                    self.skip()\n        else if self.state == 1:\n            while self[0] is not None:\n                if self.paintJavaComment() == 1: return 1\n        else if self.state == 2:\n            return self.paintTriple('\"')\n        return None\n\nbind(KotlinHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/javascript.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\ndef makeit():\n    let FLAG_NONE = Highlighter.FLAG_NONE\n    let FLAG_KEYWORD = Highlighter.FLAG_KEYWORD\n    let FLAG_STRING = Highlighter.FLAG_STRING\n    let FLAG_COMMENT = Highlighter.FLAG_COMMENT\n    let FLAG_TYPE = Highlighter.FLAG_TYPE\n    let FLAG_PRAGMA = Highlighter.FLAG_PRAGMA\n    let FLAG_NUMERAL = Highlighter.FLAG_NUMERAL\n    let FLAG_ERROR = Highlighter.FLAG_ERROR\n    let FLAG_DIFFPLUS = Highlighter.FLAG_DIFFPLUS\n    let FLAG_DIFFMINUS = Highlighter.FLAG_DIFFMINUS\n    let FLAG_NOTICE = Highlighter.FLAG_NOTICE\n    let FLAG_BOLD = Highlighter.FLAG_BOLD\n    let FLAG_LINK = Highlighter.FLAG_LINK\n    let FLAG_ESCAPE = Highlighter.FLAG_ESCAPE\n    let FLAG_EXTRA = Highlighter.FLAG_EXTRA\n    let FLAG_SPECIAL = Highlighter.FLAG_SPECIAL\n    let FLAG_UNDERLINE = Highlighter.FLAG_UNDERLINE\n    class JavascriptHighlighter(Highlighter):\n        name = 'javascript'\n        extensions = ('.js','.jsx','.ts','.tsx')\n        keywords = [\n            \"abstract\",\"arguments\",\"from\",\n            \"await\",\"break\",\"case\",\"catch\",\"class\",\"const\",\n            \"continue\",\"debugger\",\"default\",\"delete\",\"do\",\"else\",\"enum\",\"eval\",\n            \"export\",\"extends\",\"final\",\"finally\",\"for\",\"function\",\"goto\",\n            \"if\",\"implements\",\"import\",\"in\",\"instanceof\",\"interface\",\"let\",\"long\",\n            \"native\",\"new\",\"package\",\"private\",\"protected\",\"public\",\"return\",\n            \"static\",\"super\",\"switch\",\"synchronized\",\"this\",\"throw\",\"throws\",\n            \"transient\",\"true\",\"try\",\"typeof\",\"volatile\",\"while\",\"with\",\"yield\",\n        ]\n        types = [\n            \"int\",\"float\",\"double\",\"short\",\"var\",\"void\",\"byte\",\"char\",\"boolean\",\n        ]\n        special = ['true','false','null']\n        def paintJSFormatString():\n            while self[0]:\n                if '\\\\' in self and self[1] == '`':\n                    self[2] = FLAG_ESCAPE\n                else if '`' in self:\n                    self[1] = FLAG_STRING\n                    return 0\n                else if '\\\\' in self:\n                    self[2] = FLAG_ESCAPE\n                else if '$' in self and self[1] == '{':\n                    self[2] = FLAG_NUMERAL\n                    while self[0] and self[0] != '}':\n                        self[1] = FLAG_NUMERAL\n                    self[1] = FLAG_NUMERAL\n                else:\n                    self[1] = FLAG_STRING\n            return 1\n        def calculate():\n            if self.state < 1:\n                while None not in self:\n                    if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                        CHighlighter.paintCNumeral(self)\n                    else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':\n                        while self[0] and self.cKeywordQualifier(self[0]):\n                            self[1] = FLAG_TYPE\n                    else if '/' in self and self[1] == '/':\n                        self.paintComment()\n                    else if '/' in self and self[1] == '*':\n                        if CHighlighter.paintCComment(self) == 1: return 1\n                    else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier):\n                        continue\n                    else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier):\n                        continue\n                    else if self.findKeywords(self.special, FLAG_NUMERAL, self.cKeywordQualifier):\n                        continue\n                    else if '=' in self and self[1] == '>':\n                        self[2] = FLAG_PRAGMA\n                    else if ':' in self and self.cKeywordQualifier(self[-1]):\n                        let start = self.i\n                        while self[-1] and self.cKeywordQualifier(self[-1]):\n                            self.rewind(1)\n                        while self[0] and self.i != start:\n                            self[1] = FLAG_TYPE\n                        self[1] = FLAG_PRAGMA\n                    else if '<' in self:\n                        self[1] = FLAG_TYPE\n                        while self[0] and ('/' in self or self.cKeywordQualifier(self[0])):\n                            self[1] = FLAG_TYPE\n                    else if '>' in self:\n                        self[1] = FLAG_TYPE\n                    else if '\"' in self:\n                        self.paintSimpleString()\n                    else if \"'\" in self:\n                        self.paintSingleString()\n                    else if '`' in self:\n                        self[1] = FLAG_STRING\n                        if self.paintJSFormatString(): return 2\n                    else if self[0]:\n                        self.skip()\n            else if self.state == 1:\n                if CHighlighter.paintCComment(self) == 1: return 1\n                return 0\n            else if self.state == 2:\n                if self.paintJSFormatString(): return 2\n                return 0\n            return None\n    return JavascriptHighlighter\n\nlet JavascriptHighlighter = makeit()\nbind(JavascriptHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/json.krk",
    "content": "from syntax import Highlighter, bind\n\nclass JsonHighlighter(Highlighter):\n    name = 'json'\n    extensions = ('.json',)\n    keywords = ['true','false','null']\n    def calculate():\n        while self[0]:\n            if self[0] == '\"':\n                let start = self.i\n                self.paintSimpleString()\n                let end = self.i\n                while self[0] == ' ': self.skip()\n                if self[0] == ':':\n                    self.rewind(end-start)\n                    self.paint(1, self.FLAG_ESCAPE)\n                    while self.i < end-1:\n                        self.paint(1, self.FLAG_KEYWORD)\n                    if self[0] == '\"':\n                        self.paint(1, self.FLAG_ESCAPE)\n                return 0\n            else if self[0] == '-' or self.isdigit(self[0]):\n                if self[0] == '-': self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == '0':\n                    self.paint(1, self.FLAG_NUMERAL)\n                else:\n                    while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == '.':\n                    self.paint(1, self.FLAG_NUMERAL)\n                    while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == 'e' or self[0] == 'E':\n                    self.paint(1, self.FLAG_NUMERAL)\n                    if self[0] == '+' or self[0] == '-':\n                        self.paint(1, self.FLAG_NUMERAL)\n                    while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL)\n            else if self.findKeywords(self.keywords, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                continue\n            else:\n                self.skip()\n                return 0\n        return None\n\nbind(JsonHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/krk.krk",
    "content": "import os\nfrom bim import getCommands\nfrom syntax import Highlighter, bind\nfrom syntax.doxygen import tryDoxygenComment\n\n@bind\nclass KrkHighlighter(Highlighter):\n    name = 'krk'\n    extensions = ('.krk',)\n    spaces = True\n\n    doxygenDocstrings = False\n    enableChecking = False\n    checkKrkCode = None\n\n    keywords = [\n        'and','class','def','else','for','if','in','import','let','not',\n        'or','return','while','try','except','raise','continue','break','as','from',\n        'elif', 'lambda', 'pass', 'with', 'is', 'del', 'assert', 'yield', 'finally',\n        'async', 'await',\n    ]\n\n    types = [\n        'self','super','len','str','int','float','dir','repr','list','dict','range',\n        'object','isinstance','type','print','tuple','bool','any','all',\n        'hex','ord','chr','bytes','set','getattr','setattr','input','zip','enumerate',\n        'property','staticmethod','classmethod','filter','min','max','id','map','bin',\n        'sum','sorted','issubclass','hasattr','delattr', 'NotImplemented', 'abs',\n    ]\n\n    special = [\n        'True', 'False', 'None'\n    ]\n\n    exceptions = [\n        'Exception', 'TypeError', 'ArgumentError', 'IndexError', 'KeyError',\n        'AttributeError', 'NameError', 'ImportError', 'IOError', 'ValueError',\n        'KeyboardInterrupt', 'ZeroDivisionError', 'NotImplementedError', 'SyntaxError',\n        'AssertionError',\n    ]\n\n    def paintString(self, strType, isTriple, isFormat=False):\n        while self[0] != None:\n            if self[0] == '\\\\' and self[1] == strType:\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == strType:\n                self.paint(1, self.FLAG_STRING)\n                if isTriple:\n                    if self[0] == strType and self[1] == strType:\n                        self.paint(2, self.FLAG_STRING)\n                        return 0\n                else:\n                    return 0\n            else if self[0] == '\\\\':\n                if self[1] == 'x':\n                    self.paint(4, self.FLAG_ESCAPE)\n                else if self[1] == 'u':\n                    self.paint(6, self.FLAG_ESCAPE)\n                else if self[1] == 'U':\n                    self.paint(10, self.FLAG_ESCAPE)\n                else if self[1] == None and not isTriple:\n                    self.paint(1, self.FLAG_ESCAPE)\n                    return 3 if strType == '\"' else (14 if isFormat else 4)\n                else if self[1] in '\\\\\\'\"abfnrtv[':\n                    self.paint(2, self.FLAG_ESCAPE)\n                else:\n                    self.paint(1, self.FLAG_STRING)\n            else if isFormat and self[0] == '{':\n                if self[1] == '}':\n                    self.paint(-1, self.FLAG_ERROR)\n                    return None\n                self.paint(1, self.FLAG_ESCAPE)\n                let x = 0\n                while self[0]:\n                    if self[0] == '{':\n                        x++\n                    else if self[0] == '}':\n                        if x == 0:\n                            self.paint(1, self.FLAG_ESCAPE)\n                            break\n                        x--\n                    else if self[0] == strType and not isTriple:\n                        self.paint(-1, self.FLAG_ERROR)\n                        return None\n                    else if self.findKeywords(self.keywords, self.FLAG_ESCAPE, self.cKeywordQualifier):\n                        continue\n                    else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                        continue\n                    else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier):\n                        continue\n                    self.paint(1, self.FLAG_NUMERAL)\n            else if self.doxygenDocstrings and isTriple and tryDoxygenComment(self, self.FLAG_STRING):\n                continue\n            else:\n                self.paint(1, self.FLAG_STRING)\n        if isTriple: return int(strType == \"'\") + (11 if isFormat else 1)\n        return 0\n\n    def paintNumeral(self):\n        if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'):\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isxdigit(self[0]):\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and (self[1] == 'o' or self[1] == 'O'):\n            self.paint(2, self.FLAG_NUMERAL)\n            while self[0] in '01234567':\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and (self[1] == 'b' or self[1] == 'B'):\n            self.paint(2, self.FLAG_NUMERAL)\n            while self[0] == '0' or self[0] == '1':\n                self.paint(1, self.FLAG_NUMERAL)\n        else:\n            while self.isdigit(self[0]):\n                self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == '.' and self.isdigit(self[1]):\n                self.paint(1, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]):\n                    self.paint(1, self.FLAG_NUMERAL)\n        return 0\n\n    def paintDoxyComment(self):\n        while self[0] is not None:\n            if tryDoxygenComment(self): continue\n            else if self.commentBuzzwords(): continue\n            else: self.paint(1, self.FLAG_COMMENT)\n\n    def calculate(self):\n        if self.enableChecking and self.i == 0: self.checkKrkCode()\n        if self.state <= 0:\n            if self[0] == '#':\n                self.paintDoxyComment()\n            else if self[0] == '@':\n                self.paint(1, self.FLAG_TYPE)\n                while self.cKeywordQualifier(self[0]):\n                    self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if self[0] == '\"' or self[0] == \"'\":\n                let strType = self[0]\n                if self[-1] == 'f':\n                    self.rewind(1)\n                    self.paint(1, self.FLAG_KEYWORD)\n                if self[1] == strType and self[2] == strType:\n                    self.paint(3, self.FLAG_STRING)\n                    return self.paintString(strType,True,self[-4]=='f')\n                self.paint(1, self.FLAG_STRING)\n                return self.paintString(strType,False,self[-2]=='f')\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier):\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                self.paintNumeral()\n                return 0\n            else if self[0] != None:\n                self.skip()\n                return 0\n        else if self.state >= 1:\n            return self.paintString(\"'\\\"\"[self.state % 2], (self.state % 10) <= 2, self.state > 10)\n        return None\n\n@bind\nclass BimcmdHighlighter(KrkHighlighter):\n    name = 'bimcmd'\n    extensions = ('.bim3rc',)\n\n    file_commands = ['e','tabnew','split','w','runscript','rundir']\n\n    def calculate(self):\n        if self.state <= 0 and self[-1] != '.':\n            if self.i == 0:\n                if self.findKeywords(self.file_commands, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                    if self[0] == ' ':\n                        while self[0] == ' ': self.skip()\n                        let filePath = self[0:]\n                        if filePath:\n                            if filePath.startswith('~/'):\n                                filePath = filePath.replace('~',os.environ.get('HOME','~'),1)\n                            try:\n                                os.stat(filePath)\n                                self.paint(-1, self.FLAG_DIFFPLUS)\n                            except:\n                                self.paint(-1, self.FLAG_DIFFMINUS)\n                        return -1\n            if self.findKeywords(getCommands(), self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n        return super().calculate()\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/latex.krk",
    "content": "from syntax import Highlighter, bind\n\nclass LatexHighlighter(Highlighter):\n    name = \"latex\"\n    extensions = ('.tex',)\n    spaces = True\n\n    def calculate(self):\n        while self[0]:\n            if self[0] == '%':\n                self.paintComment()\n                return None\n            else if self[0] == '\\\\':\n                if self.isalpha(self[1]):\n                    self.paint(2, self.FLAG_KEYWORD)\n                    while self.isalpha(self[0]):\n                        self.paint(1, self.FLAG_KEYWORD)\n                else:\n                    self.skip()\n            else:\n                self.skip()\n        return None\n\nbind(LatexHighlighter)\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/ld.krk",
    "content": "from syntax import Highlighter, bind\n\nclass LinkerScriptHighlighter(Highlighter):\n    name = \"ld\"\n    extensions = ('.ld',)\n    spaces = False\n\n    keywords = [\n        'INCLUDE','INPUT','GROUP','AS_NEEDED','OUTPUT','SEARCH_DIR',\n        'STARTUP','TARGET','REGION_ALIAS','ASSERT',\n        'EXTERN','FORCE_COMMON_ALLOCATION','INHIBIT_COMMON_ALLOCATION',\n        'FORCE_GROUP_ALLOCATION','INSERT','AFTER','BEFORE',\n        'NOCROSSREFS','NOCROSSREFS_TO','OUTPUT_ARCH','LD_FEATURE',\n        'HIDDEN','PROVIDE','PROVIDE_HIDDEN','SECTIONS','EXCLUDE_FILE',\n        'SORT_BY_NAME','SORT_BY_ALIGNMENT','KEEP','ENTRY','BLOCK','ALIGN',\n        'COMMON','DISCARD'\n        # Special:\n        # 'OUTPUT_FORMAT',\n    ]\n\n    types = [\n        'BYTE','LONG','SHORT','QUAD'\n    ]\n\n    bfd_names = [\n        'elf64-x86-64','elf32-i386',\n    ]\n\n    def paintCComment(self):\n        let last = None\n        while self[0] != None:\n            if self.commentBuzzwords(): continue\n            else if last == '*' and self[0] == '/':\n                self.paint(1, self.FLAG_COMMENT)\n                return 0\n            else:\n                last = self[0]\n                self.paint(1, self.FLAG_COMMENT)\n        return 1\n\n    def whitespaceThen(self, c):\n        while self[0] == ' ':\n            self.skip()\n        return self[0] == c\n\n    def bfdName(self, c):\n        return self.cKeywordQualifier(c) or c == '-'\n\n    def calculate(self):\n        let cond = self.state\n        if cond <= 0:\n            while self[0] is not None:\n                if self[0] == '/' and self[1] == '*':\n                    self.paint(2, self.FLAG_COMMENT)\n                    return self.paintCComment()\n                else if self.matchAndPaint(\"OUTPUT_FORMAT\", self.FLAG_KEYWORD, self.cKeywordQualifier):\n                    # Expect an argument\n                    if not self.whitespaceThen('('):\n                        self.paint(-1, self.FLAG_ERROR)\n                        return None\n                    self.skip(1)\n                    # A BFD name\n                    if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName):\n                        while self[0] is not None and self[0] != ',' and self[0] != ')':\n                            self.skip()\n                    if not (self.whitespaceThen(',') or self.whitespaceThen(')')):\n                        self.paint(-1, self.FLAG_ERROR)\n                        return None\n                    if self[0] == ')':\n                        self.skip()\n                        continue\n                    else:\n                        self.skip() # comma\n                    if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName):\n                        while self[0] is not None and self[0] != ',' and self[0] != ')':\n                            self.skip()\n                    if not self.whitespaceThen(','):\n                        self.paint(-1, self.FLAG_ERROR)\n                        return None\n                    self.skip() # comma\n                    if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName):\n                        while self[0] is not None and self[0] != ',' and self[0] != ')':\n                            self.skip()\n                    if not self.whitespaceThen(')'):\n                        self.paint(-1, self.FLAG_ERROR)\n                        return None\n                    self.skip() # ')'\n                else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                    return 0\n                else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                    return 0\n                else if self[0] == '.' and self.cKeywordQualifier(self[1]):\n                    self.paint(2,self.FLAG_TYPE)\n                    while self.cKeywordQualifier(self[0]) or self[0] == '-':\n                        self.paint(1,self.FLAG_TYPE)\n                else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                    if self[0] == '0' and self[1] == 'x':\n                        self.paint(2,self.FLAG_NUMERAL)\n                        while self[0] in '0123456789abcdefABCDEF':\n                            self.paint(1,self.FLAG_NUMERAL)\n                    else if self[0] == '0':\n                        self.paint(1,self.FLAG_NUMERAL)\n                        while self[0] in '01234567':\n                            self.paint(1,self.FLAG_NUMERAL)\n                    else:\n                        self.paint(1,self.FLAG_NUMERAL)\n                        while self.isdigit(self[0]):\n                            self.paint(1,self.FLAG_NUMERAL)\n                    if self[0] in 'kKM':\n                        self.paint(1, self.FLAG_NUMERAL)\n                else if self[0] is not None:\n                    self.skip()\n        else if cond == 1:\n            return self.paintCComment()\n        return None\n\nbind(LinkerScriptHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/lisp.krk",
    "content": "from syntax import Highlighter, bind\n\nclass LispHighlighter(Highlighter):\n    name = 'lisp'\n    extensions = ('.lisp','.lsp','.cl')\n    parens = [\n        Highlighter.FLAG_DIFFPLUS,\n        Highlighter.FLAG_TYPE,\n        Highlighter.FLAG_PRAGMA,\n        Highlighter.FLAG_KEYWORD,\n    ]\n    def parenFromState(i):\n        return self.parens[i % len(self.parens)]\n    def calculate():\n        if self.state == -1: self.state = 0\n        while self[0]:\n            if self[0] == ';':\n                self.paintComment()\n            else if self[0] == '(':\n                self.paint(1, self.parenFromState(self.state))\n                self << self.state + 1\n                while self[0] and self[0] not in ' ()':\n                    self.paint(1, self.FLAG_KEYWORD)\n            else if self[0] == ')':\n                if self.state == 0:\n                    self.paint(1, self.FLAG_ERROR)\n                else:\n                    self << self.state - 1\n                    self.paint(1, self.parenFromState(self.state))\n            else if self[0] == ':':\n                while self[0] and self[0] not in ' ()':\n                    self.paint(1, self.FLAG_PRAGMA)\n            else if self[0] == '\"':\n                self.paintSimpleString()\n            else if self[0]:\n                self.skip()\n        if self.state == 0: return None\n        if not self[0]: return self.state\n        return None\n\nbind(LispHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/lua.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass LuaHighlighter(Highlighter):\n    name = 'lua'\n    extensions = ('.lua',)\n    keywords = [\n        'and','break','do','else','elseif',\n        'end','for','function','if',\n        'in','local','not','or',\n        'repeat','return','then','until','while'\n    ]\n    constants = [\n        'false','nil','true'\n    ]\n    def calculate():\n        if self.state <= 0:\n            if self[0] == '-' and self[1] == '-':\n                self.paintComment()\n                return None\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                return 0\n            else if self[0] == \"'\":\n                self.paintSingleString()\n                return 0\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                CHighlighter.paintCNumeral(self)\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n            return None\n        return None\n\nbind(LuaHighlighter)\n\n"
  },
  {
    "path": "base/usr/share/bim/syntax/make.krk",
    "content": "from syntax import Highlighter, bind\n\nclass GnumakeHighlighter(Highlighter):\n    name = 'make'\n    extensions = ('Makefile','makefile','GNUmakefile','.mak','.mk')\n    commands = [\n        \"define\",\"endef\",\"undefine\",\"ifdef\",\"ifndef\",\"ifeq\",\"ifneq\",\"else\",\"endif\",\n        \"include\",\"sinclude\",\"override\",\"export\",\"unexport\",\"private\",\"vpath\",\n        \"-include\",\n    ]\n    functions = [\n        \"subst\",\"patsubst\",\"findstring\",\"filter\",\"filter-out\",\n        \"sort\",\"word\",\"words\",\"wordlist\",\"firstword\",\"lastword\",\n        \"dir\",\"notdir\",\"suffix\",\"basename\",\"addsuffix\",\"addprefix\",\n        \"join\",\"wildcard\",\"realpath\",\"abspath\",\"error\",\"warning\",\n        \"shell\",\"origin\",\"flavor\",\"foreach\",\"if\",\"or\",\"and\",\n        \"call\",\"eval\",\"file\",\"value\",\n    ]\n    targets = [\n        \"all\", # Not really special, but highlight it 'cause I feel like it.\n        \".PHONY\", \".SUFFIXES\", \".DEFAULT\", \".PRECIOUS\", \".INTERMEDIATE\",\n        \".SECONDARY\", \".SECONDEXPANSION\", \".DELETE_ON_ERROR\", \".IGNORE\",\n        \".LOW_RESOLUTION_TIME\", \".SILENT\", \".EXPORT_ALL_VARIABLES\",\n        \".NOTPARALLEL\", \".ONESHELL\", \".POSIX\",\n    ]\n    def commandQualifier(c):\n        if isinstance(c,int): c = chr(c) if c > 0 else '\\x00'\n        return self.isalnum(c) or c in '_-.'\n    def makeCloseParen():\n        self.paint(2, self.FLAG_TYPE)\n        self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier)\n        let i = 1\n        while self[0]:\n            if self[0] == '(':\n                i++\n                if self[-1] == '$':\n                    self.paint(1, self.FLAG_TYPE)\n                    self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier)\n                    continue\n            else if self[0] == ')':\n                i--\n                if i == 0:\n                    self.paint(1, self.FLAG_TYPE)\n                    return 0\n            else if self[0] == '\"':\n                self.paintSimpleString()\n            self.paint(1, self.FLAG_TYPE)\n        return 0\n    def makeCloseBrace():\n        self.paint(2, self.FLAG_TYPE)\n        while self[0]:\n            if self[0] == '}':\n                self.paint(1, self.FLAG_TYPE)\n                return 0\n            self.paint(1, self.FLAG_TYPE)\n        return 0\n    def makeVar():\n        if self[1] == '(':\n            self.makeCloseParen()\n        else if self[1] == '{':\n            self.makeCloseBrace()\n        else:\n            self.paint(2, self.FLAG_TYPE)\n    def paintString():\n        let strType = self[0]\n        self.paint(1, self.FLAG_STRING)\n        while self[0]:\n            if self[0] == strType and self[-1] != '\\\\':\n                self.paint(1, self.FLAG_STRING)\n                return\n            else if self[0] == '\\\\' and (self[1] == strType or strType == '\"'):\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == '$':\n                self.makeVar()\n            else:\n                self.paint(1, self.FLAG_STRING)\n    def variableOrComment(flag, next):\n        while self[0]:\n            if self[0] == '$':\n                self.makeVar()\n            else if self[0] == '#':\n                self.paintComment()\n            else if self[0] == \"'\":\n                self.paintString()\n            else if self[0] == '\"':\n                self.paintString()\n            else if self[0] == '\\\\' and not self[1]:\n                return next\n            else:\n                self.paint(1, flag)\n        return 0\n    def calculate():\n        if self.state == 2:\n            return self.variableOrComment(self.FLAG_NUMERAL, 2)\n        else if self.state == 3:\n            return self.variableOrComment(self.FLAG_NONE, 3)\n        if self.i == 0 and self[0] == '\\t':\n            self.skip()\n            if self[0] == '@':\n                self.paint(1, self.FLAG_KEYWORD)\n            return self.variableOrComment(self.FLAG_NUMERAL, 2)\n        else:\n            while self[0] == ' ': self.skip()\n            let whatisit\n            for i = 0; self[i]; i++:\n                if self[i] == ':' and self[i+1] != '=':\n                    whatisit = 1\n                    break\n                else if self[i] == '=':\n                    whatisit = 2\n                    break\n                else if self[i] == '#':\n                    break\n            if not whatisit:\n                while self[0]:\n                    if self[0] == '#':\n                        self.paintComment()\n                    else if self.findKeywords(self.commands, self.FLAG_KEYWORD, self.commandQualifier):\n                        continue\n                    else if self[0] == '$':\n                        self.variableOrComment(self.FLAG_NONE, 3)\n                    else:\n                        self.skip()\n            else if whatisit == 1: # rule\n                while self[0]:\n                    if self[0] == '#':\n                        self.paintComment()\n                    else if self[0] == ':':\n                        self.paint(1, self.FLAG_TYPE)\n                        self.variableOrComment(self.FLAG_NONE, 3)\n                    else if self.findKeywords(self.targets, self.FLAG_KEYWORD, self.commandQualifier):\n                        continue\n                    else:\n                        self.paint(1, self.FLAG_TYPE)\n            else if whatisit == 2: # variable\n                self.matchAndPaint('export', self.FLAG_KEYWORD, self.cKeywordQualifier)\n                while self[0] and self[0] not in '+=:?':\n                    self.paint(1, self.FLAG_TYPE)\n                while self[0] and self[0] != '=':\n                    self.skip()\n                return self.variableOrComment(self.FLAG_NONE, 3)\n        return None\n\nbind(GnumakeHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/man.krk",
    "content": "from syntax import Highlighter, bind\n\nclass ManpageHighlighter(Highlighter):\n    name = 'man'\n    extensions = ('.1','.2','.3','.4','.5','.6','.7','.8')\n    def calculate():\n        while self[0]:\n            if self.i == 0 and self[0] == '.':\n                if self[1] == 'S' and self[2] == 'H' and self[3] == ' ':\n                    self.paint(3, self.FLAG_KEYWORD)\n                    self.paint(-1, self.FLAG_STRING)\n                else if self[1] == 'B' and self[2] == ' ':\n                    self.paint(2, self.FLAG_KEYWORD)\n                    self.paint(-1, self.FLAG_BOLD)\n                else if self.isalpha(self[1]):\n                    self.paint(1, self.FLAG_KEYWORD)\n                    while self[0] and self.isalpha(self[0]):\n                        self.paint(1, self.FLAG_KEYWORD)\n                else if self[1] == '\\\\' and self[2] == '\"':\n                    self.paint(1, self.FLAG_COMMENT)\n                else:\n                    self.skip()\n            else if self[0] == '\\\\':\n                if self[1] == 'f':\n                    self.paint(2, self.FLAG_NUMERAL)\n                    self.paint(1, self.FLAG_PRAGMA)\n                else:\n                    self.paint(2, self.FLAG_ESCAPE)\n            else:\n                self.skip()\n        return None\n\nbind(ManpageHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/markdown.krk",
    "content": "from syntax import Highlighter, bind\n\nfrom syntax.c import CHighlighter\nfrom syntax.py import PythonHighlighter\nfrom syntax.krk import KrkHighlighter\nfrom syntax.java import JavaHighlighter\nfrom syntax.json import JsonHighlighter\nfrom syntax.xml import XMLHighlighter, HTMLHighlighter\nfrom syntax.diff import DiffHighlighter\nfrom syntax.bash import BashHighlighter\nfrom syntax.make import GnumakeHighlighter\n\nclass MarkdownHighlighter(Highlighter):\n    name = 'markdown'\n    extensions = ('.md',)\n    nestables = [\n        ('c', 100, CHighlighter),\n        ('py', 200, PythonHighlighter),\n        ('java', 300, JavaHighlighter),\n        ('json', 400, JsonHighlighter),\n        ('xml', 500, XMLHighlighter),\n        ('html', 600, HTMLHighlighter),\n        ('make', 700, GnumakeHighlighter),\n        ('diff', 800, DiffHighlighter),\n        ('bash', 900, BashHighlighter),\n        ('krk', 1000, KrkHighlighter),\n    ]\n    def nest(name, state, highlighter):\n        self << (0 if self.state < 1 else self.state - state)\n        let sub = highlighter(self)\n        while True:\n            let next = sub.calculate()\n            if next is None:\n                sub << -1\n                break\n            sub << next\n            if sub.state != 0: break\n        if not sub.state or sub.state == -1: return state\n        return sub.state + state\n    def calculate():\n        if self.state < 1:\n            if self.i == 0 and self[0] == '#':\n                while self[0] == '#': self.paint(1, self.FLAG_KEYWORD)\n                self.paint(-1, self.FLAG_BOLD)\n                return None\n            else if self.i == 0:\n                while self[0] == ' ': self.skip()\n                if self[0] == '`' and self[1] == '`' and self[2] == '`':\n                    self.paint(3, self.FLAG_STRING)\n                    for k,s,h in self.nestables:\n                        if self.matchPrefix(k):\n                            self.paint(len(k), self.FLAG_NUMERAL)\n                            return s\n                    return 1\n            if self[0] == '`':\n                self.paint(1, self.FLAG_STRING)\n                while self[0]:\n                    if self[0] == '`':\n                        self.paint(1, self.FLAG_STRING)\n                        return 0\n                    self.paint(1, self.FLAG_STRING)\n                return 0\n            else if self[0] == '[':\n                self.skip()\n                while self[0] and self[0] != ']':\n                    self.paint(1, self.FLAG_LINK)\n                if self[0] == ']':\n                    self.skip()\n                if self[0] == '(':\n                    self.skip()\n                    while self[0] and self[0] != ')':\n                        self.paint(1, self.FLAG_NUMERAL)\n                return 0\n            else if self[0] == '*' and self[1] == '*' and self.isalnum(self[2]) and not self.isalnum(self[-1]):\n                self.paint(2, self.FLAG_BOLD)\n                while self[0] and not (self[0] == '*' and self[1] == '*'):\n                    self.paint(1, self.FLAG_BOLD)\n                if self[0] == '*' and self[1] == '*':\n                    self.paint(2, self.FLAG_BOLD)\n                return 0\n            else if self[0] == '*' and self.isalnum(self[1]) and not self.isalnum(self[-1]):\n                self.paint(1, self.FLAG_COMMENT) # uh\n                while self[0] and self[0] != '*':\n                    self.paint(1, self.FLAG_COMMENT)\n                if self[0] == '*':\n                    self.paint(1, self.FLAG_COMMENT)\n                return 0\n            else if self[0] == '_' and self.isalnum(self[1]) and not self.isalnum(self[-1]):\n                self.paint(1, self.FLAG_COMMENT)\n                while self[0] and self[0] != '_':\n                    self.paint(1, self.FLAG_COMMENT)\n                if self[0] == '_':\n                    self.paint(1, self.FLAG_COMMENT)\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n            return None\n        else if self.state >= 1:\n            if self.i == 0:\n                for j=0;self[j];j++:\n                    if self[j] == '`' and self[j+1] == '`' and self[j+2] == '`':\n                        self.paint(j, self.FLAG_NONE)\n                        self.paint(3, self.FLAG_STRING)\n                        return None\n            if self.state == 1:\n                self.paint(-1, self.FLAG_STRING)\n                return 1\n            else:\n                for k,state,highlighter in self.nestables:\n                    if self.state < state + 99:\n                        return self.nest(k, state, highlighter)\n        return None\n\nbind(MarkdownHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/protobuf.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\nclass ProtobufHighlighter(Highlighter):\n    name = 'protobuf'\n    extensions = ('.proto',)\n    keywords = [\n        \"syntax\",\"import\",\"option\",\"package\",\"message\",\"group\",\"oneof\",\n        \"optional\",\"required\",\"repeated\",\"default\",\"extend\",\"extensions\",\"to\",\"max\",\"reserved\",\n        \"service\",\"rpc\",\"returns\",\"stream\",\n    ]\n    types = [\n        \"int32\",\"int64\",\"uint32\",\"uint64\",\"sint32\",\"sint64\",\n        \"fixed32\",\"fixed64\",\"sfixed32\",\"sfixed64\",\n        \"float\",\"double\",\"bool\",\"string\",\"bytes\",\n        \"enum\",\n    ]\n    special = ['true','false']\n    def calculate():\n        if self.state < 1:\n            if self[0] == '/' and self[1] == '/':\n                self.paintComment()\n            else if self[0] == '/' and self[1] == '*':\n                if CHighlighter.paintCComment(self) == 1: return 1\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                CHighlighter.paintCNumeral(self)\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n            return None\n        else:\n            if CHighlighter.paintCComment(self) == 1: return 1\n            return 0\n\nbind(ProtobufHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/py.krk",
    "content": "from syntax import Highlighter, bind\n\nclass PythonHighlighter(Highlighter):\n    name = 'python'\n    extensions = ('.py',)\n    spaces = True\n    keywords = [\n        \"class\",\"def\",\"return\",\"del\",\"if\",\"else\",\"elif\",\"for\",\"while\",\"continue\",\n        \"break\",\"assert\",\"as\",\"and\",\"or\",\"except\",\"finally\",\"from\",\"global\",\n        \"import\",\"in\",\"is\",\"lambda\",\"with\",\"nonlocal\",\"not\",\"pass\",\"raise\",\"try\",\"yield\",\n        \"async\",\"await\",\n    ]\n    types = [\n        \"abs\",\"all\",\"any\",\"ascii\",\"bin\",\"bool\",\"breakpoint\",\"bytes\",\n        \"bytearray\",\"callable\",\"compile\",\"complex\",\"delattr\",\"chr\",\n        \"dict\",\"dir\",\"divmod\",\"enumerate\",\"eval\",\"exec\",\"filter\",\"float\",\n        \"format\",\"frozenset\",\"getattr\",\"globals\",\"hasattr\",\"hash\",\"help\",\n        \"hex\",\"id\",\"input\",\"int\",\"isinstance\",\"issubclass\",\"iter\",\"len\",\n        \"list\",\"locals\",\"map\",\"max\",\"memoryview\",\"min\",\"next\",\"object\",\n        \"oct\",\"open\",\"ord\",\"pow\",\"print\",\"property\",\"range\",\"repr\",\"reverse\",\n        \"round\",\"set\",\"setattr\",\"slice\",\"sorted\",\"staticmethod\",\"str\",\"sum\",\n        \"super\",\"tuple\",\"type\",\"vars\",\"zip\",\n        'self',\n    ]\n    special = ['True','False','None']\n    def paintPyTriple(quote):\n        while self[0]:\n            if self[0] == quote:\n                self.paint(1, self.FLAG_STRING)\n                if self[0] == quote and self[1] == quote:\n                    self.paint(2, self.FLAG_STRING)\n                    return 0\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 1 if quote == '\"' else 2\n    def paintPyString(quote):\n        self.paint(1, self.FLAG_STRING)\n        while self[0]:\n            if self[0] == '\\\\' and self[1] == quote:\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == quote:\n                self.paint(1, self.FLAG_STRING)\n                return 0\n            else if self[0] == '\\\\':\n                self.paint(2, self.FLAG_ESCAPE)\n            else:\n                self.paint(1, self.FLAG_STRING)\n        return 0\n    def paintPyNumeral():\n        if self[0] == '0' and self[1] == 'x' or self[1] == 'X':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isxdigit(self[0]) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and self[1] == '.':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isdigit(self[0]) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n            if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'):\n                self.paint(2, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]) or self[0] == '_':\n                    self.paint(1, self.FLAG_NUMERAL)\n            else if self[0] == 'e' or self[0] == 'E':\n                self.paint(1, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]) or self[0] == '_':\n                    self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL)\n            return 0\n        else:\n            while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == '.':\n                self.paint(1, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL)\n                if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'):\n                    self.paint(2, self.FLAG_NUMERAL)\n                    while self.isdigit(self[0]) or self[0] == '_':\n                        self.paint(1, self.FLAG_NUMERAL)\n                else if self[0] == 'e' or self[0] == 'E':\n                    self.paint(1, self.FLAG_NUMERAL)\n                    while self.isdigit(self[0]) or self[0] == '_':\n                        self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL)\n                return 0\n        while self[0] == 'l' or self[0] == 'L': self.paint(1, self.FLAG_NUMERAL)\n        return 0\n    def paintPyFString(quote):\n        self.paint(1, self.FLAG_STRING)\n        while self[0]:\n            if self[0] == '\\\\' and self[1] == quote:\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == quote:\n                self.paint(1, self.FLAG_STRING)\n                return\n            else if self[0] == '\\\\':\n                self.paint(2, self.FLAG_ESCAPE)\n            else if self[0] == '{':\n                self.paint(1, self.FLAG_NUMERAL)\n                if self[0] == '}':\n                    self.rewind(1)\n                    self.paint(2, self.FLAG_ERROR)\n                else:\n                    while self[0] and self[0] != '}':\n                        self.paint(1, self.FLAG_NUMERAL)\n                    self.paint(1, self.FLAG_NUMERAL)\n            else:\n                self.paint(1, self.FLAG_STRING)\n    def calculate():\n        if self.state < 1:\n            if self[0] == '#':\n                self.paintComment()\n            else if self.i == 0 and self.matchAndPaint('import', self.FLAG_PRAGMA, self.cKeywordQualifier):\n                return 0\n            else if self[0] == '@':\n                self.paint(1, self.FLAG_PRAGMA)\n                while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA)\n                return 0\n            else if self[0] == '\"':\n                if self[1] == '\"' and self[2] == '\"':\n                    self.paint(3, self.FLAG_STRING)\n                    return self.paintPyTriple('\"')\n                else if self[-1] == 'f':\n                    self.rewind(1)\n                    self.paint(1, self.FLAG_TYPE)\n                    self.paintPyFString('\"')\n                    return 0\n                else:\n                    self.paintPyString('\"')\n                    return 0\n            else if self[0] == \"'\":\n                if self[1] == \"'\" and self[2] == \"'\":\n                    self.paint(3, self.FLAG_STRING)\n                    return self.paintPyTriple(\"'\")\n                else if self[-1] == 'f':\n                    self.rewind(1)\n                    self.paint(1, self.FLAG_TYPE)\n                    self.paintPyFString(\"'\")\n                    return 0\n                else:\n                    self.paintPyString(\"'\")\n                    return 0\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier):\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                self.paintPyNumeral()\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n        else if self.state == 1:\n            return self.paintPyTriple('\"')\n        else if self.state == 2:\n            return self.paintPyTriple(\"'\")\n        return None\n\nbind(PythonHighlighter)\n"
  },
  {
    "path": "base/usr/share/bim/syntax/rust.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.c import CHighlighter\n\n@bind\nclass RustHighlighter(Highlighter):\n    name = 'rust'\n    extensions = ('.rs',)\n    keywords = [\n        \"as\",\"break\",\"const\",\"continue\",\"crate\",\"else\",\"enum\",\"extern\",\n        \"false\",\"fn\",\"for\",\"if\",\"impl\",\"in\",\"let\",\"loop\",\"match\",\"mod\",\n        \"move\",\"mut\",\"pub\",\"ref\",\"return\",\"Self\",\"self\",\"static\",\"struct\",\n        \"super\",\"trait\",\"true\",\"type\",\"unsafe\",\"use\",\"where\",\"while\",\n    ]\n    types = [\n        \"bool\",\"char\",\"str\",\n        \"i8\",\"i16\",\"i32\",\"i64\",\n        \"u8\",\"u16\",\"u32\",\"u64\",\n        \"isize\",\"usize\",\n        \"f32\",\"f64\",\n    ]\n    def paintRustComment():\n        while self[0]:\n            if self.commentBuzzwords(): continue\n            else if self[0] == '*' and self[1] == '/':\n                self.paint(2, self.FLAG_COMMENT)\n                self << self.state - 1\n                if self.state == 0: return 0\n            else if self[0] == '/' and self[1] == '*':\n                self << self.state + 1\n                self.paint(2, self.FLAG_COMMENT)\n            else:\n                self.paint(1, self.FLAG_COMMENT)\n        return self.state\n    def paintRustNumeral():\n        if self[0] == '0' and self[1] == 'b':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self[0] == '0' or self[0] == '1' or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and self[1] == 'o':\n            self.paint(2, self.FLAG_NUMERAL)\n            while (ord(self[0]) >= ord('0') and ord(self[0]) < ord('7')) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and self[1] == 'x':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isxdigit(self[0]) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n        else if self[0] == '0' and self[1] == '.':\n            self.paint(2, self.FLAG_NUMERAL)\n            while self.isdigit(self[0]) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n        else:\n            while self.isdigit(self[0]) or self[0] == '_':\n                self.paint(1, self.FLAG_NUMERAL)\n            if self[0] == '.':\n                self.paint(1, self.FLAG_NUMERAL)\n                while self.isdigit(self[0]) or self[0] == '_':\n                    self.paint(1, self.FLAG_NUMERAL)\n        return 0\n    def calculate():\n        if self.state == -1 or self.state == 0:\n            if self[0] == '/' and self[1] == '/':\n                self.paintComment()\n            else if self[0] == '/' and self[1] == '*':\n                self.paint(2, self.FLAG_COMMENT)\n                self.state = 1\n                return self.paintRustComment()\n            else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier):\n                return 0\n            else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier):\n                return 0\n            else if self[0] == '\\\"':\n                self.paintSimpleString()\n                return 0\n            else if self[0] == '\\'' and self[1] == 's' and self[2] == 't':\n                self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if self[0] == '\\'':\n                CHighlighter.paintCChar(self)\n                return 0\n            else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]):\n                self.paintRustNumeral()\n                return 0\n            else if self[0]:\n                self.skip()\n                return 0\n        else:\n            return self.paintRustComment()\n"
  },
  {
    "path": "base/usr/share/bim/syntax/xml.krk",
    "content": "from syntax import Highlighter, bind\nfrom syntax.css import CSSHighlighter\n\nclass XMLHighlighter(Highlighter):\n    name = 'xml'\n    extensions = ('.xml','.iml')\n    def paintXmlComment():\n        while self[0]:\n            if self[0] == '-' and self[1] == '-' and self[2] == '>':\n                self.paint(3, self.FLAG_COMMENT)\n                return 0\n            else:\n                if self.commentBuzzwords(): continue\n                self.paint(1, self.FLAG_COMMENT)\n        return 4\n    def paintTagString():\n        if self[0] == '\"':\n            self.paint(1, self.FLAG_STRING)\n            return 2\n        else:\n            self.paintSimpleString()\n            if not self[0] and self[-1] != '\"':\n                return 3\n        return None\n    def paintInsideTag():\n        while self[0]:\n            if self[0] == '>':\n                self.paint(1, self.FLAG_TYPE)\n                return 0\n            else if self[0] == '\"':\n                self.paintSimpleString()\n                if not self[0] and self[-1] != '\"':\n                    return 3\n            else:\n                self.paint(1, self.FLAG_TYPE)\n        return 2\n    def paintTag():\n        while self[0]:\n            if self[0] == '/': self.paint(1, self.FLAG_TYPE)\n            if self[0] == '?': self.paint(1, self.FLAG_TYPE)\n            if self[0] == ' ' or self[0] == '\\t': self.skip()\n            if self.isalnum(self[0]):\n                while self.isalnum(self[0]) or self[0] == '-':\n                    self.paint(1, self.FLAG_KEYWORD)\n                if not self[0]: return 2\n                return self.paintInsideTag()\n            else:\n                self.paint(1, self.FLAG_TYPE)\n        return None\n    def calculate():\n        if self.state < 1:\n            while self[0] and self[0] != '<':\n                self.skip()\n            if not self[0]: return None\n            if self[0] == '<' and self[1] == '!' and self[2] == '-' and self[3] == '-':\n                self.paint(4, self.FLAG_COMMENT)\n                return self.paintXmlComment()\n            self.paint(1, self.FLAG_TYPE)\n            return self.paintTag()\n        else if self.state == 1:\n            return self.paintTag()\n        else if self.state == 2:\n            return self.paintInsideTag()\n        else if self.state == 3:\n            return self.paintTagString()\n        else if self.state == 4:\n            return self.paintXmlComment()\n        return None\n\n\nbind(XMLHighlighter)\n\n'''\nGeneral proof of concept for an HTML highlighter that does nested CSS.\nCould be extended to do nested JS, probably.\nDoesn't actually do either right now, but demonstrates how it could be done.\n'''\nclass HTMLHighlighter(XMLHighlighter):\n    name = 'html'\n    extensions = ('.html','.htm')\n    def paintTag():\n        if self.matchAndPaint('style', self.FLAG_KEYWORD, self.cKeywordQualifier):\n            return self.paintInsideStyleTag()\n        return super().paintTag()\n    def paintInsideStyleTag():\n        let r = self.paintInsideTag()\n        if r == 2:\n            return 5\n        else:\n            return self.paintCss()\n    def paintCss():\n        while self[0] and not (self[0] == '<' and self[1] == '/' and self[2] == 's'):\n            self << 0\n            let css = CSSHighlighter(self)\n            let result = css.calculate()\n            if result == 0:\n                self.__init__(css)\n                continue\n            else if result == 1:\n                return 7\n            else if result == None:\n                return 6\n        if (self[0] == '<' and self[1] == '/' and self[2] == 's'):\n            return self.paintTag()\n        return 6\n    def calculate():\n        if self.state < 5:\n            return super().calculate()\n        else if self.state == 5:\n            return self.paintInsideStyleTag()\n        else if self.state == 6:\n            return self.paintCss()\n        else if self.state == 7:\n            self << 1\n            let css = CSSHighlighter(self)\n            if css.calculate() == 1: return 7\n            self.__init__(css)\n            return 6\n\nbind(HTMLHighlighter)\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/__init__.krk",
    "content": ""
  },
  {
    "path": "base/usr/share/bim/themes/ansi.krk",
    "content": "from bim import defineTheme\n\n@defineTheme\ndef ansi():\n    setcolor(\"text-fg\",\"@7\")\n    setcolor(\"text-bg\",\"@0\")\n    setcolor(\"alternate-fg\",\"@5\")\n    setcolor(\"alternate-bg\",\"@9\")\n    setcolor(\"number-fg\",\"@3\")\n    setcolor(\"number-bg\",\"@9\")\n    setcolor(\"status-fg\",\"@7\")\n    setcolor(\"status-bg\",\"@4\")\n    setcolor(\"status-alt\",\"@4\")\n    setcolor(\"tabbar-bg\",\"@4\")\n    setcolor(\"tab-bg\",\"@4\")\n    setcolor(\"keyword\",\"@4\")\n    setcolor(\"string\",\"@2\")\n    setcolor(\"comment\",\"@5\")\n    setcolor(\"type\",\"@3\")\n    setcolor(\"pragma\",\"@1\")\n    setcolor(\"numeral\",\"@1\")\n    setcolor(\"error-fg\",\"@7\")\n    setcolor(\"error-bg\",\"@1\")\n    setcolor(\"search-fg\",\"@0\")\n    setcolor(\"search-bg\",\"@3\")\n    setcolor(\"select-bg\",\"@7\")\n    setcolor(\"select-fg\",\"@0\")\n    setcolor(\"red\",\"@1\")\n    setcolor(\"green\",\"@2\")\n    setcolor(\"bold\",\"@7\")\n    setcolor(\"link\",\"@4\")\n    setcolor(\"escape\",\"@2\")\n    if checkprop(\"can_bright\") == 0: # TODO These should return True/False, not 0 for success\n        setcolor(\"text-fg\",\"@17\")\n        setcolor(\"text-bg\",\"@9\")\n        setcolor(\"alternate-fg\",\"@10\")\n        setcolor(\"status-fg\",\"@17\")\n        setcolor(\"status-alt\",\"@14\")\n        setcolor(\"keyword\",\"@14\")\n        setcolor(\"comment\",\"@10\")\n        setcolor(\"error-fg\",\"@17\")\n        setcolor(\"search-bg\",\"@13\")\n        setcolor(\"select-bg\",\"@17\")\n        setcolor(\"link\",\"@14\")\n        setcolor(\"escape\",\"@12\")\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/citylights.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n@defineTheme\ndef citylights():\n    if checkprop('can_24bit'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\",\"2;151;178;198\")\n    setcolor(\"text-bg\",\"2;29;37;44\")\n    setcolor(\"alternate-fg\",\"2;45;55;65\")\n    setcolor(\"alternate-bg\",\"2;33;42;50\")\n    setcolor(\"number-fg\",\"2;71;89;103\")\n    setcolor(\"number-bg\",\"2;37;47;56\")\n    setcolor(\"status-fg\",\"2;151;178;198\")\n    setcolor(\"status-bg\",\"2;53;67;78\")\n    setcolor(\"status-alt\",\"2;116;144;166\")\n    setcolor(\"tabbar-bg\",\"2;37;47;56\")\n    setcolor(\"tab-bg\",\"2;29;37;44\")\n    setcolor(\"keyword\",\"2;94;196;255\")\n    setcolor(\"string\",\"2;83;154;252\")\n    setcolor(\"comment\",\"2;107;133;153;3\")\n    setcolor(\"type\",\"2;139;212;156\")\n    setcolor(\"pragma\",\"2;0;139;148\")\n    setcolor(\"numeral\",\"2;207;118;132\")\n    setcolor(\"error-fg\",\"5;15\")\n    setcolor(\"error-bg\",\"5;196\")\n    setcolor(\"search-fg\",\"5;234\")\n    setcolor(\"search-bg\",\"5;226\")\n    setcolor(\"select-fg\",\"2;29;37;44\")\n    setcolor(\"select-bg\",\"2;151;178;198\")\n    setcolor(\"red\",\"2;222;53;53\")\n    setcolor(\"green\",\"2;55;167;0\")\n    setcolor(\"bold\",\"2;151;178;198;1\")\n    setcolor(\"link\",\"2;94;196;255\")\n    setcolor(\"escape\",\"2;133;182;249\")\n"
  },
  {
    "path": "base/usr/share/bim/themes/light.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n# A light color scheme\n# Based on selenized by Jan Warchoł\n@defineTheme\ndef light():\n    if checkprop('can_24bit'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\",\"2;57;76;82\")\n    setcolor(\"text-bg\",\"2;250;242;218\")\n    setcolor(\"alternate-fg\",\"2;144;153;149\")\n    setcolor(\"alternate-bg\",\"2;236;227;204\")\n    setcolor(\"number-fg\",\"2;57;76;82\")\n    setcolor(\"number-bg\",\"2;212;204;181\")\n    setcolor(\"status-fg\",\"2;82;102;109\")\n    setcolor(\"status-bg\",\"2;212;204;181\")\n    setcolor(\"status-alt\",\"2;129;61;19\")\n    setcolor(\"tab-bg\",\"2;212;204;181\")\n    setcolor(\"tabbar-bg\",\"2;236;227;204\")\n    setcolor(\"error-fg\",\"5;15\")\n    setcolor(\"error-bg\",\"5;196\")\n    setcolor(\"search-fg\",\"5;234\")\n    setcolor(\"search-bg\",\"5;226\")\n    setcolor(\"keyword\",\"2;0;114;212;1\")\n    setcolor(\"string\",\"2;72;145;0\")\n    setcolor(\"comment\",\"2;144;153;149;3\")\n    setcolor(\"type\",\"2;193;92;29\")\n    setcolor(\"pragma\",\"2;210;33;45;1\")\n    setcolor(\"numeral\",\"2;202;72;152;1\")\n    setcolor(\"select-fg\",\"2;213;205;182\")\n    setcolor(\"select-bg\",\"2;144;153;149\")\n    setcolor(\"red\",\"2;222;53;53\")\n    setcolor(\"green\",\"2;55;167;0\")\n    setcolor(\"bold\",\"2;57;76;82;1\")\n    setcolor(\"link\",\"2;0;114;212\")\n    setcolor(\"escape\",\"2;0;156;143\")\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/solarized.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n@defineTheme\ndef solarized_dark():\n    if checkprop('can_24bit'):\n        return ansi()\n    setcolor(\"text-fg\",\"2;147;161;161\")\n    setcolor(\"text-bg\",\"2;0;43;54\")\n    setcolor(\"alternate-fg\",\"2;147;161;161\")\n    setcolor(\"alternate-bg\",\"2;7;54;66\")\n    setcolor(\"number-fg\",\"2;131;148;149\")\n    setcolor(\"number-bg\",\"2;7;54;66\")\n    setcolor(\"status-fg\",\"2;131;148;150\")\n    setcolor(\"status-bg\",\"2;7;54;66\")\n    setcolor(\"status-alt\",\"2;133;153;0\")\n    setcolor(\"tabbar-bg\",\"2;7;54;66\")\n    setcolor(\"tab-bg\",\"2;131;148;150\")\n    setcolor(\"keyword\",\"2;133;153;0\")\n    setcolor(\"string\",\"2;42;161;152\")\n    setcolor(\"comment\",\"2;101;123;131\")\n    setcolor(\"type\",\"2;181;137;0\")\n    setcolor(\"pragma\",\"2;203;75;22\")\n    setcolor(\"numeral\",\"2;220;50;47\")\n    setcolor(\"error-fg\",\"5;15\")\n    setcolor(\"error-bg\",\"5;196\")\n    setcolor(\"search-fg\",\"5;234\")\n    setcolor(\"search-bg\",\"5;226\")\n    setcolor(\"select-fg\",\"2;0;43;54\")\n    setcolor(\"select-bg\",\"2;147;161;161\")\n    setcolor(\"red\",\"2;222;53;53\")\n    setcolor(\"green\",\"2;55;167;0\")\n    setcolor(\"bold\",\"2;147;161;161;1\")\n    setcolor(\"link\",\"2;42;161;152\")\n    setcolor(\"escape\",\"2;133;153;0\")\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/strawberry.krk",
    "content": "'''\nBased on 'strawberry' vim theme:\n@ref https://github.com/haystackandroid/strawberry\n'''\nfrom bim import defineTheme\nfrom themes import ansi\n\n@defineTheme\ndef strawberry():\n    if checkprop('can_24bit'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\", \"2;117;97;107\")\n    setcolor(\"text-bg\", \"2;255;240;247\")\n    setcolor(\"alternate-fg\", \"2;158;139;149\")\n    setcolor(\"alternate-bg\", \"2;252;238;244\")\n    setcolor(\"number-bg\", \"2;240;221;230\")\n    setcolor(\"number-fg\", \"2;158;139;149\")\n    setcolor(\"status-fg\", \"2;255;240;247\")\n    setcolor(\"status-bg\", \"2;224;162;177\")\n    setcolor(\"status-alt\", \"2;255;255;255;1\")\n    setcolor(\"tabbar-bg\", \"2;224;162;177\")\n    setcolor(\"tab-bg\", \"2;240;221;230\")\n    setcolor(\"keyword\", \"2;33;158;33;1\")\n    setcolor(\"string\", \"2;70;141;212\")\n    setcolor(\"comment\", \"2;158;139;149;3\")\n    setcolor(\"type\", \"2;212;106;132;1\")\n    setcolor(\"pragma\", \"2;162;111;191;1\")\n    setcolor(\"numeral\", \"2;70;141;212\")\n    setcolor(\"error-fg\", \"5;15\")\n    setcolor(\"error-bg\", \"5;196\")\n    setcolor(\"search-fg\", \"5;234\")\n    setcolor(\"search-bg\", \"2;224;162;177\")\n    setcolor(\"select-fg\", \"2;0;43;54\")\n    setcolor(\"select-bg\", \"2;147;161;161\")\n    setcolor(\"red\", \"2;222;53;53\")\n    setcolor(\"green\", \"2;55;167;0\")\n    setcolor(\"bold\", \"2;117;97;107;1\")\n    setcolor(\"link\", \"2;51;162;230;4\")\n    setcolor(\"escape\", \"2;113;203;173\")\n\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/sunsmoke.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n@defineTheme\ndef sunsmoke256():\n    if checkprop('can_256color'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\",\"5;188\")\n    setcolor(\"text-bg\",\"5;234\")\n    setcolor(\"alternate-fg\",\"5;244\")\n    setcolor(\"alternate-bg\",\"5;236\")\n    setcolor(\"number-fg\",\"5;101\")\n    setcolor(\"number-bg\",\"5;232\")\n    setcolor(\"status-fg\",\"5;188\")\n    setcolor(\"status-bg\",\"5;59\")\n    setcolor(\"status-alt\",\"5;244\")\n    setcolor(\"tabbar-bg\",\"5;59\")\n    setcolor(\"tab-bg\",\"5;59\")\n    setcolor(\"keyword\",\"5;74\")\n    setcolor(\"string\",\"5;71\")\n    setcolor(\"comment\",\"5;102\")\n    setcolor(\"type\",\"5;221\")\n    setcolor(\"pragma\",\"5;160\")\n    setcolor(\"numeral\",\"5;161\")\n    setcolor(\"error-fg\",\"5;15\")\n    setcolor(\"error-bg\",\"5;196\")\n    setcolor(\"search-fg\",\"5;234\")\n    setcolor(\"search-bg\",\"5;226\")\n    setcolor(\"select-fg\",\"5;17\")\n    setcolor(\"select-bg\",\"5;109\")\n    setcolor(\"red\",\"@1\")\n    setcolor(\"green\",\"@2\")\n    setcolor(\"bold\",\"5;188;1\")\n    setcolor(\"link\",\"5;74;4\")\n    setcolor(\"escape\",\"5;79\")\n\n@defineTheme\ndef sunsmoke():\n    if checkprop('can_24bit'):\n        colorscheme(\"sunsmoke256\")\n        return\n    setcolor(\"text-fg\", \"2;230;230;230\")\n    setcolor(\"text-bg\", \"2;31;31;31\")\n    setcolor(\"alternate-fg\", \"2;122;122;122\")\n    setcolor(\"alternate-bg\", \"2;46;43;46\")\n    setcolor(\"number-fg\", \"2;150;139;57\")\n    setcolor(\"number-bg\", \"2;0;0;0\")\n    setcolor(\"status-fg\", \"2;230;230;230\")\n    setcolor(\"status-bg\", \"2;71;64;58\")\n    setcolor(\"status-alt\", \"2;122;122;122\")\n    setcolor(\"tabbar-bg\", \"2;71;64;58\")\n    setcolor(\"tab-bg\", \"2;71;64;58\")\n    setcolor(\"keyword\", \"2;51;162;230\")\n    setcolor(\"string\", \"2;72;176;72\")\n    setcolor(\"comment\", \"2;158;153;129;3\")\n    setcolor(\"type\", \"2;230;206;110\")\n    setcolor(\"pragma\", \"2;194;70;54\")\n    setcolor(\"numeral\", \"2;230;43;127\")\n    setcolor(\"error-fg\", \"5;15\")\n    setcolor(\"error-bg\", \"5;196\")\n    setcolor(\"search-fg\", \"5;234\")\n    setcolor(\"search-bg\", \"5;226\")\n    setcolor(\"select-fg\", \"2;0;43;54\")\n    setcolor(\"select-bg\", \"2;147;161;161\")\n    setcolor(\"red\", \"2;222;53;53\")\n    setcolor(\"green\", \"2;55;167;0\")\n    setcolor(\"bold\", \"2;230;230;230;1\")\n    setcolor(\"link\", \"2;51;162;230;4\")\n    setcolor(\"escape\", \"2;113;203;173\")\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/tiff.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n@defineTheme\ndef tiff():\n    if checkprop('can_24bit'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\", \"2;255;255;255\")\n    setcolor(\"text-bg\", \"2;23;0;30\")\n    setcolor(\"alternate-fg\", \"2;36;22;67\")\n    setcolor(\"alternate-bg\", \"2;20;0;27\")\n    setcolor(\"number-fg\", \"2;156;152;178\")\n    setcolor(\"number-bg\", \"2;23;0;30\")\n    setcolor(\"status-fg\", \"2;255;255;255\")\n    setcolor(\"status-bg\", \"2;30;100;190\")\n    setcolor(\"status-alt\", \"2;230;230;230\")\n    setcolor(\"tabbar-bg\", \"2;32;0;44\")\n    setcolor(\"tab-bg\", \"2;53;28;62\")\n    setcolor(\"keyword\", \"2;254;222;93\")\n    setcolor(\"string\", \"2;105;206;96\")\n    setcolor(\"comment\", \"2;130;130;130;3\")\n    setcolor(\"type\", \"2;97;175;239\")\n    setcolor(\"pragma\", \"2;105;128;247\")\n    setcolor(\"numeral\", \"2;213;128;71\")\n    setcolor(\"error-fg\", \"5;15\")\n    setcolor(\"error-bg\", \"5;196\")\n    setcolor(\"search-fg\", \"5;234\")\n    setcolor(\"search-bg\", \"5;226\")\n    setcolor(\"select-fg\", \"2;0;43;54\")\n    setcolor(\"select-bg\", \"2;147;161;161\")\n    setcolor(\"red\", \"2;213;128;71\")\n    setcolor(\"green\", \"2;55;167;0\")\n    setcolor(\"bold\", \"2;230;230;230;1\")\n    setcolor(\"link\", \"2;51;162;230;4\")\n    setcolor(\"escape\", \"2;113;203;173\")\n\n"
  },
  {
    "path": "base/usr/share/bim/themes/wombat.krk",
    "content": "from bim import defineTheme\nfrom themes import ansi\n\n# Wombat 256-color theme\n@defineTheme\ndef wombat():\n    if checkprop('can_256color'):\n        colorscheme(\"ansi\")\n        return\n    setcolor(\"text-fg\",\"5;230\")\n    setcolor(\"text-bg\",\"5;235\")\n    setcolor(\"alternate-fg\",\"5;244\")\n    setcolor(\"alternate-bg\",\"5;236\")\n    setcolor(\"number-bg\",\"5;232\")\n    setcolor(\"number-fg\",\"5;101\")\n    setcolor(\"status-fg\",\"5;230\")\n    setcolor(\"status-bg\",\"5;238\")\n    setcolor(\"status-alt\",\"5;186\")\n    setcolor(\"tabbar-bg\",\"5;230\")\n    setcolor(\"tab-bg\",\"5;248\")\n    setcolor(\"keyword\",\"5;117\")\n    setcolor(\"string\",\"5;113\")\n    setcolor(\"comment\",\"5;102\")\n    setcolor(\"type\",\"5;186\")\n    setcolor(\"pragma\",\"5;173\")\n    setcolor(\"numeral\",\"5;173\")\n    setcolor(\"error-fg\",\"5;15\")\n    setcolor(\"error-bg\",\"5;196\")\n    setcolor(\"search-fg\",\"5;234\")\n    setcolor(\"search-bg\",\"5;226\")\n    setcolor(\"select-fg\",\"5;235\")\n    setcolor(\"select-bg\",\"5;230\")\n    setcolor(\"red\",\"@1\")\n    setcolor(\"green\",\"@2\")\n    setcolor(\"bold\",\"5;230;1\")\n    setcolor(\"link\",\"5;117\")\n    setcolor(\"escape\",\"5;194\")\n    if not checkprop('can_italic'):\n        setcolor('comment','5;102;3')\n\n"
  },
  {
    "path": "base/usr/share/help/0_index.trt",
    "content": "<h1>Welcome!</h1>\n\nWelcome to the <b>Help Browser</b>. This is a simple rich text document viewer. It is incomplete.\nTo view the documentation for an application, select the \"Contents\" entry from the \"Help\" menu, when available.\n"
  },
  {
    "path": "base/usr/share/help/calculator.trt",
    "content": "<h1>Calculator</h1>\n\nA simple calculator powered by Kuroko.<br />\nUse the keyboard or the provided buttons to enter calculations as Kuroko expressions.<br />\nSingle-line statements such as imports are also supported. For more math functionality, <mono>import math</mono>.\n"
  },
  {
    "path": "base/usr/share/help/file-browser.trt",
    "content": "<h1>File Browser</h1>\n\nProvides a graphical representation of the file system.<br />\n\nDouble click to navigate through directories or open files.<br />\n\nA right-click context menu provides additional options.\n"
  },
  {
    "path": "base/usr/share/help/help-browser.trt",
    "content": "<h1>Help Browser</h1>\n\nDisplays HTML-escape markup files describing applications.<br />\nThis application is incomplete. A more featureful HTML rendering engine will eventually replace it.\n"
  },
  {
    "path": "base/usr/share/help/package-manager.trt",
    "content": "<h1>Package Manager</h1>\n\nGraphical interface to the msk command-line utility.<br />\n\nDouble click packages to install them. Dependencies will be installed automatically.\n"
  },
  {
    "path": "base/usr/share/help/terminal.trt",
    "content": "<h1>Terminal</h1>\n\nTerminal emulator providing extensive ANSI escape sequence support.<br />\n\nSupports most xterm-compatible escape squences, as well as 24-bit color.<br />\n\nText display supports anti-aliased and bitmap fonts.\n"
  },
  {
    "path": "boot/README.md",
    "content": "# ToaruOS Bootloader\n\nThis is version 4.0 of the ToaruOS Live CD bootloader.\n\nThe bootloader targets both BIOS and EFI. The BIOS loader is limited to El Torito CD boot, while the EFI loader should work in most configurations.\n\nThe bootloader provides a menu for selecting boot options, a simple editor for further customization of the kernel command line, and a 32-bit ELF loader for loading multiboot-compatible builds of Misaka.\n\nWhile much of the codebase is shared between the two platforms, the BIOS loader works very differently from the EFI loader. It includes a minimal ISO 9660 filesystem implementation for locating boot files, and also loads the entire contents of the boot medium into memory before entering protected mode and displaying the menu.\n\nThe EFI loader, meanwhile, is built for x86-64 (\"x64\" in MS/EFI terms), and runs as a normal EFI application in long mode to display the menu, load the kernel and ramdisk through EFI filesystem access APIs, and load it into memory. It then downgrades to protected mode to allow the kernel's multiboot entrypoint to execute (which then returns to long mode again).\n\nWhile the loader implements a subset of Multiboot functionality, it is likely not suited for general use by other multiboot kernels and is tailored specifically for loading Misaka.\n\n"
  },
  {
    "path": "boot/boot.S",
    "content": ".code16\nmain:\n\tljmp $0x0,$entry\n\nentry:\n\t/* Set up initial segments */\n\txor %ax, %ax\n\tmov %ax, %ds\n\tmov %ax, %ss\n\n\t/* Don't lose dl */\n\tmov %dl, boot_disk\n\n\t/* Initialize stack to just below us */\n\tmov $0x7c00, %ax\n\tmov %ax, %sp\n\n\t/* Prepare to switch to unreal mode */\n\tcli\n\tpush %ds\n\tpush %es\n\n\t/* Enable A20 */\n\tin $0x92, %al\n\tor $2, %al\n\tout %al, $0x92\n\n\t/* Switch to unreal mode */\n\tlgdtw gdtr\n\tmov %cr0, %eax\n\tor $1, %al\n\tmov %eax, %cr0\n\tjmp pmode\npmode:\n\tmov $0x10, %bx\n\tmov %bx, %ds\n\tmov %bx, %es\n\tand $0xfe, %al\n\tmov %eax, %cr0\n\tjmp unrealmode\nunrealmode:\n\tpop %es\n\tpop %ds\n\n\t/* Clear the screen */\n\tmov  $0, %al\n\tmovl $3840, %ecx\n\tmovl $0xb8000, %edi\n\taddr32 rep stosb\n\n\t/* Check if we can actually go to long mode on this */\n\tmov $0x80000001, %eax\n\tcpuid\n\tand $0x20000000, %edx\n\tjnz can_long\n\n\tmovl $str_Need_long, %esi\n\tcall print_string\n\tjmp _oh_no\n\ncan_long:\n\t/* Spot check memory */\n\tmovl $0x12345678, %eax\n\tmovl $0x5000000, %ebx\n\tmovl %eax, (%ebx)\n\tmovl (%ebx), %edx\n\tcmp %edx, %eax\n\tjz good_memory\n\n\tmovl $str_More_mem, %esi\n\tcall print_string\n\n_oh_no:\n\tjmp _oh_no\n\ngood_memory:\n\t/* Ask for drive params */\n\tmov $0x48, %ah\n\tmov boot_disk, %dl\n\tmov $drive_params, %si\n\tint $0x13\n\n.extern _bss_start\n\nboot_from_cd:\n\t/* Collect information on lower memory. */\n\tmov $0x500, %ax\n\tmov %ax, %es\n\tclc\n\tint $0x12\n\tmov %ax, lower_mem\n\n\t/* Collect information on upper memory. */\n\tmov $0x0, %di\n\tcall do_e820\n\tjc hang\n\n\t/* Get video mode info */\n\tmov $0, %ax\n\tmov %ax, %es\n\tmov $vbe_cont_info, %di\n\tmov $0x4F00, %ax\n\tint $0x10\n\n\t/* Actually switch to protected mode. */\n\tmov %cr0, %eax\n\tor $1, %eax\n\tmov %eax, %cr0\n\n\tmov $0x10, %ax\n\tmov %ax, %ds\n\tmov %ax, %es\n\tmov %ax, %fs\n\tmov %ax, %gs\n\tmov %ax, %ss\n\n\tcli\n\n.global bios_main\n\tljmp $0x08,$bios_main\n\nhang:\n\tjmp hang\n\ndo_e820:\n\txor %ebx, %ebx\n\txor %bp, %bp\n\tmov $0x534D4150, %edx\n\tmov $0xe820, %eax\n\tmovl $0x1,%es:20(%di)\n\tmov $24, %ecx\n\tint $0x15\n\tjb  do_e820.failed\n\tmov $0x534D4150, %edx\n\tcmp %edx, %eax\n\tjne do_e820.failed\n\ttest %ebx, %ebx\n\tje  do_e820.failed\n\tjmp do_e820.jmpin\ndo_e820.e820lp:\n\tmov $0xe820, %eax\n\tmovl $0x1,%es:20(%di)\n\tmov $24, %ecx\n\tint $0x15\n\tjb do_e820.e820f\n\tmov $0x534D4150, %edx\ndo_e820.jmpin:\n\tjcxz do_e820.skipent\n\tcmp $20, %cl\n\tjbe do_e820.notext\n\ttestb $0x1, %es:20(%di)\n\tje do_e820.skipent\ndo_e820.notext:\n\tmov %es:8(%di), %ecx\n\tor %es:12(%di), %ecx\n\tjz do_e820.skipent\n\tinc %bp\n\tadd $24, %di\ndo_e820.skipent:\n\ttest %ebx, %ebx\n\tjne do_e820.e820lp\ndo_e820.e820f:\n\tmov %bp, mmap_ent\n\tclc\n\tret\ndo_e820.failed:\n\tstc\n\tret\n\nprint_string:\n\tmovl $0xb8000, %edi\nprint_string.loop:\n\tmovb (%esi), %ah\n\tcmp $0, %ah\n\tje print_string.exit\n\tmovb %ah, (%edi)\n\tinc %edi\n\tmovb $7, (%edi)\n\tinc %esi\n\tinc %edi\n\tjmp print_string.loop\nprint_string.exit:\n\tret\n\npm_stack:\n\t.quad 0\n\n.global do_bios_call\ndo_bios_call:\n.code32\n\t/* Standard function entry point stuff */\n\tpush %ebp\n\tmov  %esp, %ebp\n\tpush %eax\n\tpush %ebx\n\tpush %ecx\n\tpush %edx\n\tpush %esi\n\tpush %edi\n\t/* Save stack because bios might mess it up? */\n\tmovl %esp, %eax\n\tmovl %eax, (pm_stack)\n\n\t/* Prepare intermediary mode */\n\tmov $0x20, %ax\n\tmov %ax, %ds\n\tmov %ax, %es\n\tmov %ax, %fs\n\tmov %ax, %gs\n\tmov %ax, %ss\n\n\t/* Enable intermediary mode */\n\tljmp $0x18,$do_bios_call.0\n\ndo_bios_call.0:\n.code16\n\t/* Disable protected mode */\n\tmov %cr0, %eax\n\tand $~1, %eax\n\tmov %eax, %cr0\n\n\t/* Jump to deactivate protected mode */\n\tljmp $0x0,$do_bios_call.1\n\ndo_bios_call.1:\n\t/* Set up real mode segments */\n\txor %ax, %ax\n\tmov %ax, %ds\n\tmov %ax, %es\n\tmov %ax, %fs\n\tmov %ax, %gs\n\tmov %ax, %ss\n\n\t/* Enable interrupts while BIOS is active */\n\tsti\n\n\t/* Function switch */\n\tmovl 32(%esp), %ebx\n\n\t/* 1: Read disk */\n\tmov $0x01, %ax\n\tcmp %bx, %ax\n\tje do_bios_call.read_disk\n\n\t/* 2: Query mode index */\n\tmov $0x02, %ax\n\tcmp %bx, %ax\n\tje do_bios_call.query_mode\n\n\t/* 3: Set mode */\n\tmov $0x03, %ax\n\tcmp %bx, %ax\n\tje do_bios_call.set_mode\n\n\tmov $0x04, %ax\n\tcmp %bx, %ax\n\tje do_bios_call.test_key\n\n\tmov $0x05, %ax\n\tcmp %bx, %ax\n\tje do_bios_call.set_font\n\n\t/* Else: Bad call, jump to loop. */\n\tjmp do_bios_call.done\n\ndo_bios_call.read_disk:\n\tmov $0x42, %ah     /* Extended read */\n\tmov boot_disk, %dl /* Using our boot disk */\n\tmov $dap, %si      /* From the DAP below */\n\tint $0x13\n\tjmp do_bios_call.done\n\ndo_bios_call.query_mode:\n\tmovl 36(%esp), %ecx\n\tmov $0x0, %ax\n\tmov %ax, %es\n\tmov $vbe_info, %edi\n\tmov $0x4F01, %ax\n\tint $0x10\n\tjmp do_bios_call.done\n\ndo_bios_call.set_mode:\n\tmovl 36(%esp), %ebx\n\tmov $0x4F02, %ax\n\tint $0x10\n\tjmp do_bios_call.done\n\ndo_bios_call.test_key:\n\tmovl 36(%esp), %ebx\n\txor %ax, %ax\n\tmov %bl, %ah\n\tint $0x16\n\tmovl %eax, 20(%esp)\n\tjmp do_bios_call.done\n\ndo_bios_call.set_font:\n\tmovl 36(%esp), %ebp /* address of font data into ebp */\n\tmov $0x1100, %ax /* mode = load user-defined font */\n\tmov $17, %bh /* 17 bytes (rows) per character */\n\tmov $0, %bl  /* font block 0 */\n\tmov $0, %dx  /* starting from char 0 */\n\tmov $0x100, %cx /* write 256 glyphs */\n\tint $0x10\n\tjmp do_bios_call.done\n\ndo_bios_call.done:\n\t/* Disable interrupts again */\n\tcli\n\n\t/* Restore data segment, gdt */\n\txor %ax,%ax\n\tmov %ax, %ds\n\tlgdtw gdtr\n\n\t/* Enable protected mode */\n\tmov %cr0, %eax\n\tor $1, %eax\n\tmov %eax, %cr0\n\n\t/* Jump to activate protected mode */\n\tljmp $0x08,$do_bios_call.2\n\ndo_bios_call.2:\n.code32\n\t/* Restore protected mode data segments */\n\tmov $0x10, %ax\n\tmov %ax, %ds\n\tmov %ax, %es\n\tmov %ax, %fs\n\tmov %ax, %gs\n\tmov %ax, %ss\n\n\t/* Restore stack */\n\tmovl (pm_stack), %eax\n\tmovl %eax, %esp\n\n\t/* Pop callee-saved registers we may messed with */\n\tpop %edi\n\tpop %esi\n\tpop %edx\n\tpop %ecx\n\tpop %ebx\n\tpop %eax\n\tpop %ebp\n\n\t/* Return */\n\tret\n\n.align 8\ngdtr:\n\t.word gdt_end - gdt_base - 1\n\t.long gdt_base\n\ngdt_base:\n\t.quad 0\n\t.word 0xFFFF\n\t.word 0\n\t.byte 0\n\t.byte 0x9a\n\t.byte 0xcf\n\t.byte 0\n\n\t.word 0xffff\n\t.word 0\n\t.byte 0\n\t.byte 0x92\n\t.byte 0xcf\n\t.byte 0\n\n\t.word 0xffff\n\t.word 0\n\t.byte 0\n\t.byte 0x9e\n\t.byte 0\n\t.byte 0\n\n\t.word 0xffff\n\t.word 0\n\t.byte 0\n\t.byte 0x92\n\t.byte 0\n\t.byte 0\ngdt_end:\n\n.global boot_disk\nboot_disk:\n\t.byte 0\n\n.global mmap_ent\nmmap_ent:\n\t.byte 0\n\t.byte 0\n\n.global lower_mem\nlower_mem:\n\t.byte 0\n\t.byte 0\n\n.align 4\n.global dap\ndap:\n\t.byte 16\n\t.byte 0 /* always 0 */\n.global dap_sectors\ndap_sectors:\n\t.word 1\n.global dap_buffer\ndap_buffer:\n\t.long 0x0\n.global dap_lba_low\ndap_lba_low:\n\t.long 0\n.global dap_lba_high\ndap_lba_high:\n\t.long 0\n\n.align 4\ndrive_params:\n\t.word 0x1A\n\t.word 0 /* flags */\n\t.long 0 /* cylinders */\n\t.long 0 /* heads */\n\t.long 0 /* sectors */\n\t.quad 0 /* total sectors */\n.global drive_params_bps\ndrive_params_bps:\n\t.word 0 /* bytes per sector */\n\n.align 4\n.global vbe_info\nvbe_info:\n\t.word 0 /* attributes */\n\t.word 0 /* old shit (window a/b) */\n\t.word 0 /* Granulatory of banks, don't care. */\n\t.word 0 /* Window size, don't care. */\n\t.long 0 /* Segments... */\n\t.long 0 /* old bank switching thing */\n.global vbe_info_pitch\nvbe_info_pitch:\n\t.word 0 /* PITCH */\n.global vbe_info_width\nvbe_info_width:\n\t.word 0 /* WIDTH */\n.global vbe_info_height\nvbe_info_height:\n\t.word 0 /* HEIGHT */\n\t.word 0 /* w, y */\n\t.byte 0 /* planes */\n.global vbe_info_bpp\nvbe_info_bpp:\n\t.byte 0 /* bits per pixel */\n\t.byte 0 /* banks */\n\t.byte 0 /* Memory model */\n\t.byte 0 /* bank size */\n\t.byte 0 /* pages */\n\t.byte 0 /* reserved */\n\t.byte 0 /* RED mask */\n\t.byte 0 /* RED offset */\n\t.byte 0 /* GREEN mask */\n\t.byte 0 /* GREEN offset */\n\t.byte 0 /* BLUE maask */\n\t.byte 0 /* BLUE offset */\n\t.byte 0 /* ALPHA mask */\n\t.byte 0 /* ALPHA offset */\n\t.byte 0 /* Color attributes */\n.global vbe_info_fbaddr\nvbe_info_fbaddr:\n\t.long 0 /* Framebuffer address */\n\t.long 0 /* Extra memory offset */\n\t.word 0 /* Extra memory size */\n\t.zero 206 /* Other crap */\n\n.align 4\nvbe_cont_info:\n\t.ascii \"VBE2\"\n\t.word 0x200\n\t.long 0\n\t.long 0 /* caps */\n.global vbe_cont_info_mode_off\nvbe_cont_info_mode_off:\n\t.word 0 /* MODES */\nvbe_cont_info_mode_seg:\n\t.word 0\n\t.zero 494\n\nstr_Need_long:\n\t.asciz \"ToaruOS 2.0 requires a 64-bit processor.\"\nstr_More_mem:\n\t.asciz \"ToaruOS 2.0 needs at least 128MiB of RAM, and 1GiB is recommended.\"\n\n.global disk_space\ndisk_space:\n\t.zero 2048\n"
  },
  {
    "path": "boot/config.c",
    "content": "/**\n * @brief Shared bootloader configuration.\n *\n * Sets up menus that present the boot options for both the EFI\n * and BIOS loaders. If you want to tweak ToaruOS's bootloader\n * to boot some other Multiboot1-compliant OS, start here.\n *\n * This is also the place to add new default startup configs,\n * add toggles for command line options, and so on.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n\n#include \"options.h\"\n#include \"util.h\"\n#include \"menu.h\"\n#include \"text.h\"\n#include \"multiboot.h\"\n#include \"editor.h\"\n\n/* Basic text strings */\n#define BASE_VERSION \"ToaruOS Bootloader v5.0\"\nchar * VERSION_TEXT = BASE_VERSION\n#ifdef EFI_PLATFORM\n\t\" (EFI)\";\n#else\n\t\" (BIOS)\";\n#endif\nchar * HELP_TEXT = \"<Enter> to boot, <e> to edit, or select a menu option with \\030/\\031/\\032/\\033.\";\nchar * HELP_TEXT_OPT = \"<Enter> to toggle, or select another option with \\030/\\031/\\032/\\033.\";\nchar * COPYRIGHT_TEXT = \"ToaruOS is free software under the NCSA license.\";\nchar * LINK_TEXT = \"https://toaruos.org - https://github.com/klange/toaruos\";\n\n/* Boot command line strings */\n#define DEFAULT_ROOT_CMDLINE \"root=/dev/ram0 \"\n#define DEFAULT_GRAPHICAL_CMDLINE \"start=live-session \"\n#define DEFAULT_SINGLE_CMDLINE \"start=\\\"terminal -F\\\" \"\n#define DEFAULT_TEXT_CMDLINE \"start=--vga vid=text \"\n#define DEFAULT_VID_CMDLINE \"vid=auto \"\n#define MIGRATE_CMDLINE \"migrate \"\n#define DEFAULT_HEADLESS_CMDLINE \"start=--headless \"\n\nchar * kernel_path = \"KERNEL.\";\nchar * ramdisk_path = \"RAMDISK.IGZ\";\nchar cmdline[1024] = {0};\n\n/* Names of the available boot modes. */\nstruct bootmode boot_mode_names[] = {\n\t{1, \"normal\",   \"Normal Boot\"},\n\t{2, \"video\",    \"Configure Video Output\"},\n\t{3, \"single\",   \"Single-User Graphical Terminal\"},\n\t{4, \"headless\", \"Headless\"},\n#ifndef EFI_PLATFORM\n\t{5, \"vga\",      \"VGA Text Mode\"},\n#endif\n};\n\nint base_sel = 0;\n\nint kmain() {\n\tBOOT_SET();\n\n\tBOOT_OPTION(_debug,       0, \"Debug output\",\n\t\t\t\"Enable debug output in the bootloader and enable the\",\n\t\t\t\"serial debug log in the operating system itself.\");\n\n\tBOOT_OPTION(_smp,         1, \"Enable SMP\",\n\t\t\t\"SMP support may not be completely stable and can be\",\n\t\t\t\"disabled with this option if desired.\");\n\n\tBOOT_OPTION(_vbox,        1, \"VirtualBox Guest Additions\",\n\t\t\t\"Enable integration with VirtualBox, including\",\n\t\t\t\"automatic mode setting and absolute mouse pointer.\");\n\n\tBOOT_OPTION(_vboxrects,   0, \"VirtualBox Seamless support\",\n\t\t\t\"(Requires Guest Additions) Enables support for the\",\n\t\t\t\"Seamless Desktop mode in VirtualBox.\");\n\n\tBOOT_OPTION(_vboxpointer, 1, \"VirtualBox Pointer\",\n\t\t\t\"(Requires Guest Additions) Enables support for the\",\n\t\t\t\"VirtualBox hardware pointer mapping.\");\n\n\tBOOT_OPTION(_vmware,      1, \"VMWare driver\",\n\t\t\t\"Enable the VMware / QEMU absolute mouse pointer,\",\n\t\t\t\"and optional guest scaling.\");\n\n\tBOOT_OPTION(_vmwareres,   1, \"VMware guest size\",\n\t\t\t\"(Requires VMware driver) Enables support for\",\n\t\t\t\"automatically setting display size in VMware\");\n\n\tBOOT_OPTION(_qemubug,     0, \"QEMU PS/2 workaround\",\n\t\t\t\"Work around a bug in QEMU's PS/2 controller\",\n\t\t\t\"prior to 6.0.50.\");\n\n\tBOOT_OPTION(_migrate,     1, \"Writable root\",\n\t\t\t\"Migrates the ramdisk from tarball to an in-memory\",\n\t\t\t\"temporary filesystem at boot. Needed for packages.\");\n\n\tBOOT_OPTION(_lfbwc,       1, \"WC framebuffer\",\n\t\t\t\"Enables write-combining PAT configuration for\",\n\t\t\t\"framebuffers. Toggle if graphics are slow.\");\n\n\tBOOT_OPTION(_kaslr,       1, \"KASLR (experimental)\",\n\t\t\t\"Enables rudimentary randomization of the kernel\",\n\t\t\t\"load address within a small range.\");\n\n\twhile (1) {\n\t\t/* Loop over rendering the menu */\n\t\tshow_menu();\n\n\t\tif (boot_mode == 2) {\n\t\t\textern int video_menu(void);\n\t\t\tvideo_menu();\n\t\t\tboot_edit = 0;\n\t\t\tmemset(cmdline, 0, 1024);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Build our command line. */\n\t\tstrcat(cmdline, DEFAULT_ROOT_CMDLINE);\n\n\t\tif (_migrate) {\n\t\t\tstrcat(cmdline, MIGRATE_CMDLINE);\n\t\t}\n\n\t\tchar * _video_command_line = DEFAULT_VID_CMDLINE;\n\n\t\tif (boot_mode == 1) {\n\t\t\tstrcat(cmdline, DEFAULT_GRAPHICAL_CMDLINE);\n\t\t\tstrcat(cmdline, _video_command_line);\n\t\t} else if (boot_mode == 3) {\n\t\t\tstrcat(cmdline, DEFAULT_SINGLE_CMDLINE);\n\t\t\tstrcat(cmdline, _video_command_line);\n\t\t} else if (boot_mode == 4) {\n\t\t\tstrcat(cmdline, DEFAULT_HEADLESS_CMDLINE);\n\t\t} else if (boot_mode == 5) {\n\t\t\tstrcat(cmdline, DEFAULT_TEXT_CMDLINE);\n\t\t}\n\n\t\tif (_debug) {\n\t\t\ttxt_debug = 1;\n\t\t\tstrcat(cmdline, \"debug \");\n\t\t}\n\n\t\tif (!_vbox) {\n\t\t\tstrcat(cmdline, \"novbox \");\n\t\t}\n\n\t\tif (_vbox && !_vboxrects) {\n\t\t\tstrcat(cmdline, \"novboxseamless \");\n\t\t}\n\n\t\tif (_vbox && !_vboxpointer) {\n\t\t\tstrcat(cmdline, \"novboxpointer \");\n\t\t}\n\n\t\tif (_vmware && !_vmwareres) {\n\t\t\tstrcat(cmdline, \"novmwareresset \");\n\t\t}\n\n\t\tif (!_smp) {\n\t\t\tstrcat(cmdline, \"nosmp \");\n\t\t}\n\n\t\tif (_qemubug) {\n\t\t\tstrcat(cmdline, \"sharedps2 \");\n\t\t}\n\n\t\tif (_lfbwc) {\n\t\t\tstrcat(cmdline, \"lfbwc \");\n\t\t}\n\n\t\textern int disable_kaslr;\n\t\tdisable_kaslr = !_kaslr;\n\n\t\tif (!boot_edit) break;\n\t\tif (boot_editor()) break;\n\n\t\tboot_edit = 0;\n\t\tmemset(cmdline, 0, 1024);\n\t}\n\n\tboot();\n\n\twhile (1) {}\n\treturn 0;\n}\n"
  },
  {
    "path": "boot/editor.c",
    "content": "/**\n * @brief Command line editor.\n *\n * Very rudimentary command line editor so options can be\n * tweaked. Has a couple of nice features like being\n * able to move the cursor. Not intended to be all that\n * robust, and needs to work in EFI and BIOS.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdint.h>\n#include \"options.h\"\n#include \"text.h\"\n#include \"util.h\"\n#include \"kbd.h\"\n\nint boot_edit = 0;\n\nstatic uint16_t attribute_cache[80*25] = {0};\n\nstatic void draw_text(int cursor, int len) {\n\tint i = 0;\n\tfor (int _my_y = 0; _my_y < 25; ++_my_y) {\n\t\tfor (int _my_x = 0; _my_x < 80; ++_my_x) {\n\t\t\tint ch = (i < len) ? cmdline[i] : ' ';\n\t\t\tint attr = (i == cursor) ? 0x70 : 0x07;\n\t\t\tuint16_t combined = (attr << 8) | (ch & 0xFF);\n\n\t\t\tif (attribute_cache[i] != combined) {\n\t\t\t\tmove_cursor(_my_x, _my_y);\n\t\t\t\tset_attr(attr);\n\t\t\t\tprint_((char[]){ch,'\\0'});\n\t\t\t\tattribute_cache[i] = combined;\n\t\t\t}\n\n\t\t\ti++;\n\t\t}\n\t}\n}\n\nint boot_editor(void) {\n\tint len    = strlen(cmdline);\n\tint cursor = len;\n\tint data = 0;\n\n\tmemset(attribute_cache, 0, sizeof(attribute_cache));\n\n\twhile (1) {\n\t\tdraw_text(cursor, len);\n\n\t\tint status;\n\t\tdo {\n\t\t\tstatus = read_key(&data);\n\t\t} while (status == 1);\n\n\t\tif (status == 0) {\n\t\t\t/* Handle a few special characters */\n\t\t\tif (data == '\\n') {\n\t\t\t\treturn 1;\n\t\t\t} else if (data == 27) {\n\t\t\t\treturn 0;\n\t\t\t} else if (data == '\\b') {\n\t\t\t\tif (!cursor) continue;\n\t\t\t\tif (cursor == len) {\n\t\t\t\t\tcmdline[len-1] = '\\0';\n\t\t\t\t\tcursor--;\n\t\t\t\t\tlen--;\n\t\t\t\t} else {\n\t\t\t\t\tcmdline[cursor-1] = '\\0';\n\t\t\t\t\tstrcat(cmdline,&cmdline[cursor]);\n\t\t\t\t\tcursor--;\n\t\t\t\t\tlen--;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (len > 1022) continue;\n\t\t\t\t/* Move everything from the cursor onward forward */\n\t\t\t\tif (cursor < len) {\n\t\t\t\t\tint x = len + 1;\n\t\t\t\t\twhile (x > cursor) {\n\t\t\t\t\t\tcmdline[x] = cmdline[x-1];\n\t\t\t\t\t\tx--;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcmdline[cursor] = data;\n\t\t\t\tlen++;\n\t\t\t\tcursor++;\n\t\t\t}\n\t\t} else if (status == 2) {\n\t\t\t/* Left */\n\t\t\tif (cursor) cursor--;\n\t\t} else if (status == 3) {\n\t\t\t/* Right */\n\t\t\tif (cursor < len) cursor++;\n\t\t} else if (status == 4) {\n\t\t\t/* Shift-left: Word left */\n\t\t\twhile (cursor && cmdline[cursor] == ' ') cursor--;\n\t\t\twhile (cursor && cmdline[cursor] != ' ') cursor--;\n\t\t} else if (status == 5) {\n\t\t\t/* Shift-right: Word right */\n\t\t\twhile (cursor < len && cmdline[cursor] == ' ') cursor++;\n\t\t\twhile (cursor < len && cmdline[cursor] != ' ') cursor++;\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "boot/editor.h",
    "content": "#pragma once\n\nextern int boot_edit;\nint boot_editor(void);\n"
  },
  {
    "path": "boot/elf.h",
    "content": "#pragma once\n#define ELFMAG0   0x7f\n#define ELFMAG1   'E'\n#define ELFMAG2   'L'\n#define ELFMAG3   'F'\n#define EI_NIDENT 16\n\ntypedef uint32_t Elf32_Word;\ntypedef uint32_t Elf32_Addr;\ntypedef uint32_t Elf32_Off;\ntypedef uint32_t Elf32_Sword;\ntypedef uint16_t Elf32_Half;\n\n/*\n * ELF Header\n */\ntypedef struct {\n\tunsigned char e_ident[EI_NIDENT];\n\tElf32_Half    e_type;\n\tElf32_Half    e_machine;\n\tElf32_Word    e_version;\n\tElf32_Addr    e_entry;\n\tElf32_Off     e_phoff;\n\tElf32_Off     e_shoff;\n\tElf32_Word    e_flags;\n\tElf32_Half    e_ehsize;\n\tElf32_Half    e_phentsize;\n\tElf32_Half    e_phnum;\n\tElf32_Half    e_shentsize;\n\tElf32_Half    e_shnum;\n\tElf32_Half    e_shstrndx;\n} Elf32_Header;\n\n/*\n * e_type\n */\n\n#define ET_NONE   0     /* No file type */\n#define ET_REL    1     /* Relocatable file */\n#define ET_EXEC   2     /* Executable file */\n#define ET_DYN    3     /* Shared object file */\n#define ET_CORE   4     /* Core file */\n#define ET_LOPROC 0xff0 /* [Processor Specific] */\n#define ET_HIPROC 0xfff /* [Processor Specific] */\n\n/*\n * Machine types\n */\n#define EM_NONE  0\n#define EM_386   3\n\n#define EV_NONE    0\n#define EV_CURRENT 1\n\n/** Program Header */\ntypedef struct {\n\tElf32_Word p_type;\n\tElf32_Off  p_offset;\n\tElf32_Addr p_vaddr;\n\tElf32_Addr p_paddr;\n\tElf32_Word p_filesz;\n\tElf32_Word p_memsz;\n\tElf32_Word p_flags;\n\tElf32_Word p_align;\n} Elf32_Phdr;\n\n/* p_type values */\n#define PT_NULL    0 /* Unused, skip me */\n#define PT_LOAD    1 /* Loadable segment */\n#define PT_DYNAMIC 2 /* Dynamic linking information */\n#define PT_INTERP  3 /* Interpreter (null-terminated string, pathname) */\n#define PT_NOTE    4 /* Auxillary information */\n#define PT_SHLIB   5 /* Reserved. */\n#define PT_PHDR    6 /* Oh, it's me. Hello! Back-reference to the header table itself */\n#define PT_LOPROC  0x70000000\n#define PT_HIPROC  0x7FFFFFFF\n\n\n/** Section Header */\ntypedef struct {\n\tElf32_Word sh_name;\n\tElf32_Word sh_type;\n\tElf32_Word sh_flags;\n\tElf32_Addr sh_addr;\n\tElf32_Off  sh_offset;\n\tElf32_Word sh_size;\n\tElf32_Word sh_link;\n\tElf32_Word sh_info;\n\tElf32_Word sh_addralign;\n\tElf32_Word sh_entsize;\n} Elf32_Shdr;\n\ntypedef struct {\n\tuint32_t  id;\n\tuint32_t  ptr;\n} Elf32_auxv;\n\ntypedef struct {\n\tElf32_Word st_name;\n\tElf32_Addr st_value;\n\tElf32_Word st_size;\n\tunsigned char st_info;\n\tunsigned char st_other;\n\tElf32_Half st_shndx;\n} Elf32_Sym;\n\ntypedef struct {\n\tElf32_Addr r_offset;\n\tElf32_Word r_info;\n} Elf32_Rel;\n\ntypedef struct {\n\tElf32_Sword d_tag;\n\tunion {\n\t\tElf32_Word      d_val;\n\t\tElf32_Addr      d_ptr;\n\t\tElf32_Off       d_off;\n\t} d_un;\n} Elf32_Dyn;\n\n/* sh_type values */\n#define SHT_NONE     0\n#define SHT_PROGBITS 1\n#define SHT_SYMTAB   2\n#define SHT_STRTAB   3\n#define SHT_NOBITS   8\n#define SHT_REL      9\n\n#define ELF32_R_SYM(i)    ((i) >> 8)\n#define ELF32_R_TYPE(i)   ((unsigned char)(i))\n#define ELF32_R_INFO(s,t) (((s) << 8) + (unsigned char)(t))\n\n#define ELF32_ST_BIND(i)   ((i) >> 4)\n#define ELF32_ST_TYPE(i)   ((i) & 0xf)\n#define ELF32_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf))\n\n#define STB_LOCAL  0\n#define STB_GLOBAL 1\n#define STB_WEAK   2\n#define STB_NUM    3\n\n#define STB_LOPROC 13\n#define STB_HIPROC 15\n\n#define STT_NOTYPE  0\n#define STT_OBJECT  1\n#define STT_FUNC    2\n#define STT_SECTION 3\n#define STT_FILE    4\n#define STT_COMMON  5\n#define STT_TLS     6\n#define STT_NUM     7\n\n#define STT_LOPROC  13\n#define STT_HIPROC  15\n\n\n"
  },
  {
    "path": "boot/iso9660.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\ntypedef struct {\n\tchar year[4];\n\tchar month[2];\n\tchar day[2];\n\tchar hour[2];\n\tchar minute[2];\n\tchar second[2];\n\tchar hundredths[2];\n\tint8_t timezone;\n} __attribute__((packed)) iso_9660_datetime_t;\n\ntypedef struct {\n\tuint8_t year;\n\tuint8_t month;\n\tuint8_t day;\n\tuint8_t hour;\n\tuint8_t minute;\n\tuint8_t second;\n\tint8_t timezone;\n} __attribute__((packed)) iso_9660_rec_date_t;\n\ntypedef struct {\n\tuint8_t length;\n\tuint8_t ext_length;\n\n\tuint32_t extent_start_LSB;\n\tuint32_t extent_start_MSB;\n\n\tuint32_t extent_length_LSB;\n\tuint32_t extent_length_MSB;\n\n\tiso_9660_rec_date_t record_date;\n\n\tuint8_t flags;\n\tuint8_t interleave_units;\n\tuint8_t interleave_gap;\n\n\tuint16_t volume_seq_LSB;\n\tuint16_t volume_seq_MSB;\n\n\tuint8_t name_len;\n\tchar name[];\n} __attribute__((packed)) iso_9660_directory_entry_t;\n\ntypedef struct {\n\tuint8_t type; /* 0x01 */\n\tchar id[5]; /* CD001 */\n\n\tuint8_t version;\n\tuint8_t _unused0;\n\n\tchar system_id[32];\n\tchar volume_id[32];\n\n\tuint8_t _unused1[8];\n\n\tuint32_t volume_space_LSB;\n\tuint32_t volume_space_MSB;\n\n\tuint8_t _unused2[32];\n\n\tuint16_t volume_set_LSB;\n\tuint16_t volume_set_MSB;\n\n\tuint16_t volume_seq_LSB;\n\tuint16_t volume_seq_MSB;\n\n\tuint16_t logical_block_size_LSB;\n\tuint16_t logical_block_size_MSB;\n\n\tuint32_t path_table_size_LSB;\n\tuint32_t path_table_size_MSB;\n\n\tuint32_t path_table_LSB;\n\tuint32_t optional_path_table_LSB;\n\n\tuint32_t path_table_MSB;\n\tuint32_t optional_path_table_MSB;\n\n\t/* iso_9660_directory_entry_t */\n\tchar root[34];\n\n\tchar volume_set_id[128];\n\tchar volume_publisher[128];\n\tchar data_preparer[128];\n\tchar application_id[128];\n\n\tchar copyright_file[38];\n\tchar abstract_file[36];\n\tchar bibliographic_file[37];\n\n\tiso_9660_datetime_t creation;\n\tiso_9660_datetime_t modification;\n\tiso_9660_datetime_t expiration;\n\tiso_9660_datetime_t effective;\n\n\tuint8_t file_structure_version;\n\tuint8_t _unused_3;\n\n\tchar application_use[];\n} __attribute__((packed)) iso_9660_volume_descriptor_t;\n\n#define ISO_SECTOR_SIZE 2048\n\n#define FLAG_HIDDEN      0x01\n#define FLAG_DIRECTORY   0x02\n#define FLAG_ASSOCIATED  0x04\n#define FLAG_EXTENDED    0x08\n#define FLAG_PERMISSIONS 0x10\n#define FLAG_CONTINUES   0x80\n\nextern char root_data[ISO_SECTOR_SIZE];\nextern iso_9660_volume_descriptor_t * root;\nextern iso_9660_directory_entry_t * dir_entry;\n\nint navigate(char * name);\n"
  },
  {
    "path": "boot/kbd.c",
    "content": "/**\n * @brief Keyboard reading functions.\n *\n * Abstracts away the differences between our EFI and BIOS\n * environments to provide consistent scancode feedback for\n * the menus and command line editor.\n *\n * For EFI, we use the WaitForKey and ReadKeyStroke interfaces.\n *\n * For BIOS, we have a bad PS/2 driver, which should be fine if\n * you're booting with BIOS?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include \"kbd.h\"\n#include \"util.h\"\n#include \"text.h\"\n\n#ifdef EFI_PLATFORM\n#include <efi.h>\nextern EFI_SYSTEM_TABLE *ST;\n\n#define KBD_SCAN_DOWN  0x50\n#define KBD_SCAN_UP    0x48\n#define KBD_SCAN_LEFT  0x4B\n#define KBD_SCAN_RIGHT 0x4D\n#define KBD_SCAN_ENTER 0x1C\n#define KBD_SCAN_1     2\n#define KBD_SCAN_9     10\n\nint read_scancode(int timeout) {\n\tEFI_INPUT_KEY Key;\n\tunsigned long int index;\n\tif (timeout) {\n\t\tEFI_EVENT events[] = {ST->ConIn->WaitForKey, 0};\n\t\tuefi_call_wrapper(ST->BootServices->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL, &events[1]);\n\t\tuefi_call_wrapper(ST->BootServices->SetTimer, 3, events[1], TimerRelative, 10000000UL);\n\t\tuefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 2, events, &index);\n\t} else {\n\t\tuefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);\n\t}\n\tEFI_STATUS result = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key);\n\n\tif (result == EFI_NOT_READY) return -1;\n\tswitch (Key.ScanCode) {\n\t\tcase 0:\n\t\t\tswitch (Key.UnicodeChar) {\n\t\t\t\tcase L'\\r':\n\t\t\t\t\treturn KBD_SCAN_ENTER;\n\t\t\t\tcase L'1':\n\t\t\t\tcase L'2':\n\t\t\t\tcase L'3':\n\t\t\t\tcase L'4':\n\t\t\t\tcase L'5':\n\t\t\t\tcase L'6':\n\t\t\t\tcase L'7':\n\t\t\t\tcase L'8':\n\t\t\t\tcase L'9':\n\t\t\t\t\treturn Key.UnicodeChar - L'1' + KBD_SCAN_1;\n\t\t\t\tcase L'e':\n\t\t\t\t\treturn 0x12;\n\t\t\t\tdefault:\n\t\t\t\t\treturn 0xFF;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 0x01: return KBD_SCAN_UP;\n\t\tcase 0x02: return KBD_SCAN_DOWN;\n\t\tcase 0x03: return KBD_SCAN_RIGHT;\n\t\tcase 0x04: return KBD_SCAN_LEFT;\n\t\tdefault:\n\t\t\treturn 0xFF;\n\t}\n}\n\nint read_key(int * c) {\n\tEFI_INPUT_KEY Key;\n\tunsigned long int index;\n\tuefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index);\n\tuefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key);\n\n\tif (Key.ScanCode == 0) {\n\t\t*c = Key.UnicodeChar;\n\t\tif (*c == '\\r') *c = '\\n';\n\t\treturn 0;\n\t}\n\n\tswitch (Key.ScanCode) {\n\t\tcase 0x03: return 3;\n\t\tcase 0x04: return 2;\n\t\tcase 0x09: return 4;\n\t\tcase 0x0a: return 5;\n\t\tcase 0x17: *c = 27; return 0;\n\t}\n\n\treturn 1;\n}\n\n#else\n\nint read_cmos_seconds(void) {\n\toutportb(0x70,0);\n\treturn inportb(0x71);\n}\n\nstatic char kbd_us[128] = {\n\t0, 27, '1','2','3','4','5','6','7','8','9','0',\n\t'-','=','\\b', '\\t', 'q','w','e','r','t','y','u','i','o','p','[',']','\\n',\n\t0, 'a','s','d','f','g','h','j','k','l',';','\\'', '`',\n\t0, '\\\\','z','x','c','v','b','n','m',',','.','/',\n\t0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t'-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n};\n\nstatic char kbd_us_l2[128] = {\n\t0, 27, '!','@','#','$','%','^','&','*','(',')',\n\t'_','+','\\b', '\\t', 'Q','W','E','R','T','Y','U','I','O','P','{','}','\\n',\n\t0, 'A','S','D','F','G','H','J','K','L',':','\"', '~',\n\t0, '|','Z','X','C','V','B','N','M','<','>','?',\n\t0, '*', 0, '\\037', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t'-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n};\n\nextern int do_bios_call(unsigned int function, unsigned int arg1);\n\nint read_key(int * c) {\n\tstatic int shift_state = 0;\n\n\tint sc = read_scancode(0);\n\tint shift  = do_bios_call(4,2) & 0x3;\n\n\tif (sc == 0x4B) return shift ? 4 : 2;\n\tif (sc == 0x4D) return shift ? 5 : 3;\n\n\tif (!(sc & 0x80)) {\n\t\t*c = shift ? kbd_us_l2[sc] : kbd_us[sc];\n\t\treturn *c == 0;\n\t}\n\n\treturn 1;\n}\n\nint kbd_status(void) {\n\tint result = do_bios_call(4,0x11);\n\treturn (result & 0xFF) == 0;\n}\n\nint read_scancode(int timeout) {\n\tif (timeout) {\n\t\tint start_s = read_cmos_seconds();\n\t\twhile (kbd_status()) {\n\t\t\tint now_s = read_cmos_seconds();\n\t\t\tif (now_s != start_s) return -1;\n\t\t}\n\t}\n\tint result = do_bios_call(4,0);\n\treturn (result >> 8) & 0xFF;\n}\n#endif\n"
  },
  {
    "path": "boot/kbd.h",
    "content": "#pragma once\n\n#define KBD_SCAN_DOWN  0x50\n#define KBD_SCAN_UP    0x48\n#define KBD_SCAN_LEFT  0x4B\n#define KBD_SCAN_RIGHT 0x4D\n#define KBD_SCAN_ENTER 0x1C\n#define KBD_SCAN_1     2\n#define KBD_SCAN_9     10\n\nint read_scancode(int);\nint read_key(int * c);\n\n"
  },
  {
    "path": "boot/link.ld",
    "content": "OUTPUT_FORMAT(\"binary\")\n/*ENTRY(start) */\nphys = 0x7c00;\n\nSECTIONS\n{\n\t. = 0x7c00;\n\n\t.text : \n\t{\n\t\tcode = .;\n\t\t*(.text)\n\t}\n\n\t.rodata BLOCK(1) : ALIGN(1)\n\t{\n\t\t*(.rodata)\n\t}\n\n\t.data BLOCK(1) : ALIGN(1)\n\t{\n\t\tdata = .;\n\t\t*(.data)\n\t}\n\n\t.bss BLOCK(1) : ALIGN(1)\n\t{\n\t\tPROVIDE(_bss_start = .);\n\t\tbss = .;\n\t\t*(COMMON)\n\t\t*(.bss)\n\t\t*(.stack)\n\t\tPROVIDE(_bss_end = .);\n\t}\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.eh_frame)\n\t\t*(.note.gnu.build-id)\n\t}\n}\n"
  },
  {
    "path": "boot/mbr.S",
    "content": ".code16\nmain:\n\t/* fix up code seg */\n\tljmp $0x0,$entry\n\nentry:\n\t/* init data segments */\n\txor %ax, %ax\n\tmov %ax, %ds\n\tmov %ax, %ss\n\t/* save boot disk */\n\tmov %dl, boot_disk\n\t/* set up stack */\n\tmov $0x7b00, %ax\n\tmov %ax, %sp\n\t/* figure out sector size */\n\tmov $0x48, %ah\n\tmov boot_disk, %dl\n\tmov $drive_params, %si\n\tint $0x13\n\t/* figure out first sector of stage 2 */\n\tmov $0, %edx\n\tmov $BOOT_FILE_OFFSET, %eax\n\tmov (drive_params_bps), %ecx\n\tdiv %ecx\n\tmov %eax, dap_lba_low\n\tmov $BOOT_FILE_SIZE, %eax\n\tdiv %ecx\n\tinc %eax\n\tmov %ax, dap_sectors\n\tmovl $0x7e00, dap_buffer\n\tmov $0x42, %ah     /* Extended read */\n\tmov boot_disk, %dl /* Using our boot disk */\n\tmov $dap, %si      /* From the DAP below */\n\tint $0x13\n\n\tmov $0, %ax\n\tmov %ax, %es\n\tmov %ax, %ds\n\t/* Now move the rest of our code somewhere low */\n\tmov $mover, %esi\n\tmov $0x7b00, %edi\n\tmov $(mover_end-mover), %ecx\n\trep movsb\n\tmov $0x7b00, %eax\n\tjmp *%eax\n\nmover:\n\tmov $0x7e00, %esi\n\tmov $0x7c00, %edi\n\tmov $BOOT_FILE_SIZE, %ecx\n\trep movsb\n\tmov $0x7c00, %eax\n\tjmp *%eax\nmover_end:\n\nboot_disk:\n\t.byte 0\n\n.align 4\n.global dap\ndap:\n\t.byte 16\n\t.byte 0 /* always 0 */\n.global dap_sectors\ndap_sectors:\n\t.word 1\n.global dap_buffer\ndap_buffer:\n\t.long 0x0\n.global dap_lba_low\ndap_lba_low:\n\t.long 0\n.global dap_lba_high\ndap_lba_high:\n\t.long 0\n\n.align 4\ndrive_params:\n\t.word 0x1A\n\t.word 0 /* flags */\n\t.long 0 /* cylinders */\n\t.long 0 /* heads */\n\t.long 0 /* sectors */\n\t.quad 0 /* total sectors */\ndrive_params_bps:\n\t.word 0 /* bytes per sector */\n\n.org 510\n\t.byte 0x55\n\t.byte 0xaa\n"
  },
  {
    "path": "boot/menu.c",
    "content": "/**\n * @brief Present configuration menus.\n *\n * Handles display and user interaction for the config menu.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include \"options.h\"\n#include \"text.h\"\n#include \"util.h\"\n#include \"kbd.h\"\n#include \"qemu.h\"\n#include \"editor.h\"\n\nextern void draw_logo(int);\n\nstruct option boot_options[20] = {{0}};\n\nint sel_max = 0;\nint sel = 0;\nint boot_mode = 0;\n\nvoid toggle(int ndx, int value, char *str) {\n\tset_attr(sel == ndx ? 0x70 : 0x07);\n\tif (value) {\n\t\tprint_(\" [X] \");\n\t} else {\n\t\tprint_(\" [ ] \");\n\t}\n\tprint_(str);\n\tif (x < 40) {\n\t\twhile (x < 39) {\n\t\t\tprint_(\" \");\n\t\t}\n\t\tx = 40;\n\t} else {\n\t\tprint_(\"\\n\");\n\t}\n}\n\n\nvoid show_menu(void) {\n\tif (detect_qemu()) return;\n\n\tstatic int timeout_shown = 0;\n\n\tint s;\n\n\tint timeout = 4;\n\tchar timeout_msg[] = \"Normal boot will commence in 0 seconds.\";\n\tchar * timeout_val = strchr(timeout_msg,'0');\n\n\t/* Determine number of options */\n\tsel_max = 0;\n\twhile (boot_options[sel_max].value) {\n\t\tsel_max++;\n\t}\n\tsel_max += base_sel + 1;\n\tclear_();\n\n\tif (!timeout_shown) {\n\t\ttimeout_shown = 1;\n\t\tdraw_logo(10);\n\t\twhile (1) {\n\t\t\tmove_cursor(0,15);\n\t\t\t*timeout_val = timeout + '0';\n\t\t\tset_attr(0x08);\n\t\t\tprint_banner(\"Press <Enter> to boot now, <e> to edit command line,\");\n\t\t\tprint_banner(\"or use \\030/\\031/\\032/\\033 to select a menu option.\");\n\t\t\tprint_banner(timeout_msg);\n\n\t\t\ts = read_scancode(1);\n\t\t\tif (timeout && s == -1) {\n\t\t\t\ttimeout--;\n\t\t\t\tif (!timeout) {\n\t\t\t\t\tboot_mode = boot_mode_names[sel].index;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tclear_();\n\t\t\tgoto _key_read;\n\t\t}\n\t}\n\n\tdo {\n\t\tmove_cursor(0,0);\n\t\tset_attr(0x1f);\n\t\tprint_banner(VERSION_TEXT);\n\t\tset_attr(0x07);\n\t\tprint_(\"\\n\");\n\n\t\tfor (int i = 0; i < base_sel + 1; ++i) {\n\t\t\tset_attr(sel == i ? 0x70 : 0x07);\n\t\t\tprint_(\" \");\n\t\t\tchar tmp[] = {'0' + (i + 1), '.', ' ', '\\0'};\n\t\t\tprint_(tmp);\n\t\t\tprint_(boot_mode_names[i].title);\n\t\t\tprint_(\"\\n\");\n\t\t}\n\n\t\t// put a gap\n\t\tset_attr(0x07);\n\t\tprint_(\"\\n\");\n\n\t\tfor (int i = 0; i < sel_max - base_sel - 1; ++i) {\n\t\t\ttoggle(base_sel + 1 + i, *boot_options[i].value, boot_options[i].title);\n\t\t}\n\n\t\tset_attr(0x07);\n\t\tmove_cursor(0,17);\n\t\tprint_banner(sel <= base_sel ? HELP_TEXT : HELP_TEXT_OPT);\n\t\tprint_(\"\\n\");\n\n\t\tif (sel > base_sel) {\n\t\t\tprint_banner(boot_options[sel - base_sel - 1].description_1);\n\t\t\tprint_banner(boot_options[sel - base_sel - 1].description_2);\n\t\t\tprint_(\"\\n\");\n\t\t} else {\n\t\t\tprint_banner(COPYRIGHT_TEXT);\n\t\t\tprint_(\"\\n\");\n\t\t\tprint_banner(LINK_TEXT);\n\t\t}\n\n\t\tread_again:\n\t\ttimeout = 0;\n\t\ts = read_scancode(0);\n\t\t_key_read:\n\t\tif (s == 0x50) { /* DOWN */\n\t\t\tif (sel > base_sel && sel < sel_max - 1) {\n\t\t\t\tsel = (sel + 2) % sel_max;\n\t\t\t} else {\n\t\t\t\tsel = (sel + 1)  % sel_max;\n\t\t\t}\n\t\t} else if (s == 0x48) { /* UP */\n\t\t\tif (sel > base_sel + 1) {\n\t\t\t\tsel = (sel_max + sel - 2)  % sel_max;\n\t\t\t} else {\n\t\t\t\tsel = (sel_max + sel - 1)  % sel_max;\n\t\t\t}\n\t\t} else if (s == 0x4B) { /* LEFT */\n\t\t\tif (sel > base_sel) {\n\t\t\t\tif ((sel - base_sel) % 2) {\n\t\t\t\t\tsel = (sel + 1) % sel_max;\n\t\t\t\t} else {\n\t\t\t\t\tsel -= 1;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (s == 0x4D) { /* RIGHT */\n\t\t\tif (sel > base_sel) {\n\t\t\t\tif ((sel - base_sel) % 2) {\n\t\t\t\t\tsel = (sel + 1) % sel_max;\n\t\t\t\t} else {\n\t\t\t\t\tsel -= 1;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (s == 0x1c) {\n\t\t\tif (sel <= base_sel) {\n\t\t\t\tboot_mode = boot_mode_names[sel].index;\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tint index = sel - base_sel - 1;\n\t\t\t\t*boot_options[index].value = !*boot_options[index].value;\n\t\t\t}\n\t\t} else if (s == 0x12) { /* e */\n\t\t\tif (sel <= base_sel) {\n\t\t\t\tboot_edit = 1;\n\t\t\t\tboot_mode = boot_mode_names[sel].index;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else if (s >= 2 && s <= 10) {\n\t\t\tint i = s - 2;\n\t\t\tif (i <= base_sel) {\n\t\t\t\tboot_mode = boot_mode_names[i].index;\n\t\t\t\tbreak;\n\t\t\t}\n#ifndef EFI_PLATFORM\n\t\t} else if (s == 0x2f) { /* v */\n\t\t\tvoid bios_toggle_mode(void);\n\t\t\tbios_toggle_mode();\n#endif\n\t\t} else if (!timeout) {\n\t\t\tgoto read_again;\n\t\t}\n\t} while (1);\n}\n\n"
  },
  {
    "path": "boot/menu.h",
    "content": "#pragma once\n\nextern int boot_mode;\nvoid show_menu(void);\n"
  },
  {
    "path": "boot/multiboot.c",
    "content": "/**\n * @brief Main bootloader logic.\n *\n * Does all the heavy lifting after configuration options have\n * been selected by the user. Loads the kernel and ramdisk,\n * sets up multiboot structures, and jumps to the kernel.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n#include \"multiboot.h\"\n#include \"text.h\"\n#include \"util.h\"\n#include \"menu.h\"\n#include \"elf.h\"\n#include \"options.h\"\n#include \"iso9660.h\"\n#include \"kbd.h\"\n\nextern void draw_logo(int);\nchar * kernel_load_start = 0;\n\nmboot_mod_t modules_mboot[1] = {\n\t{0,0,0,1}\n};\n\nstatic struct multiboot multiboot_header = {\n\t/* flags;             */ MULTIBOOT_FLAG_CMDLINE | MULTIBOOT_FLAG_MODS | MULTIBOOT_FLAG_MEM | MULTIBOOT_FLAG_MMAP | MULTIBOOT_FLAG_LOADER,\n\t/* mem_lower;         */ 0x100000,\n\t/* mem_upper;         */ 0x640000,\n\t/* boot_device;       */ 0,\n\t/* cmdline;           */ 0,\n\t/* mods_count;        */ 1,\n\t/* mods_addr;         */ 0,\n\t/* num;               */ 0,\n\t/* size;              */ 0,\n\t/* addr;              */ 0,\n\t/* shndx;             */ 0,\n\t/* mmap_length;       */ 0,\n\t/* mmap_addr;         */ 0,\n\t/* drives_length;     */ 0,\n\t/* drives_addr;       */ 0,\n\t/* config_table;      */ 0,\n\t/* boot_loader_name;  */ 0,\n\t/* apm_table;         */ 0,\n\t/* vbe_control_info;  */ 0,\n\t/* vbe_mode_info;     */ 0,\n\t/* vbe_mode;          */ 0,\n\t/* vbe_interface_seg; */ 0,\n\t/* vbe_interface_off; */ 0,\n\t/* vbe_interface_len; */ 0,\n};\n\nstatic uintptr_t ramdisk_off = 0;\nstatic uintptr_t ramdisk_len = 0;\nuintptr_t final_offset = 0;\nuintptr_t _xmain = 0;\nint disable_kaslr = 0;\n\nstatic inline uint64_t read_tsc(void) {\n\tuint32_t lo, hi;\n\tasm volatile ( \"rdtsc\" : \"=a\"(lo), \"=d\"(hi) );\n\treturn ((uint64_t)hi << 32) | (uint64_t)lo;\n}\n\nstatic int load_aout(uint32_t * hdr) {\n\tuintptr_t base_offset = (uintptr_t)hdr - (uintptr_t)kernel_load_start;\n\tuintptr_t hdr_offset  = hdr[3] - base_offset;\n\tuint32_t  rando = 0;\n\tsize_t    xtra = 0;\n\n\tif (!disable_kaslr) {\n\t\tasm volatile ( \"rdtsc\" : \"=a\"(rando), \"=d\"((uint32_t){0}) );\n\t\txtra = (rando & 0xFF) << 12;\n\t}\n\n\tmemcpy((void*)(uintptr_t)hdr[4] + xtra, kernel_load_start + (hdr[4] - hdr_offset), (hdr[5] - hdr[4]));\n\tmemset((void*)(uintptr_t)hdr[5] + xtra, 0, (hdr[6] - hdr[5]));\n\t_xmain = (uintptr_t)hdr[7] + xtra;\n\n\tif (hdr[6] + xtra > final_offset) final_offset = hdr[6] + xtra;\n\tfinal_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);\n\n\treturn 1;\n}\n\nstatic int load_elf32(Elf32_Header * header) {\n\tif (header->e_ident[0] != ELFMAG0 ||\n\t    header->e_ident[1] != ELFMAG1 ||\n\t    header->e_ident[2] != ELFMAG2 ||\n\t    header->e_ident[3] != ELFMAG3) {\n\t\tprint_(\"Not a valid ELF32.\\n\");\n\t\treturn 0;\n\t}\n\n\tuintptr_t entry = (uintptr_t)header->e_entry;\n\tfor (uintptr_t x = 0; x < (uint32_t)header->e_phentsize * header->e_phnum; x += header->e_phentsize) {\n\t\tElf32_Phdr * phdr = (Elf32_Phdr *)(kernel_load_start + header->e_phoff + x);\n\t\tif (phdr->p_type == PT_LOAD) {\n\t\t\tmemcpy((uint8_t*)(uintptr_t)phdr->p_vaddr, kernel_load_start + phdr->p_offset, phdr->p_filesz);\n\t\t\tuintptr_t r = phdr->p_filesz;\n\t\t\twhile (r < phdr->p_memsz) {\n\t\t\t\t*(char *)(phdr->p_vaddr + r) = 0;\n\t\t\t\tr++;\n\t\t\t}\n\t\t\tif (phdr->p_vaddr + r > final_offset) final_offset = phdr->p_vaddr + r;\n\t\t}\n\t}\n\n\t_xmain = entry;\n\n\t/* Round final offset */\n\tfinal_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);\n\n\tprint(\"Loaded with end at 0x\"); print_hex(final_offset); print(\"\\n\");\n\treturn 1;\n}\n\nstatic int load_kernel(void) {\n\tclear();\n\n\t/* Check for Multiboot header */\n\tfor (uintptr_t x = 0; x < 8192; x += 4) {\n\t\tuint32_t * check = (uint32_t *)(kernel_load_start + x);\n\t\tif (*check == 0x1BADB002) {\n\t\t\tif (check[1] & (1 << 16)) {\n\t\t\t\treturn load_aout(check);\n\t\t\t} else {\n\t\t\t\treturn load_elf32((void*)kernel_load_start);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic void relocate_ramdisk(mboot_mod_t * mboot_mods) {\n\tchar * dest = (char*)final_offset;\n\tchar * src  = (char*)ramdisk_off;\n\tfor (size_t s = 0; s < ramdisk_len; ++s) {\n\t\tdest[s] = src[s];\n\t}\n\n\tmboot_mods->mod_start = final_offset;\n\tmboot_mods->mod_end   = final_offset + ramdisk_len;\n\n\tfinal_offset += ramdisk_len;\n\tfinal_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);\n}\n\n#ifdef EFI_PLATFORM\n#include <efi.h>\nextern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP;\n\n/* EFI boot uses simple filesystem driver */\nstatic EFI_GUID efi_simple_file_system_protocol_guid =\n\t{0x0964e5b22,0x6459,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b};\n\nstatic EFI_GUID efi_loaded_image_protocol_guid =\n\t{0x5B1B31A1,0x9562,0x11d2, {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}};\n\nextern EFI_SYSTEM_TABLE *ST;\nextern EFI_HANDLE ImageHandleIn;\n\nextern char do_the_nasty[];\nstatic void finish_boot(void) {\n\t/* Set up multiboot header */\n\tstruct multiboot * finalHeader = (void*)(uintptr_t)final_offset;\n\tmemcpy((void*)final_offset, &multiboot_header, sizeof(struct multiboot));\n\tfinal_offset += sizeof(struct multiboot);\n\n\tfinalHeader->flags |= MULTIBOOT_FLAG_FB;\n\tfinalHeader->framebuffer_addr = GOP->Mode->FrameBufferBase;\n\tfinalHeader->framebuffer_pitch = GOP->Mode->Info->PixelsPerScanLine * 4;\n\tfinalHeader->framebuffer_width = GOP->Mode->Info->HorizontalResolution;\n\tfinalHeader->framebuffer_height = GOP->Mode->Info->VerticalResolution;\n\tfinalHeader->framebuffer_bpp  = 32;\n\n\t/* Copy in command line */\n\tmemcpy((void*)final_offset, cmdline, strlen(cmdline)+1);\n\tfinalHeader->cmdline = (uintptr_t)final_offset;\n\tfinal_offset += strlen(cmdline) + 1;\n\n\t/* Copy bootloader name */\n\tmemcpy((void*)final_offset, VERSION_TEXT, strlen(VERSION_TEXT)+1);\n\tfinalHeader->boot_loader_name = (uintptr_t)final_offset;\n\tfinal_offset += strlen(VERSION_TEXT) + 1;\n\n\t/* Copy module pointers */\n\tmemcpy((void*)final_offset, modules_mboot, sizeof(modules_mboot));\n\tfinalHeader->mods_addr = (uintptr_t)final_offset;\n\tfinal_offset += sizeof(modules_mboot);\n\n\t/* Realign for memory map */\n\tfinal_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0);\n\n\t/* Write memory map */\n\tmboot_memmap_t * mmap = (void*)final_offset;\n\tmemset((void*)final_offset, 0x00, 1024);\n\tfinalHeader->mmap_addr = (uint32_t)(uintptr_t)mmap;\n\n\t{\n\t\tEFI_STATUS e;\n\t\tUINTN mapSize = 0, mapKey, descriptorSize;\n\t\tUINT32 descriptorVersion;\n\t\te = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL);\n\n\t\tEFI_MEMORY_DESCRIPTOR * efi_memory = (void*)(final_offset);\n\t\tfinal_offset += mapSize;\n\t\twhile ((uintptr_t)final_offset & 0x3ff) final_offset++;\n\n\t\te = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, efi_memory, &mapKey, &descriptorSize, NULL);\n\n\t\tif (EFI_ERROR(e)) {\n\t\t\tprint_(\"EFI error.\\n\");\n\t\t\twhile (1) {};\n\t\t}\n\n\t\tuint64_t upper_mem = 0;\n\t\tint descriptors = mapSize / descriptorSize;\n\t\tfor (int i = 0; i < descriptors; ++i) {\n\t\t\tEFI_MEMORY_DESCRIPTOR * d = efi_memory;\n\n\t\t\tmmap->size = sizeof(uint64_t) * 2 + sizeof(uint32_t);\n\t\t\tmmap->base_addr = d->PhysicalStart;\n\t\t\tmmap->length = d->NumberOfPages * 4096;\n\t\t\tswitch (d->Type) {\n\t\t\t\tcase EfiConventionalMemory:\n\t\t\t\tcase EfiLoaderCode:\n\t\t\t\tcase EfiLoaderData:\n\t\t\t\tcase EfiBootServicesCode:\n\t\t\t\tcase EfiBootServicesData:\n\t\t\t\tcase EfiRuntimeServicesCode:\n\t\t\t\tcase EfiRuntimeServicesData:\n\t\t\t\t\tmmap->type = 1;\n\t\t\t\t\tbreak;\n\t\t\t\tcase EfiReservedMemoryType:\n\t\t\t\tcase EfiUnusableMemory:\n\t\t\t\tcase EfiMemoryMappedIO:\n\t\t\t\tcase EfiMemoryMappedIOPortSpace:\n\t\t\t\tcase EfiPalCode:\n\t\t\t\tcase EfiACPIMemoryNVS:\n\t\t\t\tcase EfiACPIReclaimMemory:\n\t\t\t\tdefault:\n\t\t\t\t\tmmap->type = 2;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (mmap->type == 1 && mmap->base_addr >= 0x100000) {\n\t\t\t\tupper_mem += mmap->length;\n\t\t\t}\n\t\t\tmmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t));\n\t\t\tefi_memory = (EFI_MEMORY_DESCRIPTOR *)((char *)efi_memory + descriptorSize);\n\t\t}\n\n\t\tfinalHeader->mmap_length = (uintptr_t)mmap - finalHeader->mmap_addr;\n\n\t\tfinalHeader->mem_lower = 1024;\n\t\tfinalHeader->mem_upper = upper_mem / 1024;\n\t}\n\n\trelocate_ramdisk((void*)(uintptr_t)finalHeader->mods_addr);\n\n\t{\n\t\tEFI_STATUS e;\n\t\tUINTN mapSize = 0, mapKey, descriptorSize;\n\t\tUINT32 descriptorVersion;\n\t\tuefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL);\n\t\te = uefi_call_wrapper(ST->BootServices->ExitBootServices, 2, ImageHandleIn, mapKey);\n\n\t\tif (e != EFI_SUCCESS) { \n\t\t\tprint_(\"Exit services failed. \\n\");\n\t\t\tprint_hex_(e);\n\t\t\twhile (1) {};\n\t\t}\n\t}\n\n\t/* Jump to entry with register arguments */\n\t__asm__ __volatile__ (\n\t\t\"jmp %0\" :: \"r\"(_xmain), \"a\"(MULTIBOOT_EAX_MAGIC), \"b\"(finalHeader));\n\n\t__builtin_unreachable();\n}\n\nvoid boot(void) {\n\tUINTN count;\n\tEFI_HANDLE * handles;\n\tEFI_LOADED_IMAGE * loaded_image;\n\tEFI_FILE_IO_INTERFACE *efi_simple_filesystem;\n\tEFI_FILE *root;\n\tEFI_STATUS status;\n\n\tuefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL);\n\n\tclear_();\n\n\tdraw_logo(0);\n\n\tfor (unsigned int i = 0; i < ST->NumberOfTableEntries; ++i) {\n\t\t/* ACPI 1 table pointer. */\n\t\tif (ST->ConfigurationTable[i].VendorGuid.Data1 == 0xeb9d2d30 &&\n\t\t\tST->ConfigurationTable[i].VendorGuid.Data2 == 0x2d88 &&\n\t\t\tST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) {\n\t\t\tmultiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff;\n\t\t\tbreak;\n\t\t}\n\t\t/* ACPI 2 table pointer. */\n\t\tif (ST->ConfigurationTable[i].VendorGuid.Data1 == 0x8868e871 &&\n\t\t\tST->ConfigurationTable[i].VendorGuid.Data2 == 0xe4f1 &&\n\t\t\tST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) {\n\t\t\tmultiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tstatus = uefi_call_wrapper(ST->BootServices->HandleProtocol,\n\t\t\t3, ImageHandleIn, &efi_loaded_image_protocol_guid,\n\t\t\t(void **)&loaded_image);\n\n\tif (EFI_ERROR(status)) {\n\t\tprint_(\"Could not obtain loaded_image_protocol\\n\");\n\t\twhile (1) {};\n\t}\n\n\tprint(\"Found loaded image...\\n\");\n\n\tstatus = uefi_call_wrapper(ST->BootServices->HandleProtocol,\n\t\t\t3, loaded_image->DeviceHandle, &efi_simple_file_system_protocol_guid,\n\t\t\t(void **)&efi_simple_filesystem);\n\n\tif (EFI_ERROR(status)) {\n\t\tprint_(\"Could not obtain simple_file_system_protocol.\\n\");\n\t\twhile (1) {};\n\t}\n\n\tstatus = uefi_call_wrapper(efi_simple_filesystem->OpenVolume,\n\t\t\t2, efi_simple_filesystem, &root);\n\n\tif (EFI_ERROR(status)) {\n\t\tprint_(\"Could not open volume.\\n\");\n\t\twhile (1) {};\n\t}\n\n\tEFI_FILE * file;\n\n\tCHAR16 kernel_name[16] = {0};\n\t{\n\t\tchar * c = kernel_path;\n\t\tchar * ascii = c;\n\t\tint i = 0;\n\t\twhile (*ascii) {\n\t\t\tkernel_name[i] = *ascii;\n\t\t\ti++;\n\t\t\tascii++;\n\t\t}\n\t\tif (kernel_name[i-1] == L'.') {\n\t\t\tkernel_name[i-1] = 0;\n\t\t}\n\t}\n\n\t/* Load kernel */\n\tstatus = uefi_call_wrapper(root->Open,\n\t\t\t5, root, &file, kernel_name, EFI_FILE_MODE_READ, 0);\n\n\tif (EFI_ERROR(status)) {\n\t\tprint_(\"Error opening kernel.\\n\");\n\t\twhile (1) {};\n\t}\n\n#define KERNEL_LOAD_START 0x4000000ULL\n\tkernel_load_start = (char*)KERNEL_LOAD_START;\n\n\t{\n\t\tEFI_PHYSICAL_ADDRESS addr = KERNEL_LOAD_START;\n\t\tEFI_ALLOCATE_TYPE type = AllocateAddress;\n\t\tEFI_MEMORY_TYPE memtype = EfiLoaderData;\n\t\tUINTN pages = 8192;\n\t\tEFI_STATUS status = 0;\n\t\tstatus = uefi_call_wrapper(ST->BootServices->AllocatePages, 4, type, memtype, pages, &addr);\n\t\tif (EFI_ERROR(status)) {\n\t\t\tprint_(\"Could not allocate space to load boot payloads: \");\n\t\t\tprint_hex_(status);\n\t\t\tprint_(\" \");\n\t\t\tprint_hex_(addr);\n\t\t\twhile (1) {};\n\t\t}\n\t}\n\n\tunsigned int offset = 0;\n\tUINTN bytes = 134217728;\n\tstatus = uefi_call_wrapper(file->Read,\n\t\t\t3, file, &bytes, (void *)KERNEL_LOAD_START);\n\n\tif (EFI_ERROR(status)) {\n\t\tprint_(\"Error loading kernel.\\n\");\n\t\twhile (1) {};\n\t}\n\n\toffset += bytes;\n\twhile (offset % 4096) offset++;\n\n\t{\n\t\tchar * c = ramdisk_path;\n\t\tCHAR16 name[16] = {0};\n\t\tchar * ascii = c;\n\t\tint i = 0;\n\t\twhile (*ascii) {\n\t\t\tname[i] = *ascii;\n\t\t\ti++;\n\t\t\tascii++;\n\t\t}\n\t\tif (name[i-1] == L'.') {\n\t\t\tname[i-1] == 0;\n\t\t}\n\t\tbytes = 134217728;\n\t\tstatus = uefi_call_wrapper(root->Open,\n\t\t\t\t5, root, &file, name, EFI_FILE_MODE_READ, 0);\n\t\tif (!EFI_ERROR(status)) {\n\t\t\tstatus = uefi_call_wrapper(file->Read,\n\t\t\t\t\t3, file, &bytes, (void*)(uintptr_t)(KERNEL_LOAD_START + offset));\n\t\t\tif (!EFI_ERROR(status)) {\n\t\t\t\tramdisk_off = KERNEL_LOAD_START + offset;\n\t\t\t\tramdisk_len = bytes;\n\t\t\t} else {\n\t\t\t\tprint_(\"Failed to read ramdisk\\n\");\n\t\t\t}\n\t\t} else {\n\t\t\tprint_(\"Error opening \"); print_(c); print_(\"\\n\");\n\t\t}\n\t}\n\n\tload_kernel();\n\tfinish_boot();\n}\n\n#else\nstruct mmap_entry {\n\tuint64_t base;\n\tuint64_t len;\n\tuint32_t type;\n\tuint32_t reserved;\n};\n\nextern unsigned short mmap_ent;\nextern unsigned short lower_mem;\n\nstatic void finish_boot(void) {\n\tprint(\"Setting up memory map...\\n\");\n\tprint_hex(mmap_ent);\n\tprint(\"\\n\");\n\tmemset(kernel_load_start, 0x00, 1024);\n\tmboot_memmap_t * mmap = (void*)final_offset;\n\tmultiboot_header.mmap_addr = (uintptr_t)mmap;\n\tmultiboot_header.mods_addr = (uintptr_t)&modules_mboot;\n\tmultiboot_header.boot_loader_name = (uintptr_t)VERSION_TEXT;\n\n\tstruct mmap_entry * e820 = (void*)0x5000;\n\n\tuint64_t upper_mem = 0;\n\tfor (int i = 0; i < mmap_ent; ++i) {\n\t\tprint(\"entry \"); print_hex(i);\n\t\tprint(\" \"); print_hex((uint32_t)(e820[i].base >> 32ULL)); print_hex((uint32_t)e820[i].base);\n\t\tprint(\" \"); print_hex((uint32_t)(e820[i].len >> 32ULL)); print_hex((uint32_t)e820[i].len);\n\t\tprint(\" \"); print_hex(e820[i].type); print(\"\\n\");\n\n\t\tmmap->size = sizeof(uint64_t) * 2 + sizeof(uintptr_t);\n\t\tmmap->base_addr = e820[i].base;\n\t\tmmap->length = e820[i].len;\n\t\tmmap->type = e820[i].type;\n\t\tif (mmap->type == 1 && mmap->base_addr >= 0x100000) {\n\t\t\tupper_mem += mmap->length;\n\t\t}\n\t\tmmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uintptr_t));\n\t}\n\tmultiboot_header.mmap_length = (uintptr_t)mmap - multiboot_header.mmap_addr;\n\n\tprint(\"lower \"); print_hex(lower_mem); print(\"KB\\n\");\n\tmultiboot_header.mem_lower = 1024;\n\tprint(\"upper \");\n\tprint_hex(upper_mem >> 32);\n\tprint_hex(upper_mem);\n\tprint(\"\\n\");\n\t\n\tmultiboot_header.mem_upper = upper_mem / 1024;\n\n\tprint(\"Jumping to kernel...\\n\");\n\n\tuint32_t foo[3];\n\tfoo[0] = MULTIBOOT_EAX_MAGIC;\n\tfoo[1] = (unsigned int)&multiboot_header;;\n\tfoo[2] = _xmain;\n\t__asm__ __volatile__ (\n\t\t\"mov %%cr0,%%eax\\n\"\n\t\t/* Disable paging */\n\t\t\"and $0x7FFeFFFF, %%eax\\n\"\n\t\t\"mov %%eax,%%cr0\\n\"\n\t\t/* Ensure PAE is not enabled */\n\t\t\"mov %%cr4,%%eax\\n\"\n\t\t\"and $0xffffffdf, %%eax\\n\"\n\t\t\"mov %%eax,%%cr4\\n\"\n\t\t\"mov %1,%%eax \\n\"\n\t\t\"mov %2,%%ebx \\n\"\n\t\t\"jmp *%0\" : : \"g\"(foo[2]), \"g\"(foo[0]), \"g\"(foo[1]) : \"eax\", \"ebx\"\n\t\t);\n}\n\nextern void do_bios_call(uint32_t function, uint32_t arg1);\nextern int bios_call(char * into, uint32_t sector);\n\nstatic int spin_x = 0;\nstatic void spin(void) {\n\tstatic int  spincnt = 0;\n\tdraw_logo(spincnt+1);\n\tspincnt = (spincnt + 1) & 0x7;\n}\n\nstatic void clear_spin(void) {\n\ty = 16;\n\t//print_banner(\"\");\n}\n\nextern uint16_t * vbe_cont_info_mode_off;\nextern uint32_t *vbe_info_fbaddr;\nextern uint16_t vbe_info_pitch;\nextern uint16_t vbe_info_width;\nextern uint16_t vbe_info_height;\nextern uint8_t vbe_info_bpp;\n\nextern void bios_text_mode(void);\n\nvoid boot(void) {\n\t/* Did we ask for VGA text mode and are currently in a video mode? */\n\tif (boot_mode == 5) {\n\t\tbios_text_mode();\n\t}\n\n\tclear_();\n\n\tdraw_logo(0);\n\n\tprint(\"Looking for ISO9660 filesystem... \");\n\tfor (int i = 0x10; i < 0x15; ++i) {\n\t\tbios_call((char*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i), i);\n\t\troot = (void*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i);\n\t\tswitch (root->type) {\n\t\t\tcase 1:\n\t\t\t\tprint(\"found.\\n\");\n\t\t\t\tgoto done;\n\t\t}\n\t}\n\treturn;\ndone:\n\n\tprint(\"Looking for kernel... \");\n\tif (!navigate(kernel_path)) {\n\t\tprint_(\"Failed to locate kernel.\\n\");\n\t\treturn;\n\t}\n\tprint(\"found.\\n\");\n\n\tkernel_load_start = (char*)(DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE);\n\n\tprint(\"Loading kernel... \"); spin_x = x;\n\tfor (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) {\n\t\tif (!(j & 0x3FF)) spin();\n\t\tbios_call(kernel_load_start + i, dir_entry->extent_start_LSB + j);\n\t\ti += ISO_SECTOR_SIZE;\n\t}\n\tprint(\"\\n\");\n\n\tprint(\"Looking for ramdisk... \");\n\tif (!navigate(ramdisk_path)) {\n\t\tprint_(\"Failed to locate ramdisk.\\n\");\n\t\treturn;\n\t}\n\tprint(\"found.\\n\");\n\n\tramdisk_off = DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE;\n\n\tprint(\"Loading ramdisk... \"); spin_x = x;\n\tfor (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) {\n\t\tif (!(j & 0x3FF)) spin();\n\t\tbios_call((char*)(ramdisk_off + i), dir_entry->extent_start_LSB + j);\n\t\ti += ISO_SECTOR_SIZE;\n\t}\n\tprint(\"\\n\");\n\n\tramdisk_len = dir_entry->extent_length_LSB;\n\n\tmultiboot_header.cmdline = (uintptr_t)cmdline;\n\n\tdraw_logo(0);\n\n\tif (vbe_info_width) {\n\t\tmultiboot_header.framebuffer_addr   = (uint32_t)vbe_info_fbaddr;\n\t\tmultiboot_header.framebuffer_pitch  = vbe_info_pitch;\n\t\tmultiboot_header.framebuffer_width  = vbe_info_width;\n\t\tmultiboot_header.framebuffer_height = vbe_info_height;\n\t\tmultiboot_header.framebuffer_bpp    = vbe_info_bpp;\n\t}\n\n\tprint(\"Loading kernel from 0x\"); print_hex((uint32_t)kernel_load_start); print(\"... \");\n\tif (!load_kernel()) {\n\t\tprint_(\"Failed to load kernel.\\n\");\n\t\treturn;\n\t}\n\n\tprint(\"Relocating ramdisk from 0x\"); print_hex((uint32_t)ramdisk_off); print(\":0x\"); print_hex(ramdisk_len); print(\" to 0x\"); print_hex(final_offset); print(\"... \");\n\trelocate_ramdisk(modules_mboot);\n\n\tfinish_boot();\n}\n#endif\n"
  },
  {
    "path": "boot/multiboot.h",
    "content": "#pragma once\n\n#define MULTIBOOT_MAGIC        0x1BADB002\n#define MULTIBOOT_EAX_MAGIC    0x2BADB002\n#define MULTIBOOT_FLAG_MEM     0x001\n#define MULTIBOOT_FLAG_DEVICE  0x002\n#define MULTIBOOT_FLAG_CMDLINE 0x004\n#define MULTIBOOT_FLAG_MODS    0x008\n#define MULTIBOOT_FLAG_AOUT    0x010\n#define MULTIBOOT_FLAG_ELF     0x020\n#define MULTIBOOT_FLAG_MMAP    0x040\n#define MULTIBOOT_FLAG_DRIVE   0x080\n#define MULTIBOOT_FLAG_CONFIG  0x100\n#define MULTIBOOT_FLAG_LOADER  0x200\n#define MULTIBOOT_FLAG_APM     0x400\n#define MULTIBOOT_FLAG_VBE     0x800\n#define MULTIBOOT_FLAG_FB     0x1000\n\nstruct multiboot\n{\n\tuint32_t flags;\n\tuint32_t mem_lower;\n\tuint32_t mem_upper;\n\tuint32_t boot_device;\n\tuint32_t cmdline;\n\tuint32_t mods_count;\n\tuint32_t mods_addr;\n\tuint32_t num;\n\tuint32_t size;\n\tuint32_t addr;\n\tuint32_t shndx;\n\tuint32_t mmap_length;\n\tuint32_t mmap_addr;\n\tuint32_t drives_length;\n\tuint32_t drives_addr;\n\tuint32_t config_table;\n\tuint32_t boot_loader_name;\n\tuint32_t apm_table;\n\tuint32_t vbe_control_info;\n\tuint32_t vbe_mode_info;\n\tuint16_t vbe_mode;\n\tuint16_t vbe_interface_seg;\n\tuint16_t vbe_interface_off;\n\tuint16_t vbe_interface_len;\n\tuint64_t framebuffer_addr;\n\tuint32_t framebuffer_pitch;\n\tuint32_t framebuffer_width;\n\tuint32_t framebuffer_height;\n\tuint8_t  framebuffer_bpp;\n\tuint8_t  framebuffer_type;\n} __attribute__ ((packed));\n\ntypedef struct {\n\tuint16_t attributes;\n\tuint8_t  winA, winB;\n\tuint16_t granularity;\n\tuint16_t winsize;\n\tuint16_t segmentA, segmentB;\n\tuint32_t realFctPtr;\n\tuint16_t pitch;\n\n\tuint16_t Xres, Yres;\n\tuint8_t  Wchar, Ychar, planes, bpp, banks;\n\tuint8_t  memory_model, bank_size, image_pages;\n\tuint8_t  reserved0;\n\n\tuint8_t  red_mask, red_position;\n\tuint8_t  green_mask, green_position;\n\tuint8_t  blue_mask, blue_position;\n\tuint8_t  rsv_mask, rsv_position;\n\tuint8_t  directcolor_attributes;\n\n\tuint32_t physbase;\n\tuint32_t reserved1;\n\tuint16_t reserved2;\n} __attribute__ ((packed)) vbe_info_t;\n\ntypedef struct {\n\tuint32_t mod_start;\n\tuint32_t mod_end;\n\tuint32_t cmdline;\n\tuint32_t reserved;\n} __attribute__ ((packed)) mboot_mod_t;\n\ntypedef struct {\n\tuint32_t size;\n\tuint64_t base_addr;\n\tuint64_t length;\n\tuint32_t type;\n} __attribute__ ((packed)) mboot_memmap_t;\n\nextern struct multiboot *copy_multiboot(struct multiboot *mboot_ptr);\nextern void dump_multiboot(struct multiboot *mboot_ptr);\nextern char * ramdisk;\nextern struct multiboot * mboot_ptr;\nextern void boot(void);\n"
  },
  {
    "path": "boot/options.h",
    "content": "#pragma once\n\nstruct option {\n\tint * value;\n\tchar * title;\n\tchar * description_1;\n\tchar * description_2;\n};\n\nextern struct option boot_options[20];\nstatic int _boot_offset = 0;\n\n#define BOOT_OPTION(_value, default_val, option, d1, d2) \\\n\tint _value = default_val;\\\n\tboot_options[_boot_offset].value = &_value; \\\n\tboot_options[_boot_offset].title = option; \\\n\tboot_options[_boot_offset].description_1 = d1; \\\n\tboot_options[_boot_offset].description_2 = d2; \\\n\t_boot_offset++;\n\nstruct bootmode {\n\tint index;\n\tchar * key;\n\tchar * title;\n};\n\n#define BASE_SEL ((sizeof(boot_mode_names)/sizeof(*boot_mode_names))-1)\nextern int base_sel;\n\n#define BOOT_SET() do { \\\n\tbase_sel = BASE_SEL; \\\n\t_boot_offset = 0; \\\n\tmemset(boot_options, 0, sizeof(boot_options)); \\\n} while (0)\n\nextern char * VERSION_TEXT;\nextern char * HELP_TEXT;\nextern char * HELP_TEXT_OPT;\nextern char * COPYRIGHT_TEXT;\nextern char * LINK_TEXT;\nextern char * kernel_path;\nextern char * ramdisk_path;\nextern char cmdline[1024];\n\nextern struct bootmode boot_mode_names[];\n"
  },
  {
    "path": "boot/platform.c",
    "content": "/**\n * @brief Some platform-specific abstractions.\n *\n * Things like initial entry point, utility functions we need\n * in BIOS but don't already have, BIOS trampoline management,\n * and so on.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\nextern int kmain();\n\n#ifdef EFI_PLATFORM\n#include <efi.h>\n#include <efilib.h>\nEFI_HANDLE ImageHandleIn;\n\nextern int init_graphics();\n\nEFI_STATUS\n\tEFIAPI\nefi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)\n{\n\tInitializeLib(ImageHandle, SystemTable);\n\tST = SystemTable;\n\tImageHandleIn = ImageHandle;\n\n\tinit_graphics();\n\n\treturn kmain();\n}\n#else\n#include <stdint.h>\n#include <stddef.h>\n#include \"iso9660.h\"\n#include \"util.h\"\n#include \"text.h\"\nextern char _bss_start[];\nextern char _bss_end[];\n\nvoid * memcpy(void * restrict dest, const void * restrict src, long n) {\n\tasm volatile(\"cld; rep movsb\"\n\t            : \"=c\"((int){0})\n\t            : \"D\"(dest), \"S\"(src), \"c\"(n)\n\t            : \"flags\", \"memory\");\n\treturn dest;\n}\n\nvoid * memset(void * dest, int c, long n) {\n\tasm volatile(\"cld; rep stosb\"\n\t             : \"=c\"((int){0})\n\t             : \"D\"(dest), \"a\"(c), \"c\"(n)\n\t             : \"flags\", \"memory\");\n\treturn dest;\n}\n\nextern void init_graphics(void);\nextern void do_bios_call(uint32_t function, uint32_t arg1);\n\nextern uint32_t vbe_cont_info_mode_off;\nextern uint16_t vbe_info_pitch;\nextern uint16_t vbe_info_width;\nextern uint16_t vbe_info_height;\nextern uint8_t vbe_info_bpp;\nextern uint16_t vbe_info;\n\nvoid text_reset(void) {\n\t/* Hide the cursor */\n\toutportb(0x3D4, 14);\n\toutportb(0x3D5, 0xFF);\n\toutportb(0x3D4, 15);\n\toutportb(0x3D5, 0xFF);\n\n\t/* iirc this disables blink? */\n\tinportb(0x3DA);\n\toutportb(0x3C0, 0x30);\n\tchar b = inportb(0x3C1);\n\tb &= ~8;\n\toutportb(0x3c0, b);\n}\n\nextern int in_graphics_mode;\nint bios_text_mode(void) {\n\tdo_bios_call(3, 3);\n\n\textern char large_font[];\n\tdo_bios_call(5, (uintptr_t)large_font);\n\n\tvbe_info_width = 0;\n\tin_graphics_mode = 0;\n\ttext_reset();\n}\n\nint last_video_mode = -1;\nvoid bios_set_video(int mode) {\n\tlast_video_mode = mode;\n\tdo_bios_call(2, mode);\n\tdo_bios_call(3, mode | 0x4000);\n\tinit_graphics();\n}\n\nint bios_video_mode(void) {\n\tint best_match = 0;\n\tint match_score = 0;\n\n#define MATCH(w,h,s) if (match_score < s && vbe_info_width == w && vbe_info_height == h) { best_match = *x; match_score = s; }\n\n\tuint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF);\n\n\tfor (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF;  x++) {\n\t\t/* Query mode info */\n\t\tdo_bios_call(2, *x);\n\n\t\tif (!(vbe_info & (1 << 7))) continue;\n\t\tif (vbe_info_bpp < 24) continue;\n\n\t\tif (vbe_info_bpp == 32) {\n\t\t\tif (match_score < 9) { best_match = *x; match_score = 9; }\n\t\t\tMATCH(1024,768,10);\n\t\t\tMATCH(1280,720,50);\n\t\t\tMATCH(1280,800,60);\n\t\t\tMATCH(1440,900,75);\n\t\t\tMATCH(1920,1080,100);\n\t\t} else if (vbe_info_bpp == 24) {\n\t\t\tif (!match_score) { best_match = *x; match_score = 1; }\n\t\t\tMATCH(1024,768,3);\n\t\t\tMATCH(1280,720,4);\n\t\t\tMATCH(1280,800,5);\n\t\t\tMATCH(1440,900,6);\n\t\t\tMATCH(1920,1080,7);\n\t\t}\n\t}\n\n\tif (best_match) {\n\t\tbios_set_video(best_match);\n\t} else {\n\t\tvbe_info_width = 0;\n\t}\n\n}\n\nvoid bios_toggle_mode(void) {\n\tif (in_graphics_mode) {\n\t\tbios_text_mode();\n\t} else if (last_video_mode != -1) {\n\t\tbios_set_video(last_video_mode);\n\t}\n}\n\nint bios_main(void) {\n\t/* Zero BSS */\n\tmemset(&_bss_start,0,(uintptr_t)&_bss_end-(uintptr_t)&_bss_start);\n\n\ttext_reset();\n\tbios_video_mode();\n\n\n\treturn kmain();\n}\n\nextern volatile uint16_t dap_sectors;\nextern volatile uint32_t dap_buffer;\nextern volatile uint32_t dap_lba_low;\nextern volatile uint32_t dap_lba_high;\nextern volatile uint16_t drive_params_bps;\nextern uint8_t disk_space[];\n\nint bios_call(char * into, uint32_t sector) {\n\tdap_sectors = 2048 / drive_params_bps;\n\tdap_buffer = (uint32_t)disk_space;\n\tdap_lba_low = sector * dap_sectors;\n\tdap_lba_high = 0;\n\tdo_bios_call(1,0);\n\tmemcpy(into, disk_space, 2048);\n}\n\niso_9660_volume_descriptor_t * root = NULL;\niso_9660_directory_entry_t * dir_entry = NULL;\nstatic char * dir_entries = NULL;\n\nint navigate(char * name) {\n\tdir_entry = (iso_9660_directory_entry_t*)&root->root;\n\n\tdir_entries = (char*)(DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE);\n\tbios_call(dir_entries, dir_entry->extent_start_LSB);\n\tlong offset = 0;\n\twhile (1) {\n\t\tiso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)(dir_entries + offset);\n\t\tif (dir->length == 0) {\n\t\t\tif (offset < dir_entry->extent_length_LSB) {\n\t\t\t\toffset += 1;\n\t\t\t\tgoto try_again;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (!(dir->flags & FLAG_HIDDEN)) {\n\t\t\tchar file_name[dir->name_len + 1];\n\t\t\tmemcpy(file_name, dir->name, dir->name_len);\n\t\t\tfile_name[dir->name_len] = 0;\n\t\t\tchar * s = strchr(file_name,';');\n\t\t\tif (s) {\n\t\t\t\t*s = '\\0';\n\t\t\t}\n\t\t\tif (!strcmp(file_name, name)) {\n\t\t\t\tdir_entry = dir;\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t}\n\t\toffset += dir->length;\ntry_again:\n\t\tif ((long)(offset) > dir_entry->extent_length_LSB) break;\n\t}\n\n\treturn 0;\n}\n\n\n#endif\n"
  },
  {
    "path": "boot/qemu.c",
    "content": "/**\n * @brief Detects if we were booted with QEMU and processes fwcfg.\n *\n * Determines if we're running in QEMU and looks for \"fw_cfg\" values\n * that can override the boot mode.\n *\n * TODO This should be perfectly capable of passing a full command\n *      line through the fw_cfg interface, but right now we just look\n *      at some bootmode strings...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdint.h>\n#include \"util.h\"\n#include \"menu.h\"\n#include \"options.h\"\n#include \"text.h\"\n\nstruct fw_cfg_file {\n\tuint32_t size;\n\tuint16_t select;\n\tuint16_t reserved;\n\tchar name[56];\n};\n\n\nvoid swap_bytes(void * in, int count) {\n\tchar * bytes = in;\n\tif (count == 4) {\n\t\tuint32_t * t = in;\n\t\t*t = (bytes[0] << 24) | (bytes[1] << 12) | (bytes[2] << 8) | bytes[3];\n\t} else if (count == 2) {\n\t\tuint16_t * t = in;\n\t\t*t = (bytes[0] << 8) | bytes[1];\n\t}\n}\n\nint detect_qemu(void) {\n\t/* Try to detect qemu headless boot */\n\toutports(0x510, 0x0000);\n\tif (inportb(0x511) == 'Q' &&\n\t\tinportb(0x511) == 'E' &&\n\t\tinportb(0x511) == 'M' &&\n\t\tinportb(0x511) == 'U') {\n\t\tuint32_t count = 0;\n\t\tuint8_t * bytes = (uint8_t *)&count;\n\t\toutports(0x510,0x0019);\n\t\tfor (int i = 0; i < 4; ++i) {\n\t\t\tbytes[i] = inportb(0x511);\n\t\t}\n\t\tswap_bytes(&count, 4);\n\n\t\tunsigned int bootmode_size = 0;\n\t\tint bootmode_index = -1;\n\t\tfor (unsigned int i = 0; i < count; ++i) {\n\t\t\tstruct fw_cfg_file file;\n\t\t\tuint8_t * tmp = (uint8_t *)&file;\n\t\t\tfor (int j = 0; j < sizeof(struct fw_cfg_file); ++j) {\n\t\t\t\ttmp[j] = inportb(0x511);\n\t\t\t}\n\t\t\tif (!strcmp(file.name,\"opt/org.toaruos.bootmode\")) {\n\t\t\t\tswap_bytes(&file.size, 4);\n\t\t\t\tswap_bytes(&file.select, 2);\n\t\t\t\tbootmode_size = file.size;\n\t\t\t\tbootmode_index = file.select;\n\t\t\t}\n\t\t}\n\n\t\tif (bootmode_index != -1) {\n\t\t\toutports(0x510, bootmode_index);\n\t\t\tchar tmp[33] = {0};\n\t\t\tfor (int i = 0; i < 32 && i < bootmode_size; ++i) {\n\t\t\t\ttmp[i] = inportb(0x511);\n\t\t\t}\n\t\t\tfor (int i = 0; i < base_sel + 1; ++i) {\n\t\t\t\tif (!strcmp(tmp,boot_mode_names[i].key)) {\n\t\t\t\t\tboot_mode = boot_mode_names[i].index;\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tprint_(\"fw_cfg boot mode not recognized: \");\n\t\t\tprint_(tmp);\n\t\t\tprint_(\"\\n\");\n\t\t}\n\t}\n\treturn 0;\n}\n\n"
  },
  {
    "path": "boot/qemu.h",
    "content": "#pragma once\nint detect_qemu(void);\n"
  },
  {
    "path": "boot/text.c",
    "content": "/**\n * @brief Abstractions for text output.\n *\n * Tries to provide a common interface to text output for\n * EFI framebuffer, BIOS VESA framebuffer, and BIOS VGA text mode.\n *\n * I don't know why I haven't added a full printf to this.\n *\n * A lot of this could be rewritten...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include \"text.h\"\n#include \"util.h\"\nint txt_debug = 0;\n\n#ifdef EFI_PLATFORM\n#include <efi.h>\nextern EFI_SYSTEM_TABLE *ST;\n#else\n#include <stdint.h>\n#endif\n\n#include \"../apps/terminal-font.h\"\n#define char_height LARGE_FONT_CELL_HEIGHT\n#define char_width  LARGE_FONT_CELL_WIDTH\n\nstatic int offset_x = 0;\nstatic int offset_y = 0;\nstatic int center_x = 0;\nstatic int center_y = 0;\n\nstatic void write_char(int x, int y, int val, int attr);\n\n#ifdef EFI_PLATFORM\n\nint in_graphics_mode = 1;\n\nEFI_GRAPHICS_OUTPUT_PROTOCOL * GOP;\n\nstatic EFI_GUID efi_graphics_output_protocol_guid =\n  {0x9042a9de,0x23dc,0x4a38,  {0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a}};\n\nint init_graphics() {\n\tUINTN count;\n\tEFI_HANDLE * handles;\n\tEFI_GRAPHICS_OUTPUT_PROTOCOL * gfx;\n\tEFI_STATUS status;\n\n\tstatus = uefi_call_wrapper(ST->BootServices->LocateHandleBuffer,\n\t\t\t5, ByProtocol, &efi_graphics_output_protocol_guid, NULL, &count, &handles);\n\tif (EFI_ERROR(status)) goto no_graphics;\n\tstatus = uefi_call_wrapper(ST->BootServices->HandleProtocol,\n\t\t\t3, handles[0], &efi_graphics_output_protocol_guid, (void **)&gfx);\n\tif (EFI_ERROR(status)) goto no_graphics;\n\n\tGOP = gfx;\n\n\tint total_width = GOP->Mode->Info->HorizontalResolution;\n\tint total_height = GOP->Mode->Info->VerticalResolution;\n\n\toffset_x = (total_width - 80 * char_width) / 2;\n\toffset_y = (total_height - 24 * char_height) / 2;\n\n\tcenter_x = total_width / 2;\n\tcenter_y = total_height / 2;\n\n\treturn 0;\n\nno_graphics:\n\treturn 1;\n}\n\nstatic void set_point(int x, int y, uint32_t color) {\n\t((uint32_t *)GOP->Mode->FrameBufferBase)[(x + offset_x) + (y + offset_y) * GOP->Mode->Info->PixelsPerScanLine] = color;\n}\nvoid clear_() {\n\tx = 0;\n\ty = 0;\n\tmemset((void*)GOP->Mode->FrameBufferBase,0,GOP->Mode->FrameBufferSize);\n}\n\nstatic void placech(unsigned char c, int x, int y, int attr) {\n\twrite_char(x * char_width, y * char_height, c, attr);\n}\n\n#else\nextern uint32_t *vbe_info_fbaddr;\nextern uint16_t vbe_info_pitch;\nextern uint16_t vbe_info_width;\nextern uint16_t vbe_info_height;\nextern uint8_t vbe_info_bpp;\n\nint in_graphics_mode = 0;\n\nvoid init_graphics(void) {\n\tif (!vbe_info_width) return;\n\tin_graphics_mode = 1;\n\toffset_x = (vbe_info_width - 80 * char_width) / 2;\n\toffset_y = (vbe_info_height - 24 * char_height) / 2;\n\n\tcenter_x = vbe_info_width / 2;\n\tcenter_y = vbe_info_height / 2;\n}\n\nstatic void set_point(int x, int y, uint32_t color) {\n\tif (vbe_info_bpp == 24) {\n\t\t*((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch))     = (color >> 0) & 0xFF;\n\t\t*((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch) + 1) = (color >> 8) & 0xFF;\n\t\t*((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch) + 2) = (color >> 16) & 0xFF;\n\t} else if (vbe_info_bpp == 32) {\n\t\tvbe_info_fbaddr[(x + offset_x) + (y + offset_y) * (vbe_info_pitch >> 2)] = color;\n\t}\n}\n\nstatic unsigned short * textmemptr = (unsigned short *)0xB8000;\n\nstatic void placech_vga(unsigned char c, int x, int y, int attr) {\n\tunsigned short *where;\n\tunsigned att = attr << 8;\n\twhere = textmemptr + (y * 80 + x);\n\t*where = c | att;\n}\n\nstatic void placech(unsigned char c, int x, int y, int attr) {\n\tin_graphics_mode ? write_char(x * char_width, y * char_height, c, attr) : placech_vga(c,x,y,attr);\n}\n\nvoid clear_() {\n\tx = 0;\n\ty = 0;\n\tif (in_graphics_mode) {\n\t\tmemset(vbe_info_fbaddr, 0, vbe_info_pitch * vbe_info_height);\n\t} else {\n\t\tfor (int y = 0; y < 24; ++y) {\n\t\t\tfor (int x = 0; x < 80; ++x) {\n\t\t\t\tplacech_vga(' ', x, y, 0x00);\n\t\t\t}\n\t\t}\n\t}\n}\n#endif\n\nstatic uint32_t term_colors[] = {\n\t0xFF000000,\n\t0xFFCC0000,\n\t0xFF4E9A06,\n\t0xFFC4A000,\n\t0xFF3465A4,\n\t0xFF75507B,\n\t0xFF06989A,\n\t0xFFD3D7CF,\n\n\t0xFF555753,\n\t0xFFEF2929,\n\t0xFF8AE234,\n\t0xFFFCE94F,\n\t0xFF729FCF,\n\t0xFFAD7FA8,\n\t0xFF34E2E2,\n\t0xFFEEEEEC,\n};\n\nchar vga_to_ansi[] = {\n\t0, 4, 2, 6, 1, 5, 3, 7,\n\t8,12,10,14, 9,13,11,15\n};\n\n\nstatic void write_char(int x, int y, int val, int attr) {\n\tif (val > 128) {\n\t\tval = 4;\n\t}\n\n\tuint32_t fg_color = term_colors[vga_to_ansi[attr & 0xF]];\n\tuint32_t bg_color = term_colors[vga_to_ansi[(attr >> 4) & 0xF]];\n\n\tuint32_t colors[] = {bg_color, fg_color};\n\n\tuint8_t * c = large_font[val];\n\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\tset_point(x+j,y+i,colors[!!(c[i] & (1 << LARGE_FONT_MASK-j))]);\n\t\t}\n\t}\n}\n\n\nint x = 0;\nint y = 0;\nint attr = 0x07;\n\nvoid print_(char * str) {\n\twhile (*str) {\n\t\tif (*str == '\\n') {\n\t\t\tfor (; x < 80; ++x) {\n\t\t\t\tplacech(' ', x, y, attr);\n\t\t\t}\n\t\t\tx = 0;\n\t\t\ty += 1;\n\t\t\tif (y == 24) {\n\t\t\t\ty = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tplacech(*str, x, y, attr);\n\t\t\tx++;\n\t\t\tif (x == 80) {\n\t\t\t\tx = 0;\n\t\t\t\ty += 1;\n\t\t\t\tif (y == 24) {\n\t\t\t\t\ty = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tstr++;\n\t}\n}\n\nvoid move_cursor(int _x, int _y) {\n\tx = _x;\n\ty = _y;\n}\n\nvoid set_attr(int _attr) {\n\tattr = _attr;\n}\n\nvoid print_banner(char * str) {\n\tif (!str) {\n\t\tfor (int i = 0; i < 80; ++i) {\n\t\t\tplacech(' ', i, y, attr);\n\t\t}\n\t\ty++;\n\t\treturn;\n\t}\n\tint len = 0;\n\tchar *c = str;\n\twhile (*c) {\n\t\tlen++;\n\t\tc++;\n\t}\n\tint off = (80 - len) / 2;\n\n\tfor (int i = 0; i < 80; ++i) {\n\t\tplacech((i >= off && i - off < len) ? str[i-off] : ' ', i, y, attr);\n\t}\n\n\ty++;\n}\n\nvoid print_hex_(unsigned int value) {\n\tchar out[9] = {0};\n\tfor (int i = 7; i > -1; i--) {\n\t\tout[i] = \"0123456789abcdef\"[(value >> (4 * (7 - i))) & 0xF];\n\t}\n\tprint_(out);\n}\n\nvoid print_int_(unsigned int value) {\n\tif (value == 0) {\n\t\tprint_(\"0\");\n\t\treturn;\n\t}\n\tunsigned int n_width = 1;\n\tchar tmp[32] = {0};\n\tunsigned int val = value;\n\twhile (val >= 10UL) {\n\t\tval /= 10UL;\n\t\tn_width++;\n\t}\n\tint i = n_width;\n\twhile (i > 0) {\n\t\ttmp[i-1] = (value % 10UL) + '0';\n\t\tvalue /= 10UL;\n\t\ti--;\n\t}\n\twhile (i < n_width) {\n\t\tchar t[2] = {tmp[i],'\\0'};\n\t\tprint_(t);\n\t\ti++;\n\t}\n}\n\nstatic void draw_square(int x, int y, int stage) {\n\tfor (int _y = 0; _y < 7; ++_y) {\n\t\tunsigned int color_green = 0xB2 - (y * 8 + _y) * 2;\n\t\tunsigned int color_blue  = 0xFF;\n\n\t\tif (stage > 0 && y + 1 != stage) {\n\t\t\tcolor_green /= 2;\n\t\t\tcolor_blue  /= 2;\n\t\t}\n\n\t\tunsigned int color = 0xFF000000 | (color_green << 8) | color_blue;\n\n\t\tfor (int _x = 0; _x < 7; ++_x) {\n\t\t\tset_point(center_x - 32 - offset_x + x * 8 + _x, center_y - 32 - offset_y + y * 8 + _y, color);\n\t\t}\n\t}\n}\n\nvoid draw_logo(int stage) {\n\tif (!in_graphics_mode) {\n\t\tmove_cursor(0,0);\n\t\tprint_(\"Loading... \");\n\t\tchar tmp[2] = {0};\n\t\ttmp[0] = \"/-\\\\|/-\\\\|\"[stage];\n\t\tprint_(tmp);\n\t\treturn;\n\t}\n\tuint64_t logo_squares = 0x981818181818FFFFUL;\n\tfor (int y = 0; y < 8; ++y) {\n\t\tfor (int x = 0; x < 8; ++x) {\n\t\t\tif (logo_squares & (1 << x)) {\n\t\t\t\tdraw_square(x,y,stage);\n\t\t\t}\n\t\t}\n\t\tlogo_squares >>= 8;\n\t}\n}\n"
  },
  {
    "path": "boot/text.h",
    "content": "#pragma once\n\nextern int txt_debug, x, y, attr;\n\nvoid move_cursor(int _x, int _y);\nvoid set_attr(int _attr);\nvoid print_(char * str);\nvoid print_hex_(unsigned int value);\nvoid print_int_(unsigned int value);\nvoid clear_();\n\nvoid print_banner(char * str);\n\n#define print(s) do {if (txt_debug) {print_(s);}} while(0)\n#define clear() do {if (txt_debug) {clear_();}} while(0)\n#define print_hex(d) do {if (txt_debug) {print_hex_(d);}} while(0)\n\n"
  },
  {
    "path": "boot/util.c",
    "content": "/**\n * @brief Utility functions.\n *\n * Kind of a barebones libc.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include \"util.h\"\n\nint strcmp(const char * l, const char * r) {\n\tfor (; *l == *r && *l; l++, r++);\n\treturn *(unsigned char *)l - *(unsigned char *)r;\n}\n\nchar * strchr(const char * s, int c) {\n\twhile (*s) {\n\t\tif (*s == c) {\n\t\t\treturn (char *)s;\n\t\t}\n\t\ts++;\n\t}\n\treturn 0;\n}\n\nunsigned long strlen(const char *s) {\n\tunsigned long out = 0;\n\twhile (*s) {\n\t\tout++;\n\t\ts++;\n\t}\n\treturn out;\n}\n\nchar * strcat(char *dest, const char *src) {\n\tchar * end = dest;\n\twhile (*end != '\\0') {\n\t\t++end;\n\t}\n\twhile (*src) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t}\n\t*end = '\\0';\n\treturn dest;\n}\n\n"
  },
  {
    "path": "boot/util.h",
    "content": "#pragma once\n\nstatic inline unsigned short inports(unsigned short _port) {\n\tunsigned short rv;\n\tasm volatile (\"inw %1, %0\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nstatic inline void outports(unsigned short _port, unsigned short _data) {\n\tasm volatile (\"outw %1, %0\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nstatic inline unsigned int inportl(unsigned short _port) {\n\tunsigned int rv;\n\tasm volatile (\"inl %%dx, %%eax\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nstatic inline void outportl(unsigned short _port, unsigned int _data) {\n\tasm volatile (\"outl %%eax, %%dx\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nstatic inline unsigned char inportb(unsigned short _port) {\n\tunsigned char rv;\n\tasm volatile (\"inb %1, %0\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nstatic inline void outportb(unsigned short _port, unsigned char _data) {\n\tasm volatile (\"outb %1, %0\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nstatic inline void inportsm(unsigned short port, unsigned char * data, unsigned long size) {\n\tasm volatile (\"rep insw\" : \"+D\" (data), \"+c\" (size) : \"d\" (port) : \"memory\");\n}\n\nvoid * memcpy(void * restrict dest, const void * restrict src, long n);\nvoid * memset(void * dest, int c, long n);\nint strcmp(const char * l, const char * r);\nchar * strchr(const char * s, int c);\nchar * strcat(char *dest, const char *src);\nvoid copy_sectors(unsigned long lba, unsigned char * buf, int sectors);\nvoid copy_sector(unsigned long lba, unsigned char * buf);\nunsigned long strlen(const char *s);\n\n#define DATA_LOAD_BASE 0x4000000\n"
  },
  {
    "path": "boot/video.c",
    "content": "/**\n * @brief Video mode management.\n *\n * Tries to abstract away differences between VESA mode setting\n * on BIOS and GOP mode setting on UEFI. Also provides the\n * video mode selection menu.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include \"text.h\"\n#include \"util.h\"\n#include \"kbd.h\"\n\n#ifdef EFI_PLATFORM\n#include <efi.h>\nextern EFI_SYSTEM_TABLE *ST;\n#else\n#include <stdint.h>\n#endif\n\nint platform_count_modes(int *);\nint platform_list_modes(int sel, int select_this_mode);\nextern void init_graphics(void);\n\nvoid mode_selector(int sel, int ndx, char *str) {\n\tset_attr(sel == ndx ? 0x70 : 0x07);\n\tprint_(str);\n\tif (x < 26) {\n\t\twhile (x < 25) {\n\t\t\tprint_(\" \");\n\t\t}\n\t\tx = 26;\n\t} else if (x < 52) {\n\t\twhile (x < 51) {\n\t\t\tprint_(\" \");\n\t\t}\n\t\tx = 52;\n\t} else {\n\t\tprint_(\"\\n\");\n\t}\n}\n\nstatic char * print_int_into(char * str, unsigned int value) {\n\tunsigned int n_width = 1;\n\tunsigned int i = 9;\n\twhile (value > i && i < UINT32_MAX) {\n\t\tn_width += 1;\n\t\ti *= 10;\n\t\ti += 9;\n\t}\n\n\tchar buf[n_width+1];\n\tfor (int i = 0; i < n_width + 1; i++) {\n\t\tbuf[i] = 0;\n\t}\n\ti = n_width;\n\twhile (i > 0) {\n\t\tunsigned int n = value / 10;\n\t\tint r = value % 10;\n\t\tbuf[i - 1] = r + '0';\n\t\ti--;\n\t\tvalue = n;\n\t}\n\tfor (char * c = buf; *c; c++) {\n\t\t*str++ = *c;\n\t}\n\treturn str;\n}\n\nint video_menu(void) {\n\tclear_();\n\n\tint sel = 0;\n\tint sel_max = platform_count_modes(&sel);\n\tint select_this_mode = 0;\n\n\tint s = 0;\n\n\tdo {\n\t\tmove_cursor(0,0);\n\t\tset_attr(0x1f);\n\t\tprint_banner(\"Select Video Mode\");\n\t\tset_attr(0x07);\n\t\tprint_(\"\\n\");\n\n\t\tif (platform_list_modes(sel,select_this_mode)) return 0;\n\n\t\tread_again:\n\t\ts = read_scancode(0);\n\t\tif (s == 0x50) { /* DOWN */\n\t\t\tif (sel >= 0 && sel < sel_max - 1) {\n\t\t\t\tsel = (sel + 3) % sel_max;\n\t\t\t} else {\n\t\t\t\tsel = (sel + 1)  % sel_max;\n\t\t\t}\n\t\t} else if (s == 0x48) { /* UP */\n\t\t\tif (sel >= 1) {\n\t\t\t\tsel = (sel_max + sel - 3)  % sel_max;\n\t\t\t} else {\n\t\t\t\tsel = (sel_max + sel - 1)  % sel_max;\n\t\t\t}\n\t\t} else if (s == 0x4B) { /* LEFT */\n\t\t\tif (sel >= 0) {\n\t\t\t\tif (sel % 3 != 0) {\n\t\t\t\t\tsel = (sel - 1) % sel_max;\n\t\t\t\t} else {\n\t\t\t\t\tsel += 2;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (s == 0x4D) { /* RIGHT */\n\t\t\tif (sel >= 0) {\n\t\t\t\tif (sel % 3 != 2) {\n\t\t\t\t\tsel = (sel + 1) % sel_max;\n\t\t\t\t} else {\n\t\t\t\t\tsel -= 2;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (s == 0x1c) {\n\t\t\tselect_this_mode = 1;\n\t\t\tcontinue;\n\t\t} else if (s == 0x01) {\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tgoto read_again;\n\t\t}\n\t} while (1);\n}\n\n#ifdef EFI_PLATFORM\nextern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP;\nint platform_list_modes(int sel, int select_this_mode) {\n\tint index = 0;\n\tfor (int i = 0; i < GOP->Mode->MaxMode; ++i) {\n\t\tEFI_STATUS status;\n\t\tUINTN size;\n\t\tEFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info;\n\n\t\tstatus = uefi_call_wrapper(GOP->QueryMode,\n\t\t\t\t4, GOP, i, &size, &info);\n\n\t\tif (EFI_ERROR(status) || info->PixelFormat != 1) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (select_this_mode && sel == index) {\n\t\t\tuefi_call_wrapper(GOP->SetMode, 2, GOP, i);\n\t\t\tinit_graphics();\n\t\t\treturn 1;\n\t\t}\n\n\t\tchar tmp[100];\n\t\tchar * t = tmp;\n\t\tt = print_int_into(t, info->HorizontalResolution); *t = 'x'; t++;\n\t\tt = print_int_into(t, info->VerticalResolution); *t = '\\0';\n\t\tmode_selector(sel, index, tmp);\n\t\tindex++;\n\t}\n\treturn 0;\n}\n\nint platform_count_modes(int * current) {\n\tint index = 0;\n\tfor (int i = 0; i < GOP->Mode->MaxMode; ++i) {\n\t\tEFI_STATUS status;\n\t\tUINTN size;\n\t\tEFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info;\n\t\tstatus = uefi_call_wrapper(GOP->QueryMode,\n\t\t\t\t4, GOP, i, &size, &info);\n\t\tif (EFI_ERROR(status) || info->PixelFormat != 1) {\n\t\t\tcontinue;\n\t\t} else {\n\t\t\tindex++;\n\t\t}\n\t}\n\treturn index;\n}\n#else\nextern void do_bios_call(uint32_t function, uint32_t arg1);\n\nextern uint32_t vbe_cont_info_mode_off;\n\nstruct ColorFormat {\n\tuint8_t mask;\n\tuint8_t offset;\n};\n\nstruct VbeMode {\n\tuint16_t attributes;\n\tuint16_t old_shit;\n\tuint16_t granularity;\n\tuint16_t window_size;\n\tuint32_t segments;\n\tuint32_t old_bank_switching_thing;\n\tuint16_t pitch;\n\tuint16_t width;\n\tuint16_t height;\n\tuint16_t w_y;\n\tuint8_t  planes;\n\tuint8_t  bpp;\n\tuint8_t  banks;\n\tuint8_t  memory_model;\n\tuint8_t  bank_size;\n\tuint8_t  pages;\n\tuint8_t  reserved;\n\tstruct ColorFormat red;\n\tstruct ColorFormat green;\n\tstruct ColorFormat blue;\n\tstruct ColorFormat alpha;\n\tuint8_t color_attributes;\n\tuint32_t framebuffer_addr;\n\tuint32_t memory_offset;\n\tuint32_t memory_size;\n\tuint8_t other[206];\n} __attribute__((packed));\n\nextern volatile struct VbeMode vbe_info;\nstatic struct VbeMode vbe_info_save;\n\nstatic int qualified(void) {\n\tif (!(vbe_info.attributes & (1 << 7))) return 0;\n\tif (vbe_info.bpp < 24) return 0;\n\tif (vbe_info.width < 640) return 0;\n\tif (vbe_info.height < 480) return 0;\n}\n\nstatic char tmp[40];\n\nint platform_list_modes(int sel, int select_this_mode) {\n\tuint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF);\n\tmemcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode));\n\tint index = 0;\n\tfor (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF;  x++) {\n\t\tdo_bios_call(2, *x);\n\t\tif (!qualified()) {\n\t\t\tmemcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (select_this_mode && sel == index) {\n\t\t\textern void bios_set_video(int);\n\t\t\tbios_set_video(*x);\n\t\t\treturn 1;\n\t\t}\n\n\t\tchar * t = tmp;\n\t\tt = print_int_into(t, vbe_info.width); *t = 'x'; t++;\n\t\tt = print_int_into(t, vbe_info.height); *t = 'x'; t++;\n\t\tt = print_int_into(t, vbe_info.bpp); *t = '\\0';\n\n\t\tmemcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));\n\n\t\tmode_selector(sel, index, tmp);\n\t\tindex++;\n\t}\n\n\treturn 0;\n}\n\nextern int last_video_mode;\nint platform_count_modes(int * current) {\n\tuint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF);\n\tint count = 0;\n\tmemcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode));\n\tfor (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF;  x++) {\n\t\tif (*x == last_video_mode) *current = count;\n\t\tdo_bios_call(2, *x);\n\t\tif (!qualified()) continue;\n\t\tcount++;\n\t}\n\tmemcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode));\n\treturn count;\n}\n\n#endif\n"
  },
  {
    "path": "build/aarch64.mk",
    "content": "ARCH=aarch64\n\nARCH_KERNEL_CFLAGS = -z max-page-size=0x1000 -nostdlib -mgeneral-regs-only -mno-outline-atomics -ffixed-x18 --sysroot=base\nARCH_USER_CFLAGS = -Wno-psabi --sysroot=base\n\nTARGET=aarch64-unknown-toaru\n\nall: system\nsystem: misaka-kernel ramdisk.igz bootstub kernel8.img | $(BUILD_KRK)\n\nmisaka-kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o kernel/arch/aarch64/link.ld\n\t${CC} -g -T kernel/arch/${ARCH}/link.ld ${KERNEL_CFLAGS} -o $@ ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o\n\nBOOTSTUB_OBJS  = $(patsubst %.c,%.o,$(wildcard kernel/arch/aarch64/bootstub/*.c))\nBOOTSTUB_OBJS += $(patsubst %.S,%.o,$(wildcard kernel/arch/aarch64/bootstub/*.S))\nBOOTSTUB_OBJS += kernel/misc/kprintf.o kernel/misc/string.o\n\nbootstub: ${BOOTSTUB_OBJS} kernel/arch/aarch64/bootstub/link.ld\n\t${CC} -g -T kernel/arch/aarch64/bootstub/link.ld ${KERNEL_CFLAGS} -o $@ ${BOOTSTUB_OBJS}\n\n\nRPI400_OBJS  = $(patsubst %.c,%.o,$(wildcard kernel/arch/aarch64/rpi400/*.c))\nRPI400_OBJS += $(patsubst %.S,%.o,$(wildcard kernel/arch/aarch64/rpi400/*.S))\nRPI400_OBJS += kernel/misc/kprintf.o kernel/misc/string.o\n\nkernel/arch/aarch64/rpi400/start.o: misaka-kernel ramdisk.igz\nkernel8.img: ${RPI400_OBJS} kernel/arch/aarch64/rpi400/link.ld\n\t${CC} -g -T kernel/arch/aarch64/rpi400/link.ld ${KERNEL_CFLAGS} -o $@.elf ${RPI400_OBJS}\n\t${OC} $@.elf -O binary $@\n\nQEMU = qemu-system-aarch64\n\nEMU_MACH = virt\nEMU_CPU  = cortex-a72\nSMP ?= 4\nRAM ?= 4G\n\nEMU_ARGS  = -M $(EMU_MACH)\nEMU_ARGS += -m $(RAM)\nEMU_ARGS += -smp $(SMP)\nEMU_ARGS += -cpu $(EMU_CPU)\nEMU_ARGS += -no-reboot\nEMU_ARGS += -serial mon:stdio\nEMU_ARGS += -device ramfb\nEMU_ARGS += -device virtio-tablet-pci    # Mouse with absolute positioning\nEMU_ARGS += -device virtio-keyboard-pci  # Keyboard\nEMU_ARGS += -device AC97\nEMU_ARGS += -d guest_errors\nEMU_ARGS += -net user\nEMU_ARGS += -netdev hubport,id=u1,hubid=0, -device e1000e,netdev=u1\nEMU_ARGS += -name \"ToaruOS ${ARCH}\"\n\nEMU_RAMDISK = -fw_cfg name=opt/org.toaruos.initrd,file=ramdisk.igz\nEMU_KERNEL  = -fw_cfg name=opt/org.toaruos.kernel,file=misaka-kernel\n\nrun: system\n\t${QEMU} ${EMU_ARGS} -kernel bootstub  -append \"root=/dev/ram0 migrate start=live-session ramfb vid=preset\" ${EMU_RAMDISK} ${EMU_KERNEL}\n\nhvf: EMU_CPU = host -accel hvf\nhvf: system\n\t${QEMU} ${EMU_ARGS} -kernel bootstub  -append \"root=/dev/ram0 migrate start=live-session ramfb vid=preset\" ${EMU_RAMDISK} ${EMU_KERNEL}\n\ndebug: system\n\t${QEMU} ${EMU_ARGS} -kernel bootstub  -append \"root=/dev/ram0 migrate start=live-session vid=auto\" ${EMU_RAMDISK} ${EMU_KERNEL} -d int 2>&1\n\nBUILD_KRK=$(TOOLCHAIN)/local/bin/kuroko\n$(TOOLCHAIN)/local/bin/kuroko: kuroko/src/*.c\n\tmkdir -p $(TOOLCHAIN)/local/bin\n\tcc -Ikuroko/src -DKRK_BUNDLE_LIBS=\"BUNDLED(os);BUNDLED(fileio);\" -DNO_RLINE -DKRK_STATIC_ONLY -DKRK_DISABLE_THREADS -o \"${TOOLCHAIN}/local/bin/kuroko\" kuroko/src/*.c kuroko/src/modules/module_os.c kuroko/src/modules/module_fileio.c\n\n"
  },
  {
    "path": "build/x86_64.mk",
    "content": "ARCH=x86_64\n\nARCH_KERNEL_CFLAGS  = -mno-red-zone -fno-omit-frame-pointer -mfsgsbase -fPIE\nARCH_KERNEL_CFLAGS += -mgeneral-regs-only -z max-page-size=0x1000 -nostdlib\nARCH_USER_CFLAGS += -z max-page-size=0x1000\n\nTARGET=x86_64-pc-toaru\n\n# Configs you can override.\n#   SMP: Argument to -smp, use 1 to disable SMP.\n#   RAM: Argument to -m, QEMU takes suffixes like \"M\" or \"G\".\n#   EXTRA_ARGS: Added raw to the QEMU command line\n#   EMU_KVM: Unset this (EMU_KVM=) to use TCG, or replace it with something like EMU_KVM=-enable-haxm\n#   EMU_MACH: Argument to -M, 'pc' should be the older default in QEMU; we use q35 to test AHCI.\nSMP ?= 4\nRAM ?= 3G\nEXTRA_ARGS ?=\nEMU_MACH ?= q35\n\nEMU_KVM  ?= -enable-kvm\n\nEMU_ARGS  = -M q35\nEMU_ARGS += -m $(RAM)\nEMU_ARGS += -smp $(SMP)\nEMU_ARGS += ${EMU_KVM}\nEMU_ARGS += -no-reboot\nEMU_ARGS += -serial mon:stdio\nEMU_ARGS += -device AC97\nEMU_ARGS += -name \"ToaruOS ${ARCH}\"\n\n# UTC is the default setting.\n#EMU_ARGS += -rtc base=utc\n\n# Customize network options here. QEMU's default is an e1000(e) under PIIX (Q35), with user networking\n# so we don't need to do anything normally.\n#EMU_ARGS += -net user\n#EMU_ARGS += -netdev hubport,id=u1,hubid=0, -device e1000e,netdev=u1  -object filter-dump,id=f1,netdev=u1,file=qemu-e1000e.pcap\n#EMU_ARGS += -netdev hubport,id=u2,hubid=0, -device e1000e,netdev=u2\n\n# Add an XHCI tablet if you want to dev on USB\n#EMU_ARGS += -device qemu-xhci -device usb-tablet\n\nall: system\nsystem: image.iso\n\nrun: system\n\t${EMU} ${EMU_ARGS} -cdrom image.iso\n\nfast: system\n\t${EMU} ${EMU_ARGS} -cdrom image.iso \\\n\t\t-fw_cfg name=opt/org.toaruos.bootmode,string=normal \\\n\nrun-vga: system\n\t${EMU} ${EMU_ARGS} -cdrom image.iso \\\n\t\t-fw_cfg name=opt/org.toaruos.bootmode,string=vga \\\n\ntest: system\n\t${EMU} -M ${EMU_MACH} -m $(RAM) -smp $(SMP) ${EMU_KVM} -kernel misaka-kernel -initrd ramdisk.igz,util/init.krk -append \"root=/dev/ram0 init=/dev/ram1\" \\\n\t\t-nographic -no-reboot -audiodev none,id=id -serial null -serial mon:stdio \\\n\t\t-device qemu-xhci -device usb-tablet -trace \"usb*\"\n\nshell: system\n\t${EMU} -M ${EMU_MACH} -m $(RAM) -smp $(SMP) ${EMU_KVM} -cdrom image.iso \\\n\t\t-nographic -no-reboot -audiodev none,id=id -serial null -serial mon:stdio \\\n\t\t-fw_cfg name=opt/org.toaruos.gettyargs,string=\"-a local /dev/ttyS1 115200 ${TERM}\" \\\n\t\t-fw_cfg name=opt/org.toaruos.bootmode,string=headless\n\nmisaka-kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o\n\t${CC} -g -T kernel/arch/${ARCH}/link.ld ${KERNEL_CFLAGS} -Wl,-static,-pie,--no-dynamic-linker,-z,notext,-z,norelro -o $@.64 ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o\n\tcp $@.64 $@\n\t${STRIP} $@\n\n# Loader stuff, legacy CDs\nfatbase/ramdisk.igz: ramdisk.igz\n\tcp $< $@\nfatbase/kernel: misaka-kernel\n\tcp $< $@\n\ncdrom/fat.img: fatbase/ramdisk.igz fatbase/kernel fatbase/efi/boot/bootx64.efi util/mkdisk.sh | dirs\n\tutil/mkdisk.sh $@ fatbase\n\ncdrom/boot.sys: boot/bios/boot.o $(patsubst boot/%.c,boot/bios/%.o,$(wildcard boot/*.c)) boot/link.ld | dirs\n\t${LD} -melf_i386 -T boot/link.ld -o $@ boot/bios/boot.o $(patsubst boot/%.c,boot/bios/%.o,$(wildcard boot/*.c))\n\nboot/bios/%.o: boot/%.c boot/*.h | dirs\n\t${CC} -m32 -c -Os -fno-pic -fno-pie -fno-strict-aliasing -finline-functions -ffreestanding -mgeneral-regs-only -o $@ $<\n\nboot/bios/boot.o: boot/boot.S | dirs\n\t${AS} --32 -o $@ $<\n\nEFI_CFLAGS=-fno-stack-protector -fpic -DEFI_PLATFORM -ffreestanding -fshort-wchar -I /usr/include/efi -mno-red-zone\nEFI_SECTIONS=-j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .reloc\nEFI_LINK=/usr/lib/crt0-efi-x86_64.o -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared -Bsymbolic -L /usr/lib -lefi -lgnuefi\n\nboot/efi/%.o: boot/%.c boot/*.h | dirs\n\t$(CC) ${EFI_CFLAGS} -I /usr/include/efi/x86_64 -DEFI_FUNCTION_WRAPPER -c -o $@ $<\n\nboot/efi64.so: $(patsubst boot/%.c,boot/efi/%.o,$(wildcard boot/*.c)) boot/*.h\n\t$(LD) $(patsubst boot/%.c,boot/efi/%.o,$(wildcard boot/*.c)) ${EFI_LINK} -o $@\n\nfatbase/efi/boot/bootx64.efi: boot/efi64.so\n\tmkdir -p fatbase/efi/boot\n\tobjcopy ${EFI_SECTIONS} --target=efi-app-x86_64 $< $@\n\nBUILD_KRK=$(TOOLCHAIN)/local/bin/kuroko\n$(TOOLCHAIN)/local/bin/kuroko: kuroko/src/*.c\n\tmkdir -p $(TOOLCHAIN)/local/bin\n\tcc -Ikuroko/src -DKRK_BUNDLE_LIBS=\"BUNDLED(os);BUNDLED(fileio);\" -DNO_RLINE -DKRK_STATIC_ONLY -DKRK_DISABLE_THREADS -o \"${TOOLCHAIN}/local/bin/kuroko\" kuroko/src/*.c kuroko/src/modules/module_os.c kuroko/src/modules/module_fileio.c\n\nimage.iso: cdrom/fat.img cdrom/boot.sys boot/mbr.S util/update-extents.krk | $(BUILD_KRK)\n\txorriso -as mkisofs -R -J -c bootcat \\\n\t  -b boot.sys -no-emul-boot -boot-load-size full \\\n\t  -eltorito-alt-boot -e fat.img -no-emul-boot -isohybrid-gpt-basdat \\\n\t  -o image.iso cdrom\n\t${AS} --32 $$(kuroko util/make_mbr.krk) -o boot/mbr.o boot/mbr.S\n\t${LD} -melf_i386 -T boot/link.ld -o boot/mbr.sys boot/mbr.o\n\ttail -c +513 image.iso > image.dat\n\tcat boot/mbr.sys image.dat > image.iso\n\trm image.dat\n\tkuroko util/update-extents.krk\n\n"
  },
  {
    "path": "kernel/arch/aarch64/arch.c",
    "content": "/**\n * @file  kernel/arch/aarch64/arch.c\n * @brief Global functions with arch-specific implementations.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/args.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/spinlock.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/aarch64/regs.h>\n#include <kernel/arch/aarch64/gic.h>\n#include <kernel/arch/aarch64/dtb.h>\n\n/**\n * @brief Enter userspace.\n *\n * Called by process startup.\n * Does not return.\n *\n * @param entrypoint Address to \"return\" to in userspace.\n * @param argc       Number of arguments to provide to the new process.\n * @param argv       Argument array to pass to the new process; make sure this is user-accessible!\n * @param envp       Environment strings array\n * @param stack      Userspace stack address.\n */\nvoid arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack) {\n\tasm volatile(\n\t\t\"msr ELR_EL1, %0\\n\" /* entrypoint */\n\t\t\"msr SP_EL0, %1\\n\" /* stack */\n\t\t\"msr SPSR_EL1, %2\\n\" /* EL 0 */\n\t\t::\n\t\t\"r\"(entrypoint), \"r\"(stack), \"r\"(0));\n\n\tupdate_process_times_on_exit();\n\n\tregister uint64_t x0 __asm__(\"x0\") = argc;\n\tregister uint64_t x1 __asm__(\"x1\") = (uintptr_t)argv;\n\tregister uint64_t x2 __asm__(\"x2\") = (uintptr_t)envp;\n\tregister uint64_t x4 __asm__(\"x4\") = (uintptr_t)this_core->sp_el1;\n\n\tasm volatile(\n\t\t\"mov sp, x4\\n\"\n\t\t\"eret\" :: \"r\"(x0), \"r\"(x1), \"r\"(x2), \"r\"(x4));\n\n\t__builtin_unreachable();\n}\n\nstatic void _kill_it(uintptr_t addr, const char * action, const char * desc, size_t size) {\n\tdprintf(\"core %d (pid=%d %s): invalid stack for signal %s (%#zx '%s' %zu)\\n\",\n\t\tthis_core->cpu_id, this_core->current_process->id, this_core->current_process->name, action, addr, desc, size);\n\ttask_exit(((128 + SIGSEGV) << 8) | SIGSEGV);\n}\n\n#define PUSH(stack, type, item) do { \\\n\tstack -= sizeof(type); \\\n\tif (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),MMU_PTR_WRITE)) \\\n\t\t_kill_it((uintptr_t)stack,\"entry\",#item,sizeof(type)); \\\n\t*((volatile type *) stack) = item; \\\n} while (0)\n\n#define POP(stack, type, item) do { \\\n\tif (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),0)) \\\n\t\t_kill_it((uintptr_t)stack,\"return\",#item,sizeof(type)); \\\n\titem = *((volatile type *) stack); \\\n\tstack += sizeof(type); \\\n} while (0)\n\nint arch_return_from_signal_handler(struct regs *r) {\n\tuintptr_t spsr;\n\tuintptr_t sp = r->user_sp;\n\n\t/* Restore floating point */\n\tPOP(sp, uintptr_t, this_core->current_process->thread.context.saved[13]);\n\tPOP(sp, uintptr_t, this_core->current_process->thread.context.saved[12]);\n\tfor (int i = 0; i < 64; ++i) {\n\t\tPOP(sp, uint64_t, this_core->current_process->thread.fp_regs[63-i]);\n\t}\n\tarch_restore_floating((process_t*)this_core->current_process);\n\n\tPOP(sp, sigset_t, this_core->current_process->blocked_signals);\n\tlong originalSignal;\n\tPOP(sp, long, originalSignal);\n\n\t/* Interrupt system call status */\n\tPOP(sp, long, this_core->current_process->interrupted_system_call);\n\n\t/* Process state */\n\tPOP(sp, uintptr_t, spsr);\n\tthis_core->current_process->thread.context.saved[11] = (spsr & 0xf0000000);\n\tasm volatile (\"msr SPSR_EL1, %0\" :: \"r\"(this_core->current_process->thread.context.saved[11]));\n\tPOP(sp, uintptr_t, this_core->current_process->thread.context.saved[10]);\n\tasm volatile (\"msr ELR_EL1, %0\" :: \"r\"(this_core->current_process->thread.context.saved[10]));\n\n\t/* Interrupt context registers */\n\tPOP(sp, struct regs, *r);\n\n\tasm volatile (\"msr SP_EL0, %0\" :: \"r\"(r->user_sp));\n\treturn originalSignal;\n}\n\n/**\n * @brief Enter a userspace signal handler.\n *\n * Similar to @c arch_enter_user but also setups up magic return addresses.\n *\n * Since signal handlers do to take complicated argument arrays, this only\n * supplies a @p signum argument.\n *\n * Does not return.\n *\n * @param entrypoint Userspace address of the signal handler, set by the process.\n * @param signum     Signal number that caused this entry.\n */\nvoid arch_enter_signal_handler(uintptr_t entrypoint, int signum, struct regs *r) {\n\n\tuintptr_t sp = (r->user_sp - 128) & 0xFFFFFFFFFFFFFFF0;\n\n\t/* Save essential registers */\n\tPUSH(sp, struct regs, *r);\n\n\t/* Save context that wasn't pushed above... */\n\tasm volatile (\"mrs %0, ELR_EL1\" : \"=r\"(this_core->current_process->thread.context.saved[10]));\n\tPUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[10]);\n\tasm volatile (\"mrs %0, SPSR_EL1\" : \"=r\"(this_core->current_process->thread.context.saved[11]));\n\tPUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[11]);\n\n\tPUSH(sp, long, this_core->current_process->interrupted_system_call);\n\tthis_core->current_process->interrupted_system_call = 0;\n\n\tPUSH(sp, long, signum);\n\tPUSH(sp, sigset_t, this_core->current_process->blocked_signals);\n\n\tstruct signal_config * config = (struct signal_config*)&this_core->current_process->signals[signum];\n\tthis_core->current_process->blocked_signals |= config->mask | (config->flags & SA_NODEFER ? 0 : (1UL << signum));\n\n\t/* Save floating point */\n\tarch_save_floating((process_t*)this_core->current_process);\n\tfor (int i = 0; i < 64; ++i) {\n\t\tPUSH(sp, uint64_t, this_core->current_process->thread.fp_regs[i]);\n\t}\n\tPUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[12]);\n\tPUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[13]);\n\n\tupdate_process_times_on_exit();\n\n\tasm volatile(\n\t\t\"msr ELR_EL1, %0\\n\" /* entrypoint */\n\t\t\"msr SP_EL0, %1\\n\" /* stack */\n\t\t\"msr SPSR_EL1, %2\\n\" /* spsr from context */\n\t\t::\n\t\t\"r\"(entrypoint),\n\t\t\"r\"(sp),\n\t\t\"r\"(0));\n\n\tregister uint64_t x0 __asm__(\"x0\") = signum;\n\tregister uint64_t x30 __asm__(\"x30\") = 0x516;\n\tregister uint64_t x4 __asm__(\"x4\") = (uintptr_t)this_core->sp_el1;\n\n\tasm volatile(\n\t\t\"mov sp, x4\\n\"\n\t\t\"eret\\nnop\\nnop\" :: \"r\"(x0), \"r\"(x30), \"r\"(x4));\n\n\t__builtin_unreachable();\n}\n\n/**\n * @brief Save FPU registers for this thread.\n */\nvoid arch_restore_floating(process_t * proc) {\n\tasm volatile (\n\t\t\"ldr q0 , [%0, #(0 * 16)]\\n\"\n\t\t\"ldr q1 , [%0, #(1 * 16)]\\n\"\n\t\t\"ldr q2 , [%0, #(2 * 16)]\\n\"\n\t\t\"ldr q3 , [%0, #(3 * 16)]\\n\"\n\t\t\"ldr q4 , [%0, #(4 * 16)]\\n\"\n\t\t\"ldr q5 , [%0, #(5 * 16)]\\n\"\n\t\t\"ldr q6 , [%0, #(6 * 16)]\\n\"\n\t\t\"ldr q7 , [%0, #(7 * 16)]\\n\"\n\t\t\"ldr q8 , [%0, #(8 * 16)]\\n\"\n\t\t\"ldr q9 , [%0, #(9 * 16)]\\n\"\n\t\t\"ldr q10, [%0, #(10 * 16)]\\n\"\n\t\t\"ldr q11, [%0, #(11 * 16)]\\n\"\n\t\t\"ldr q12, [%0, #(12 * 16)]\\n\"\n\t\t\"ldr q13, [%0, #(13 * 16)]\\n\"\n\t\t\"ldr q14, [%0, #(14 * 16)]\\n\"\n\t\t\"ldr q15, [%0, #(15 * 16)]\\n\"\n\t\t\"ldr q16, [%0, #(16 * 16)]\\n\"\n\t\t\"ldr q17, [%0, #(17 * 16)]\\n\"\n\t\t\"ldr q18, [%0, #(18 * 16)]\\n\"\n\t\t\"ldr q19, [%0, #(19 * 16)]\\n\"\n\t\t\"ldr q20, [%0, #(20 * 16)]\\n\"\n\t\t\"ldr q21, [%0, #(21 * 16)]\\n\"\n\t\t\"ldr q22, [%0, #(22 * 16)]\\n\"\n\t\t\"ldr q23, [%0, #(23 * 16)]\\n\"\n\t\t\"ldr q24, [%0, #(24 * 16)]\\n\"\n\t\t\"ldr q25, [%0, #(25 * 16)]\\n\"\n\t\t\"ldr q26, [%0, #(26 * 16)]\\n\"\n\t\t\"ldr q27, [%0, #(27 * 16)]\\n\"\n\t\t\"ldr q28, [%0, #(28 * 16)]\\n\"\n\t\t\"ldr q29, [%0, #(29 * 16)]\\n\"\n\t\t\"ldr q30, [%0, #(30 * 16)]\\n\"\n\t\t\"ldr q31, [%0, #(31 * 16)]\\n\"\n\t\t\"msr fpcr, %1\\n\"\n\t\t\"msr fpsr, %2\\n\"\n\t\t::\"r\"(&proc->thread.fp_regs),\n\t\t\"r\"(proc->thread.context.saved[12]),\n\t\t\"r\"(proc->thread.context.saved[13])\n\t\t);\n}\n\n/**\n * @brief Restore FPU registers for this thread.\n */\nvoid arch_save_floating(process_t * proc) {\n\tasm volatile (\n\t\t\"str q0 , [%2, #(0 * 16)]\\n\"\n\t\t\"str q1 , [%2, #(1 * 16)]\\n\"\n\t\t\"str q2 , [%2, #(2 * 16)]\\n\"\n\t\t\"str q3 , [%2, #(3 * 16)]\\n\"\n\t\t\"str q4 , [%2, #(4 * 16)]\\n\"\n\t\t\"str q5 , [%2, #(5 * 16)]\\n\"\n\t\t\"str q6 , [%2, #(6 * 16)]\\n\"\n\t\t\"str q7 , [%2, #(7 * 16)]\\n\"\n\t\t\"str q8 , [%2, #(8 * 16)]\\n\"\n\t\t\"str q9 , [%2, #(9 * 16)]\\n\"\n\t\t\"str q10, [%2, #(10 * 16)]\\n\"\n\t\t\"str q11, [%2, #(11 * 16)]\\n\"\n\t\t\"str q12, [%2, #(12 * 16)]\\n\"\n\t\t\"str q13, [%2, #(13 * 16)]\\n\"\n\t\t\"str q14, [%2, #(14 * 16)]\\n\"\n\t\t\"str q15, [%2, #(15 * 16)]\\n\"\n\t\t\"str q16, [%2, #(16 * 16)]\\n\"\n\t\t\"str q17, [%2, #(17 * 16)]\\n\"\n\t\t\"str q18, [%2, #(18 * 16)]\\n\"\n\t\t\"str q19, [%2, #(19 * 16)]\\n\"\n\t\t\"str q20, [%2, #(20 * 16)]\\n\"\n\t\t\"str q21, [%2, #(21 * 16)]\\n\"\n\t\t\"str q22, [%2, #(22 * 16)]\\n\"\n\t\t\"str q23, [%2, #(23 * 16)]\\n\"\n\t\t\"str q24, [%2, #(24 * 16)]\\n\"\n\t\t\"str q25, [%2, #(25 * 16)]\\n\"\n\t\t\"str q26, [%2, #(26 * 16)]\\n\"\n\t\t\"str q27, [%2, #(27 * 16)]\\n\"\n\t\t\"str q28, [%2, #(28 * 16)]\\n\"\n\t\t\"str q29, [%2, #(29 * 16)]\\n\"\n\t\t\"str q30, [%2, #(30 * 16)]\\n\"\n\t\t\"str q31, [%2, #(31 * 16)]\\n\"\n\t\t\"mrs %0, fpcr\\n\"\n\t\t\"mrs %1, fpsr\\n\"\n\t\t:\n\t\t\"=r\"(proc->thread.context.saved[12]),\n\t\t\"=r\"(proc->thread.context.saved[13])\n\t\t:\"r\"(&proc->thread.fp_regs)\n\t\t:\"memory\");\n}\n\n/**\n * @brief Prepare for a fatal event by stopping all other cores.\n */\nvoid arch_fatal_prepare(void) {\n\tif (processor_count > 1) {\n\t\tgic_send_sgi(2,-1);\n\t}\n}\n\n/**\n * @brief Halt all processors, including this one.\n * @see arch_fatal_prepare\n */\nvoid arch_fatal(void) {\n\tarch_fatal_prepare();\n\twhile (1) {\n\t\tasm volatile (\"wfi\");\n\t}\n}\n\nvoid arch_wakeup_others(void) {\n\t#if 1\n\tfor (int i = 0; i < processor_count; i++) {\n\t\tif (i == this_core->cpu_id) continue;\n\t\tif (!processor_local_data[i].current_process || (processor_local_data[i].current_process != processor_local_data[i].kernel_idle_task)) continue;\n\t\tgic_send_sgi(1,i);\n\t}\n\t#endif\n}\n\n\n/**\n * @brief Reboot the computer.\n *\n * At least on 'virt', there's a system control\n * register we can write to to reboot or at least\n * do a full shutdown.\n */\nlong arch_reboot(void) {\n\tarch_fatal_prepare(); /* Ensure other cores stop. */\n\n\t/* This semihosting SYS_EXIT interface only works under TCG */\n\tif (args_present(\"semihosting\")) {\n\t\tuint64_t payload[2] = {0x20026, 0x0};\n\t\tregister uint32_t w0 asm(\"w0\") = 0x18;\n\t\tregister uint64_t x1 asm(\"x1\") = (uintptr_t)payload;\n\t\tasm volatile (\"hlt #0xF000\" :: \"r\"(w0), \"r\"(x1) : \"memory\");\n\t}\n\n\tuint32_t * psci = dtb_find_node(\"psci\");\n\tif (psci) {\n\t\t/* Try to force the QEMU SYSTEM_RESET interface if we have PSCI at all. */\n\t\tregister uint64_t x0 asm(\"x0\") = 0x84000009;\n\t\tregister uint64_t x1 asm(\"x1\") = 0;\n\t\tasm volatile (\"hvc 0\" :: \"r\"(x0), \"r\"(x1) : \"memory\");\n\t}\n\n\tdprintf(\"aarch64: No reboot method. Halting.\\n\");\n\tarch_fatal();\n\treturn 0;\n}\n\nvoid aarch64_regs(struct regs *r) {\n#define reg(a,b) printf(\" X%02d=0x%016zx X%02d=0x%016zx\\n\",a,r->x ## a, b, r->x ## b)\n\treg(0,1);\n\treg(2,3);\n\treg(4,5);\n\treg(6,7);\n\treg(8,9);\n\treg(10,11);\n\treg(12,13);\n\treg(14,15);\n\treg(16,17);\n\treg(18,19);\n\treg(20,21);\n\treg(22,23);\n\treg(24,25);\n\treg(26,27);\n\treg(28,29);\n\tprintf(\" X30=0x%016zx  SP=0x%016zx\\n\", r->x30, r->user_sp);\n#undef reg\n}\n\nvoid aarch64_context(process_t * proc) {\n\tprintf(\"  SP=0x%016zx BP(x29)=0x%016zx\\n\", proc->thread.context.sp, proc->thread.context.bp);\n\tprintf(\"  IP=0x%016zx TLSBASE=0x%016zx\\n\", proc->thread.context.ip, proc->thread.context.tls_base);\n\tprintf(\" X19=0x%016zx     X20=%016zx\\n\", proc->thread.context.saved[0], proc->thread.context.saved[1]);\n\tprintf(\" X21=0x%016zx     X22=%016zx\\n\", proc->thread.context.saved[2], proc->thread.context.saved[3]);\n\tprintf(\" X23=0x%016zx     X24=%016zx\\n\", proc->thread.context.saved[4], proc->thread.context.saved[5]);\n\tprintf(\" X25=0x%016zx     X26=%016zx\\n\", proc->thread.context.saved[6], proc->thread.context.saved[7]);\n\tprintf(\" X27=0x%016zx     X28=%016zx\\n\", proc->thread.context.saved[8], proc->thread.context.saved[9]);\n\tprintf(\" ELR=0x%016zx    SPSR=%016zx\\n\", proc->thread.context.saved[10], proc->thread.context.saved[11]);\n\tprintf(\"fpcr=0x%016zx    fpsr=%016zx\\n\", proc->thread.context.saved[12], proc->thread.context.saved[13]);\n}\n\n/* Syscall parameter accessors */\nvoid arch_syscall_return(struct regs * r, long retval) { r->x0 = retval; }\nlong arch_syscall_number(struct regs * r) { return r->x0; }\nlong arch_syscall_arg0(struct regs * r)   { return r->x1; }\nlong arch_syscall_arg1(struct regs * r)   { return r->x2; }\nlong arch_syscall_arg2(struct regs * r)   { return r->x3; }\nlong arch_syscall_arg3(struct regs * r)   { return r->x4; }\nlong arch_syscall_arg4(struct regs * r)   { return r->x5; }\nlong arch_stack_pointer(struct regs * r)  { return r->user_sp; }\nlong arch_user_ip(struct regs * r)        { return r->x30; /* TODO this is wrong, this needs to come from ELR but we don't have that */ }\n\n/* No port i/o on arm, but these are still littered around some\n * drivers we need to remove... */\nunsigned short inports(unsigned short _port) { return 0; }\nunsigned int inportl(unsigned short _port)   { return 0; }\nunsigned char inportb(unsigned short _port)  { return 0; }\nvoid inportsm(unsigned short port, unsigned char * data, unsigned long size) {\n}\n\nvoid outports(unsigned short _port, unsigned short _data) {\n}\n\nvoid outportl(unsigned short _port, unsigned int _data) {\n}\n\nvoid outportb(unsigned short _port, unsigned char _data) {\n}\n\nvoid outportsm(unsigned short port, unsigned char * data, unsigned long size) {\n}\n\n/* From DRM sources */\n#define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \\\n                                 ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))\n#define DRM_FORMAT_XRGB8888     fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */\n\nstatic void ramfb_init(void) {\n\textern uint8_t * lfb_vid_memory;\n\textern uint16_t lfb_resolution_x;\n\textern uint16_t lfb_resolution_y;\n\textern uint16_t lfb_resolution_b;\n\textern uint32_t lfb_resolution_s;\n\textern size_t lfb_memsize;\n\n\tstruct ramfbcfg {\n\t\tuint64_t addr;\n\t\tuint32_t fourcc;\n\t\tuint32_t flags;\n\t\tuint32_t width;\n\t\tuint32_t height;\n\t\tuint32_t stride;\n\t} __attribute__((packed));\n\n\tuint32_t * fw_cfg = dtb_find_node_prefix(\"fw-cfg\");\n\tif (!fw_cfg) {\n\t\tdprintf(\"ramfb: no fw-cfg interface?\\n\");\n\t\treturn;\n\t}\n\tuint32_t * regs = dtb_node_find_property(fw_cfg, \"reg\");\n\tif (!regs) {\n\t\tdprintf(\"ramfb: no regs for fw-cfg\\n\");\n\t\treturn;\n\t}\n\n\tvolatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3])));\n\tvolatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr;\n\tvolatile uint32_t * fw_cfg_32   = (volatile uint32_t *)fw_cfg_addr;\n\tvolatile uint16_t * fw_cfg_sel  = (volatile uint16_t *)(fw_cfg_addr + 8);\n\n\t*fw_cfg_sel = 0;\n\n\tuint64_t response = fw_cfg_data[0];\n\t(void)response;\n\n\t/* Needs to be big-endian */\n\t*fw_cfg_sel = swizzle16(0x19);\n\n\t/* count response is 32-bit BE */\n\tuint32_t count = swizzle(fw_cfg_32[0]);\n\n\tstruct fw_cfg_file {\n\t\tuint32_t size;\n\t\tuint16_t select;\n\t\tuint16_t reserved;\n\t\tchar name[56];\n\t};\n\n\tstruct fw_cfg_file file;\n\tuint8_t * tmp = (uint8_t *)&file;\n\n\t/* Read count entries */\n\tfor (unsigned int i = 0; i < count; ++i) {\n\t\tfor (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) {\n\t\t\ttmp[j] = fw_cfg_addr[0];\n\t\t}\n\n\t\tfile.size = swizzle(file.size);\n\t\tfile.select = swizzle16(file.select);\n\n\t\tif (!strcmp(file.name,\"etc/ramfb\")) {\n\t\t\tstatic volatile struct ramfbcfg tmp;\n\t\t\tuintptr_t z = mmu_map_to_physical(NULL,(uint64_t)&tmp);\n\n\t\t\tlfb_resolution_x = 1440;\n\t\t\tlfb_resolution_y = 900;\n\t\t\tlfb_resolution_s = lfb_resolution_x*4;\n\t\t\tlfb_resolution_b = 32;\n\t\t\tlfb_memsize = lfb_resolution_s * lfb_resolution_y;\n\t\t\tuint64_t frames = lfb_memsize/4096;\n\t\t\tif ((lfb_memsize/4096)*4096!=lfb_memsize) frames++;\n\t\t\tuint64_t addr = mmu_allocate_n_frames(frames) << 12;\n\t\t\tlfb_vid_memory = mmu_map_from_physical(addr);\n\t\t\t/* Clear it while we're here */\n\t\t\tmemset(lfb_vid_memory, 0, lfb_memsize);\n\n\t\t\ttmp.addr = swizzle64(addr);\n\t\t\ttmp.width = swizzle(lfb_resolution_x);\n\t\t\ttmp.height = swizzle(lfb_resolution_y);\n\t\t\ttmp.flags = 0;\n\t\t\ttmp.fourcc = swizzle(DRM_FORMAT_XRGB8888);\n\t\t\ttmp.stride = swizzle(lfb_resolution_s);\n\n\t\t\tstatic volatile struct fwcfg_dma {\n\t\t\t\tvolatile uint32_t control;\n\t\t\t\tvolatile uint32_t length;\n\t\t\t\tvolatile uint64_t address;\n\t\t\t} dma __attribute__((aligned(4096)));\n\n\t\t\tdma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 4));\n\t\t\tdma.length  = swizzle(sizeof(struct ramfbcfg));\n\t\t\tdma.address = swizzle64(z);\n\n\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\t\t\tfw_cfg_data[2] = swizzle64(mmu_map_to_physical(NULL,(uint64_t)&dma));\n\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\n\nvoid arch_framebuffer_initialize(void) {\n\t/* I'm not sure we have any options here...\n\t * lfbvideo calls this expecting it to fill in information\n\t * on a preferred video mode; maybe dtb has that? */\n\tif (args_present(\"ramfb\")) {\n\t\tramfb_init();\n\t}\n}\n\nchar * _arch_args = NULL;\nconst char * arch_get_cmdline(void) {\n\t/* this should be available from dtb directly as a string */\n\textern char * _arch_args;\n\treturn _arch_args ? _arch_args : \"\";\n}\n\nconst char * arch_get_loader(void) {\n\treturn \"\";\n}\n\n/* These should probably assembly. */\nvoid arch_enter_tasklet(void) {\n\tasm volatile (\n\t\t\"ldp x0, x1, [sp], #16\\n\"\n\t\t\"br x1\\n\" ::: \"memory\");\n\t__builtin_unreachable();\n}\n\nstatic spin_lock_t deadlock_lock = { 0 };\nvoid _spin_panic(const char * lock_name, spin_lock_t * target) {\n\tarch_fatal_prepare();\n\twhile (__sync_lock_test_and_set(deadlock_lock.latch, 0x01));\n\tdprintf(\"core %d took over five seconds waiting to acquire %s (owner=%d in %s)\\n\",\n\t\tthis_core->cpu_id, lock_name, target->owner - 1, target->func);\n\t//arch_dump_traceback();\n\t__sync_lock_release(deadlock_lock.latch);\n\tarch_fatal();\n}\n\nvoid arch_spin_lock_acquire(const char * name, spin_lock_t * target, const char * func) {\n\t#if 0\n\tuint64_t expire = arch_perf_timer() + 5000000UL * arch_cpu_mhz();\n\t#endif\n\n\t/* \"loss of an exclusive monitor\" is one of the things that causes an \"event\",\n\t * so we spin on wfe to try to load-acquire the latch */\n\tasm volatile (\n\t\t\"sevl\\n\" /* And to avoid multiple jumps, we put the wfe first, so sevl will slide past the first one */\n\t\t\"1:\\n\"\n\t\t\"    wfe\\n\"\n#if 0\n\t);\n\n\t/* Yes, we can splice these assembly snippets with the clock check, this works fine.\n\t * If we've been trying to load the latch for five seconds, panic. */\n\tif (arch_perf_timer() > expire) {\n\t\t_spin_panic(name, target);\n\t}\n\n\tasm volatile (\n#endif\n\t\t\"2:\\n\"\n\t\t\"   ldaxr w2, [ %1 ]\\n\"     /* Acquire exclusive monitor and load latch value */\n\t\t\"   cbnz  w2, 1b\\n\"         /* If the latch value isn't 0, someone else owns the lock, go back to wfe and wait for them to release it */\n\t\t\"   stxr  w2, %0, [ %1 ]\\n\" /* Store our core number as the latch value. */\n\t\t\"   cbnz  w2, 2b\\n\"         /* If we failed to exclusively store, try to load again */\n\t\t::\"r\"(this_core->cpu_id+1),\"r\"(target->latch) : \"x2\",\"cc\",\"memory\");\n\n\t/* Set these for compatibility because there's at least one place we check them;\n\t * TODO: just use the latch value since we're setting it to the same thing anyway? */\n\ttarget->owner = this_core->cpu_id + 1;\n\n\t/* This is purely for debugging */\n\ttarget->func  = func;\n}\n\nvoid arch_spin_lock_release(spin_lock_t * target) {\n\t/* Clear owner debug data */\n\ttarget->owner = 0;\n\ttarget->func  = NULL;\n\n\t/* Release the exclusive monitor and clear the latch value */\n\t__atomic_store_n(target->latch, 0, __ATOMIC_RELEASE);\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/bootstub/bootstrap.S",
    "content": ".extern __bootstrap_stack_top\n.globl start\nstart:\n    ldr x30, =__bootstrap_stack_top\n    mov sp, x30\n    bl kmain\nhang:\n    b hang\n"
  },
  {
    "path": "kernel/arch/aarch64/bootstub/link.ld",
    "content": "OUTPUT_FORMAT(elf64-littleaarch64)\nENTRY(start)\n\nSECTIONS\n{\n\t. = 0x40100000;\n\tphys = .;\n\n\t.text BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.bootstrap)\n\t\tcode = .;\n\t\t*(.text)\n\t}\n\n\t.rodata BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.rodata)\n\t}\n\n\t.data BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tdata = .;\n\t\t*(.data)\n\t\t*(.symbols)\n\t\tPROVIDE(kernel_symbols_start = .);\n\t\tPROVIDE(kernel_symbols_end = .);\n\t\tPROVIDE(bss_start = .);\n\t}\n\n\t.bss BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tbss = .;\n\t\t*(COMMON)\n\t\t*(.bss)\n\t\t*(.stack)\n\t}\n\n\t/* Some built-in stack space... */\n\t. = ALIGN(0x1000);\n\t. = . + 0x1000;\n\t__bootstrap_stack_top = .;\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.eh_frame)\n\t\t*(.note.gnu.build-id)\n\t}\n\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/bootstub/main.c",
    "content": "/**\n * @file  kernel/arch/aarch64/bootstub/main.c\n * @brief Shim loader for QEMU virt machine.\n *\n * Loads at 0x4010_0000 where RAM is, sets up the MMU to have RAM\n * at our kernel virtual load address (0xffff_ffff_8000_0000), as\n * well as a direct mapping at -512GB for access to IO devices,\n * reads the kernel out of fw-cfg, loads it to the kernel virtual\n * load address, and then jumps to it.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/elf.h>\n\n//#define BOOTSTUB_LOG_ENABLED\n#define QEMU_DTB_BASE     0x40000000UL\n#define KERNEL_PHYS_BASE  0x41000000UL\n\nstatic uint32_t swizzle(uint32_t from) {\n\tuint8_t a = from >> 24;\n\tuint8_t b = from >> 16;\n\tuint8_t c = from >> 8;\n\tuint8_t d = from;\n\treturn (d << 24) | (c << 16) | (b << 8) | (a);\n}\n\nvoid * malloc(size_t x) {\n\tprintf(\"panic\\n\");\n\twhile (1);\n}\n\nstatic uint64_t swizzle64(uint64_t from) {\n\tuint8_t a = from >> 56;\n\tuint8_t b = from >> 48;\n\tuint8_t c = from >> 40;\n\tuint8_t d = from >> 32;\n\tuint8_t e = from >> 24;\n\tuint8_t f = from >> 16;\n\tuint8_t g = from >> 8;\n\tuint8_t h = from;\n\treturn ((uint64_t)h << 56) | ((uint64_t)g << 48) | ((uint64_t)f << 40) | ((uint64_t)e << 32) | (d << 24) | (c << 16) | (b << 8) | (a);\n}\n\nstatic uint16_t swizzle16(uint16_t from) {\n\tuint8_t a = from >> 8;\n\tuint8_t b = from;\n\treturn (b << 8) | (a);\n}\n\nstruct fdt_header {\n\tuint32_t magic;\n\tuint32_t totalsize;\n\tuint32_t off_dt_struct;\n\tuint32_t off_dt_strings;\n\tuint32_t off_mem_rsvmap;\n\tuint32_t version;\n\tuint32_t last_comp_version;\n\tuint32_t boot_cpuid_phys;\n\tuint32_t size_dt_strings;\n\tuint32_t size_dt_struct;\n};\n\nstatic uint32_t * parse_node(uint32_t * node, char * strings, int x) {\n\twhile (swizzle(*node) == 4) node++;\n\tif (swizzle(*node) == 9) return NULL;\n\tif (swizzle(*node) != 1) {\n\t\tprintf(\"Not a node? Got %x\\n\", swizzle(*node));\n\t\treturn NULL;\n\t}\n\n\t/* Skip the BEGIN_NODE */\n\tnode++;\n\n\tfor (int i = 0; i < x; ++i) printf(\"  \");\n\n\twhile (1) {\n\t\tchar * x = (char*)node;\n\t\tif (x[0]) { printf(\"%c\",x[0]); } else { node++; break; }\n\t\tif (x[1]) { printf(\"%c\",x[1]); } else { node++; break; }\n\t\tif (x[2]) { printf(\"%c\",x[2]); } else { node++; break; }\n\t\tif (x[3]) { printf(\"%c\",x[3]); } else { node++; break; }\n\t\tnode++;\n\t}\n\tprintf(\"\\n\");\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tfor (int i = 0; i < x; ++i) printf(\"  \");\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tuint32_t nameoff = swizzle(node[2]);\n\t\t\tprintf(\"  property %s len=%u\\n\", strings + nameoff, len);\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = parse_node(node, strings, x + 1);\n\t\t}\n\t}\n\n}\n\nstatic void dump_dtb(uintptr_t addr) {\n\n\tstruct fdt_header * fdt = (struct fdt_header*)addr;\n\n#define P(o) printf(#o \" = %#x\\n\", swizzle(fdt-> o))\n\tP(magic);\n\tP(totalsize);\n\tP(off_dt_struct);\n\tP(off_dt_strings);\n\tP(off_mem_rsvmap);\n\tP(version);\n\tP(last_comp_version);\n\tP(boot_cpuid_phys);\n\tP(size_dt_strings);\n\tP(size_dt_struct);\n\n\tchar * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings));\n\tuint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct));\n\n\tparse_node(dtb_struct, dtb_strings, 0);\n}\n\nstatic uint32_t * find_subnode(uint32_t * node, char * strings, const char * name, uint32_t ** node_out, int (*cmp)(const char* a, const char *b)) {\n\twhile (swizzle(*node) == 4) node++;\n\tif (swizzle(*node) == 9) return NULL;\n\tif (swizzle(*node) != 1) return NULL;\n\tnode++;\n\n\tif (cmp((char*)node,name)) {\n\t\t*node_out = node;\n\t\treturn NULL;\n\t}\n\n\twhile ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++;\n\tnode++;\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = find_subnode(node, strings, name, node_out, cmp);\n\t\t\tif (!node) return NULL;\n\t\t}\n\t}\n}\n\nstatic uint32_t * find_node_int(const char * name, int (*cmp)(const char*,const char*)) {\n\tuintptr_t addr = QEMU_DTB_BASE;\n\tstruct fdt_header * fdt = (struct fdt_header*)addr;\n\tchar * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings));\n\tuint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct));\n\n\tuint32_t * out = NULL;\n\tfind_subnode(dtb_struct, dtb_strings, name, &out, cmp);\n\treturn out;\n}\n\nstatic int base_cmp(const char *a, const char *b) {\n\treturn !strcmp(a,b);\n}\nstatic uint32_t * find_node(const char * name) {\n\treturn find_node_int(name,base_cmp);\n}\n\nstatic int prefix_cmp(const char *a, const char *b) {\n\treturn !memcmp(a,b,strlen(b));\n}\n\nstatic uint32_t * find_node_prefix(const char * name) {\n\treturn find_node_int(name,prefix_cmp);\n}\n\nstatic uint32_t * node_find_property_int(uint32_t * node, char * strings, const char * property, uint32_t ** out) {\n\twhile ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++;\n\tnode++;\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tuint32_t nameoff = swizzle(node[2]);\n\t\t\tif (!strcmp(strings + nameoff, property)) {\n\t\t\t\t*out = &node[1];\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = node_find_property_int(node+1, strings, property, out);\n\t\t\tif (!node) return NULL;\n\t\t}\n\t}\n}\n\nstatic uint32_t * node_find_property(uint32_t * node, const char * property) {\n\tuintptr_t addr = QEMU_DTB_BASE;\n\tstruct fdt_header * fdt = (struct fdt_header*)addr;\n\tchar * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings));\n\tuint32_t * out = NULL;\n\tnode_find_property_int(node, dtb_strings, property, &out);\n\treturn out;\n}\n\nstatic size_t _early_log_write(size_t size, uint8_t * buffer) {\n#ifdef BOOTSTUB_LOG_ENABLED\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\t*(volatile unsigned int *)(0x09000000) = buffer[i];\n\t}\n#endif\n\treturn size;\n}\n\nstatic size_t _later_log_write(size_t size, uint8_t * buffer) {\n#ifdef BOOTSTUB_LOG_ENABLED\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\t*(volatile unsigned int *)(0xffffff8009000000) = buffer[i];\n\t}\n#endif\n\treturn size;\n}\n\nstatic struct BaseTables {\n\tuintptr_t l0_base[512];\n\tuintptr_t l1_high_gbs[512];\n\tuintptr_t l1_low_gbs[512];\n\tuintptr_t l2_kernel[512];\n} _baseTables __attribute__((aligned(4096)));\n\n#define PTE_VALID      (1UL << 0)\n#define PTE_TABLE      (1UL << 1)\n\n/* Table attributes */\n#define PTE_NSTABLE    (1UL << 63)\n#define PTE_APTABLE    (3UL << 61) /* two bits */\n#define  PTE_APTABLE_A (1UL << 62)\n#define  PTE_APTABLE_B (1UL << 61)\n#define PTE_UXNTABLE   (1UL << 60)\n#define PTE_PXNTABLE   (1UL << 59)\n\n/* Block attributes */\n#define PTE_UXN        (1UL << 54)\n#define PTE_PXN        (1UL << 53)\n#define PTE_CONTIGUOUS (1UL << 52)\n#define PTE_NG         (1UL << 11)\n#define PTE_AF         (1UL << 10)\n#define PTE_SH         (3UL << 8)  /* two bits */\n#define  PTE_SH_A      (1UL << 9)\n#define  PTE_SH_B      (1UL << 8)\n#define PTE_AP         (3UL << 6)  /* two bits */\n#define  PTE_AP_A      (1UL << 7)\n#define  PTE_AP_B      (1UL << 6)\n#define PTE_NS         (1UL << 5)\n#define PTE_ATTRINDX   (7UL << 2) /* three bits */\n#define  PTE_ATTR_A    (1UL << 4)\n#define  PTE_ATTR_B    (1UL << 3)\n#define  PTE_ATTR_C    (1UL << 2)\n\nstatic void bootstub_mmu_init(void) {\n\t/* Map memory */\n\t_baseTables.l0_base[0]   = (uintptr_t)&_baseTables.l1_low_gbs | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* equivalent to high_base_pml */\n\t_baseTables.l0_base[511] = (uintptr_t)&_baseTables.l1_high_gbs | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* Mapping for us */\n\t_baseTables.l1_low_gbs[1] = QEMU_DTB_BASE | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\n\t/* -512GB is a map of 64GB of memory */\n\tfor (size_t i = 0; i < 64; ++i) {\n\t\t_baseTables.l1_high_gbs[i] = (i << 30) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t}\n\n\t/* -2GiB, map kernel here */\n\t_baseTables.l1_high_gbs[510] = (uintptr_t)&_baseTables.l2_kernel | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\tfor (size_t i = 0; i < 512; ++i) {\n\t\t_baseTables.l2_kernel[i] = (KERNEL_PHYS_BASE + (i << 21)) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t}\n\n\n\tuint64_t sctlr = 0\n\t\t| (1UL << 0)  /* mmu enabled */\n\t\t| (1UL << 2)  /* cachability */\n\t\t//| (1UL << 6)\n\t\t| (1UL << 12) /* instruction cachability */\n\t\t| (1UL << 23) /* SPAN */\n\t\t| (1UL << 28) /* nTLSMD */\n\t\t| (1UL << 29) /* LSMAOE */\n\t\t| (1UL << 20) /* TSCXT */\n\t\t| (1UL << 7)  /* ITD */\n\t;\n\n\t/* Translate control register */\n\tuint64_t tcr = 0\n\t\t|  (3UL << 32)  /* IPS 4TB? */\n\t\t|  (2UL << 30) /* TG1 4KB granules in TTBR1 */\n\t\t| (16UL << 16) /* T1SZ 48-bit */\n\t\t|  (3UL << 28)  /* SH1 */\n\t\t|  (1UL << 26)  /* ORGN1 */\n\t\t|  (1UL << 24)  /* IRGN1 */\n\t\t|  (0UL << 14) /* TG0 4KB granules in TTBR0 */\n\t\t| (16UL <<  0)  /* T0SZ 48-bit */\n\t\t|  (3UL << 12)  /* SH0 */\n\t\t|  (1UL << 10)  /* ORGN0 */\n\t\t|  (1UL <<  8)   /* IRGN0 */\n\t;\n\n\t/* MAIR setup? */\n\tuint64_t mair  = (0x000000000044ff00);\n\tasm volatile (\"msr MAIR_EL1,%0\" :: \"r\"(mair));\n\n\t/* Frob bits */\n\tprintf(\"bootstub: setting base values\\n\");\n\tasm volatile (\"msr TCR_EL1,%0\" : : \"r\"(tcr));\n\tasm volatile (\"msr TTBR0_EL1,%0\" : : \"r\"(&_baseTables.l0_base));\n\tasm volatile (\"msr TTBR1_EL1,%0\" : : \"r\"(&_baseTables.l0_base));\n\tprintf(\"bootstub: frobbing bits\\n\");\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\tasm volatile (\"msr SCTLR_EL1,%0\" : : \"r\"(sctlr));\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\t/* Point log output at new mmio address */\n\tprintf_output = &_later_log_write;\n\n\tprintf(\"bootstub: MMU initialized\\n\");\n}\n\nstatic void bootstub_read_kernel(uintptr_t kernel_load_addr) {\n\t/* See if we can find a qemu fw_cfg interface, we can use that for a ramdisk */\n\tuint32_t * fw_cfg = find_node_prefix(\"fw-cfg\");\n\tif (fw_cfg) {\n\t\tprintf(\"bootstub: found fw-cfg interface\\n\");\n\t\t/* best guess until we bother parsing these */\n\t\tuint32_t * regs = node_find_property(fw_cfg, \"reg\");\n\t\tif (regs) {\n\t\t\tprintf(\"bootstub:   length of regs = %u\\n\", swizzle(regs[0]));\n\t\t\tprintf(\"bootstub:   addr of fw-cfg = %#x\\n\", swizzle(regs[3]));\n\n\t\t\tvolatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(swizzle(regs[3]) + 0xffffff8000000000);\n\t\t\tvolatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr;\n\t\t\tvolatile uint32_t * fw_cfg_32   = (volatile uint32_t *)fw_cfg_addr;\n\t\t\tvolatile uint16_t * fw_cfg_sel  = (volatile uint16_t *)(fw_cfg_addr + 8);\n\n\t\t\t*fw_cfg_sel = 0;\n\n\t\t\tuint64_t response = fw_cfg_data[0];\n\n\t\t\tprintf(\"bootstub: response: %c%c%c%c\\n\",\n\t\t\t\t(char)(response >> 0),\n\t\t\t\t(char)(response >> 8),\n\t\t\t\t(char)(response >> 16),\n\t\t\t\t(char)(response >> 24));\n\n\t\t\t/* Needs to be big-endian */\n\t\t\t*fw_cfg_sel = swizzle16(0x19);\n\n\t\t\t/* count response is 32-bit BE */\n\t\t\tuint32_t count = swizzle(fw_cfg_32[0]);\n\t\t\tprintf(\"bootstub: %u entries\\n\", count);\n\n\t\t\tstruct fw_cfg_file {\n\t\t\t\tuint32_t size;\n\t\t\t\tuint16_t select;\n\t\t\t\tuint16_t reserved;\n\t\t\t\tchar name[56];\n\t\t\t};\n\n\t\t\tstruct fw_cfg_file file;\n\t\t\tuint8_t * tmp = (uint8_t *)&file;\n\n\t\t\t/* Read count entries */\n\t\t\tfor (unsigned int i = 0; i < count; ++i) {\n\t\t\t\tfor (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) {\n\t\t\t\t\ttmp[j] = fw_cfg_addr[0];\n\t\t\t\t}\n\n\t\t\t\t/* endian swap to get file size and selector ID */\n\t\t\t\tfile.size = swizzle(file.size);\n\t\t\t\tfile.select = swizzle16(file.select);\n\n\t\t\t\tprintf(\"bootstub: 0x%04x %s (%d bytes)\\n\",\n\t\t\t\t\tfile.select, file.name, file.size);\n\n\t\t\t\tif (!strcmp(file.name,\"opt/org.toaruos.kernel\")) {\n\t\t\t\t\tprintf(\"bootstub: Found kernel, loading\\n\");\n\t\t\t\t\tuint8_t * x = (uint8_t*)kernel_load_addr;\n\n\t\t\t\t\tstruct fwcfg_dma {\n\t\t\t\t\t\tvolatile uint32_t control;\n\t\t\t\t\t\tvolatile uint32_t length;\n\t\t\t\t\t\tvolatile uint64_t address;\n\t\t\t\t\t} dma;\n\n\t\t\t\t\tdma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 1));\n\t\t\t\t\tdma.length  = swizzle(file.size);\n\t\t\t\t\tdma.address = swizzle64((uintptr_t)x);\n\n\t\t\t\t\tfw_cfg_data[2] = swizzle64((uint64_t)&dma);\n\n\t\t\t\t\tif (dma.control) {\n\t\t\t\t\t\tprintf(\"bootstub: error on dma read?\\n\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n}\n\n\nstatic void bootstub_load_kernel(Elf64_Header * header) {\n\t/* Find load headers */\n\tfor (int i = 0; i < header->e_phnum; ++i) {\n\t\tElf64_Phdr * phdr = (void*)((uintptr_t)header + (header->e_phoff + header->e_phentsize * i));\n\t\tif (phdr->p_type == PT_LOAD) {\n\t\t\tprintf(\"bootstub: Load %zu bytes @ %zx from off %zx\\n\", phdr->p_memsz, phdr->p_vaddr, phdr->p_offset);\n\t\t\tmemset((void*)phdr->p_vaddr, 0, phdr->p_memsz);\n\t\t\tmemcpy((void*)phdr->p_vaddr, (void*)((uintptr_t)header + phdr->p_offset), phdr->p_filesz);\n\t\t} else {\n\t\t\tprintf(\"bootstub: Skip phdr %d\\n\", i);\n\t\t}\n\t}\n}\n\nstatic void bootstub_start_kernel(Elf64_Header * header) {\n\tprintf(\"bootstub: Jump to kernel entry point at %zx\\n\",\n\t\theader->e_entry);\n\n\tvoid (*entry)(uintptr_t,uintptr_t,uintptr_t) = (void(*)(uintptr_t,uintptr_t,uintptr_t))header->e_entry;\n\tentry(QEMU_DTB_BASE, KERNEL_PHYS_BASE, 0);\n}\n\nint kmain(void) {\n\textern char end[];\n\tuintptr_t kernel_load_addr = (uintptr_t)&end;\n\n\t/* Initialize log */\n\t*(volatile unsigned int *)(0x09000030) = 0x101;\n\tprintf_output = &_early_log_write;\n\tprintf(\"bootstub: Starting up\\n\");\n\n\t/* Set up MMU */\n\tbootstub_mmu_init();\n\n\t/* Read the kernel from fw-cfg */\n\tbootstub_read_kernel(kernel_load_addr);\n\n\t/* Examine kernel */\n\tElf64_Header *header = (void*)kernel_load_addr;\n\tbootstub_load_kernel(header);\n\n\t/* Jump to kernel */\n\tbootstub_start_kernel(header);\n\n\twhile (1) {}\n\treturn 0;\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/context.S",
    "content": "/**\n * @file kernel/arch/aarch64/context.S\n * @brief Kernel context switching utilities.\n *\n * arch_save_context and arch_restore_context are basically\n * setjmp and longjmp. _restore_context will also set the\n * previous thread as no longer running.\n */\n.globl arch_save_context\narch_save_context:\n    /* x0 has our struct pointer */\n    mrs x1, TPIDR_EL0\n    mov x2, sp\n    stp x2, x29,  [x0]\n    stp x30, x1,  [x0, (1 * 16)]\n    stp x19, x20, [x0, (2 * 16)]\n    stp x21, x22, [x0, (3 * 16)]\n    stp x23, x24, [x0, (4 * 16)]\n    stp x25, x26, [x0, (5 * 16)]\n    stp x27, x28, [x0, (6 * 16)]\n    mrs x1, ELR_EL1\n    mrs x2, SPSR_EL1\n    stp x1, x2,   [x0, (7 * 16)]\n    mov x0, 0\n    ret\n\n.globl arch_restore_context\narch_restore_context:\n    ldr x1, [x18, 16] /* get previous */\n    ldr x2, [x18, 0]  /* get current */\n    cmp x2, x1 /* compare current to prev, into x2 */\n    beq _restore_context_same\n    /* 20 is the offset of ->status. */\n    add x1, x1, 20\n    /* equivalent to __atomic_and_and_fetch */\n_restore_context_loop:\n    ldxr w2, [x1] /* Load exclusive */\n    and  w2, w2, 0xFFFFfff7 /* Unset running */\n    stlxr w4, w2, [x1] /* Store exclusive */\n    cbnz w4, _restore_context_loop /* try again if we failed */\n_restore_context_same:\n    /* x0 has our struct pointer */\n    ldp x2, x29,  [x0]\n    ldp x30, x1,  [x0, (1 * 16)]\n    ldp x19, x20, [x0, (2 * 16)]\n    ldp x21, x22, [x0, (3 * 16)]\n    ldp x23, x24, [x0, (4 * 16)]\n    ldp x25, x26, [x0, (5 * 16)]\n    ldp x27, x28, [x0, (6 * 16)]\n    msr TPIDR_EL0, x1\n    mov sp, x2\n    ldp x1, x2,   [x0, (7 * 16)]\n    msr ELR_EL1, x1\n    msr SPSR_EL1, x2\n    mov x0, 1\n    ret\n\n/**\n * @brief Start of userspace thread.\n */\n.globl arch_resume_user\narch_resume_user:\n    ldp x30, x0, [sp], #16\n    msr SP_EL0, x0\n    ldp x28, x29, [sp], #16\n    ldp x26, x27, [sp], #16\n    ldp x24, x25, [sp], #16\n    ldp x22, x23, [sp], #16\n    ldp x20, x21, [sp], #16\n    ldp x18, x19, [sp], #16\n    ldp x16, x17, [sp], #16\n    ldp x14, x15, [sp], #16\n    ldp x12, x13, [sp], #16\n    ldp x10, x11, [sp], #16\n    ldp x8, x9, [sp], #16\n    ldp x6, x7, [sp], #16\n    ldp x4, x5, [sp], #16\n    ldp x2, x3, [sp], #16\n    ldp x0, x1, [sp], #16\n    eret\n\n"
  },
  {
    "path": "kernel/arch/aarch64/dtb.c",
    "content": "/**\n * @file  kernel/arch/aarch64/dtb.c\n * @brief Methods for parsing device tree binaries\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/mmu.h>\n#include <kernel/misc.h>\n#include <kernel/args.h>\n#include <kernel/vfs.h>\n\n#include <kernel/arch/aarch64/dtb.h>\n\nuintptr_t aarch64_dtb_phys = 0;\n\nstatic uint32_t * parse_node(uint32_t * node, char * strings, int x) {\n\twhile (swizzle(*node) == 4) node++;\n\tif (swizzle(*node) == 9) return NULL;\n\tif (swizzle(*node) != 1) {\n\t\tprintf(\"Not a node? Got %x\\n\", swizzle(*node));\n\t\treturn NULL;\n\t}\n\n\t/* Skip the BEGIN_NODE */\n\tnode++;\n\n\tfor (int i = 0; i < x; ++i) printf(\"  \");\n\n\twhile (1) {\n\t\tchar * x = (char*)node;\n\t\tif (x[0]) { printf(\"%c\",x[0]); } else { node++; break; }\n\t\tif (x[1]) { printf(\"%c\",x[1]); } else { node++; break; }\n\t\tif (x[2]) { printf(\"%c\",x[2]); } else { node++; break; }\n\t\tif (x[3]) { printf(\"%c\",x[3]); } else { node++; break; }\n\t\tnode++;\n\t}\n\tprintf(\"\\n\");\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tfor (int i = 0; i < x; ++i) printf(\"  \");\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tuint32_t nameoff = swizzle(node[2]);\n\t\t\tprintf(\"  property %s len=%u\\n\", strings + nameoff, len);\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = parse_node(node, strings, x + 1);\n\t\t}\n\t}\n\n}\n\nstatic uint32_t * find_subnode(uint32_t * node, char * strings, const char * name, uint32_t ** node_out, int (*cmp)(const char* a, const char *b)) {\n\twhile (swizzle(*node) == 4) node++;\n\tif (swizzle(*node) == 9) return NULL;\n\tif (swizzle(*node) != 1) return NULL;\n\tnode++;\n\n\tif (cmp((char*)node,name)) {\n\t\t*node_out = node;\n\t\treturn NULL;\n\t}\n\n\twhile ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++;\n\tnode++;\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = find_subnode(node, strings, name, node_out, cmp);\n\t\t\tif (!node) return NULL;\n\t\t}\n\t}\n}\n\nstatic uint32_t * skip_node(uint32_t * node, void (*callback)(uint32_t * child)) {\n\twhile (swizzle(*node) == 4) node++;\n\tif (swizzle(*node) == 9) return NULL;\n\tif (swizzle(*node) != 1) return NULL;\n\tnode++;\n\twhile ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++;\n\tnode++;\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tif (callback) callback(node+1);\n\t\t\tnode = skip_node(node, NULL);\n\t\t\tif (!node) return NULL;\n\t\t}\n\t}\n}\n\nvoid dtb_callback_direct_children(uint32_t * node, void (*callback)(uint32_t * child)) {\n\tskip_node(node-1, callback);\n}\n\n\nstatic uint32_t * find_node_int(const char * name, int (*cmp)(const char*,const char*)) {\n\tuintptr_t addr = (uintptr_t)mmu_map_from_physical(aarch64_dtb_phys);\n\tstruct fdt_header * fdt = (struct fdt_header*)addr;\n\tchar * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings));\n\tuint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct));\n\n\tuint32_t * out = NULL;\n\tfind_subnode(dtb_struct, dtb_strings, name, &out, cmp);\n\treturn out;\n}\n\nstatic int base_cmp(const char *a, const char *b) {\n\treturn !strcmp(a,b);\n}\n\nuint32_t * dtb_find_node(const char * name) {\n\treturn find_node_int(name,base_cmp);\n}\n\nstatic int prefix_cmp(const char *a, const char *b) {\n\treturn !memcmp(a,b,strlen(b));\n}\n\nuint32_t * dtb_find_node_prefix(const char * name) {\n\treturn find_node_int(name,prefix_cmp);\n}\n\nstatic uint32_t * node_find_property_int(uint32_t * node, char * strings, const char * property, uint32_t ** out) {\n\twhile ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++;\n\tnode++;\n\n\twhile (1) {\n\t\twhile (swizzle(*node) == 4) node++;\n\t\tif (swizzle(*node) == 2) return node+1;\n\t\tif (swizzle(*node) == 3) {\n\t\t\tuint32_t len = swizzle(node[1]);\n\t\t\tuint32_t nameoff = swizzle(node[2]);\n\t\t\tif (!strcmp(strings + nameoff, property)) {\n\t\t\t\t*out = &node[1];\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tnode += 3;\n\t\t\tnode += (len + 3) / 4;\n\t\t} else if (swizzle(*node) == 1) {\n\t\t\tnode = node_find_property_int(node+1, strings, property, out);\n\t\t\tif (!node) return NULL;\n\t\t}\n\t}\n}\n\nuint32_t * dtb_node_find_property(uint32_t * node, const char * property) {\n\tuintptr_t addr = (uintptr_t)mmu_map_from_physical(aarch64_dtb_phys);\n\tstruct fdt_header * fdt = (struct fdt_header*)addr;\n\tchar * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings));\n\tuint32_t * out = NULL;\n\tnode_find_property_int(node, dtb_strings, property, &out);\n\treturn out;\n}\n\n/**\n * Figure out 1) how much actual memory we have and 2) what the last\n * address of physical memory is.\n */\nvoid dtb_memory_size(size_t * memaddr, size_t * physsize) {\n\tuint32_t * memory = dtb_find_node_prefix(\"memory\");\n\tif (!memory) {\n\t\tprintf(\"dtb: Could not find memory node.\\n\");\n\t\tarch_fatal();\n\t}\n\n\tuint32_t * regs = dtb_node_find_property(memory, \"reg\");\n\tif (!regs) {\n\t\tprintf(\"dtb: memory node has no regs\\n\");\n\t\tarch_fatal();\n\t}\n\n\t/* Eventually, same with fw-cfg, we'll need to actually figure out\n\t * the size of the 'reg' cells, but at least right now it's been\n\t * 2 address, 2 size. */\n\tuint64_t mem_addr = (uint64_t)swizzle(regs[3]) | ((uint64_t)swizzle(regs[2]) << 32UL);\n\tuint64_t mem_size = (uint64_t)swizzle(regs[5]) | ((uint64_t)swizzle(regs[4]) << 32UL);\n\n\t*memaddr = mem_addr;\n\t*physsize = mem_size;\n}\n\nvoid dtb_locate_cmdline(char ** args_out) {\n\tuint32_t * chosen = dtb_find_node(\"chosen\");\n\tif (chosen) {\n\t\tuint32_t * prop = dtb_node_find_property(chosen, \"bootargs\");\n\t\tif (prop) {\n\t\t\t*args_out = (char*)&prop[2];\n\t\t\targs_parse((char*)&prop[2]);\n\t\t}\n\t}\n}\n\nvoid dtb_pcie_base(void) {\n\textern uintptr_t pcie_ecam_phys;\n\n\tuint32_t * pcie = dtb_find_node_prefix(\"pcie\");\n\tif (pcie) {\n\t\t/* See if it has a regs */\n\t\tuint32_t * regs = dtb_node_find_property(pcie, \"reg\");\n\t\tif (regs) {\n\t\t\tpcie_ecam_phys = (uintptr_t)swizzle(regs[3]) | ((uintptr_t)swizzle(regs[2]) << 32UL);\n\t\t\tdprintf(\"dtb: pcie base is %#zx\\n\", (uint64_t)pcie_ecam_phys);\n\t\t}\n\t}\n}\n\nstatic ssize_t read_dtb(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tif ((size_t)offset > node->length) {\n\t\treturn 0;\n\t}\n\tif ((size_t)offset + size > node->length) {\n\t\tsize_t i = node->length - offset;\n\t\tsize = i;\n\t}\n\tmemcpy(buffer, (void *)((uintptr_t)mmu_map_from_physical(aarch64_dtb_phys) + (uintptr_t)offset), size);\n\treturn size;\n}\n\nvoid dtb_device(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tsnprintf(fnode->name, 10, \"dtb\");\n\tfnode->inode = 0;\n\tfnode->device = fnode;\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask    = 0770;\n\tfnode->length  = 1048576;\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_dtb;\n\tvfs_mount(\"/dev/dtb\", fnode, \"dtb\", \"\");\n}\n\n"
  },
  {
    "path": "kernel/arch/aarch64/entry.S",
    "content": "/**\n * @file kernel/arch/aarch64/entry.S\n * @brief Entrypoint stub.\n *\n * This gets us off of the bootstab's stack and onto\n * our own temporary stack, which we'll ideally switch\n * off of again when we start up the root process.\n */\n.extern __bootstrap_stack_top\n.globl start\nstart:\n    ldr x30, =__bootstrap_stack_top\n    mov sp, x30\n    bl kmain\nhang:\n    b hang\n\n\n"
  },
  {
    "path": "kernel/arch/aarch64/fwcfg.c",
    "content": "/**\n * @file  kernel/arch/aarch64/fwcfg.c\n * @brief Methods for dealing with QEMU's fw-cfg interface on aarch64.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/gzip.h>\n#include <kernel/mmu.h>\n#include <kernel/vfs.h>\n#include <errno.h>\n\n#include <kernel/arch/aarch64/dtb.h>\n\nstatic struct fwcfg_dma {\n\tvolatile uint32_t control;\n\tvolatile uint32_t length;\n\tvolatile uint64_t address;\n} dma __attribute__((aligned(4096)));\n\nvoid fwcfg_load_initrd(uintptr_t * ramdisk_phys_base, size_t * ramdisk_size) {\n\tuintptr_t z = 0;\n\tsize_t z_pages= 0;\n\tuintptr_t uz = 0;\n\tsize_t uz_pages = 0;\n\n\textern char end[];\n\tuintptr_t ramdisk_map_start = mmu_map_to_physical(NULL, (uintptr_t)&end);\n\n\t/* See if we can find a qemu fw_cfg interface, we can use that for a ramdisk */\n\tuint32_t * fw_cfg = dtb_find_node_prefix(\"fw-cfg\");\n\tif (fw_cfg) {\n\t\tdprintf(\"fw-cfg: found interface\\n\");\n\t\t/* best guess until we bother parsing these */\n\t\tuint32_t * regs = dtb_node_find_property(fw_cfg, \"reg\");\n\t\tif (regs) {\n\t\t\tvolatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3])));\n\t\t\tvolatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr;\n\t\t\tvolatile uint32_t * fw_cfg_32   = (volatile uint32_t *)fw_cfg_addr;\n\t\t\tvolatile uint16_t * fw_cfg_sel  = (volatile uint16_t *)(fw_cfg_addr + 8);\n\n\t\t\t*fw_cfg_sel = 0;\n\n\t\t\tuint64_t response = fw_cfg_data[0];\n\t\t\t(void)response;\n\n\t\t\t/* Needs to be big-endian */\n\t\t\t*fw_cfg_sel = swizzle16(0x19);\n\n\t\t\t/* count response is 32-bit BE */\n\t\t\tuint32_t count = swizzle(fw_cfg_32[0]);\n\n\t\t\tstruct fw_cfg_file {\n\t\t\t\tuint32_t size;\n\t\t\t\tuint16_t select;\n\t\t\t\tuint16_t reserved;\n\t\t\t\tchar name[56];\n\t\t\t};\n\n\t\t\tstruct fw_cfg_file file;\n\t\t\tuint8_t * tmp = (uint8_t *)&file;\n\n\t\t\t/* Read count entries */\n\t\t\tfor (unsigned int i = 0; i < count; ++i) {\n\t\t\t\tfor (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) {\n\t\t\t\t\ttmp[j] = fw_cfg_addr[0];\n\t\t\t\t}\n\n\t\t\t\t/* endian swap to get file size and selector ID */\n\t\t\t\tfile.size = swizzle(file.size);\n\t\t\t\tfile.select = swizzle16(file.select);\n\n\t\t\t\tif (!strcmp(file.name,\"opt/org.toaruos.initrd\")) {\n\t\t\t\t\tdprintf(\"fw-cfg: initrd found\\n\");\n\t\t\t\t\tz_pages = (file.size + 0xFFF) / 0x1000;\n\t\t\t\t\tz = ramdisk_map_start;\n\t\t\t\t\tramdisk_map_start += z_pages * 0x1000;\n\t\t\t\t\tuint8_t * x = mmu_map_from_physical(z);\n\n\t\t\t\t\tdma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 1));\n\t\t\t\t\tdma.length  = swizzle(file.size);\n\t\t\t\t\tdma.address = swizzle64(z);\n\n\t\t\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\t\t\t\t\tfw_cfg_data[2] = swizzle64(mmu_map_to_physical(NULL,(uint64_t)&dma));\n\t\t\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\n\t\t\t\t\tif (dma.control) {\n\t\t\t\t\t\tdprintf(\"fw-cfg: Error on DMA read (control: %#x)\\n\", dma.control);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tdprintf(\"fw-cfg: initrd loaded x=%#zx\\n\", (uintptr_t)x);\n\n\t\t\t\t\tif (x[0] == 0x1F && x[1] == 0x8B) {\n\t\t\t\t\t\tdprintf(\"fw-cfg: will attempt to read size from %#zx\\n\", (uintptr_t)(x + file.size - sizeof(uint32_t)));\n\t\t\t\t\t\tuint32_t size;\n\t\t\t\t\t\tmemcpy(&size, (x + file.size - sizeof(uint32_t)), sizeof(uint32_t));\n\t\t\t\t\t\tdprintf(\"fw-cfg: compressed ramdisk unpacks to %u bytes\\n\", size);\n\n\t\t\t\t\t\tuz_pages = (size + 0xFFF) / 0x1000;\n\t\t\t\t\t\tuz = ramdisk_map_start;\n\t\t\t\t\t\tramdisk_map_start += uz_pages * 0x1000;\n\t\t\t\t\t\tuint8_t * ramdisk = mmu_map_from_physical(uz);\n\n\t\t\t\t\t\tgzip_inputPtr = x;\n\t\t\t\t\t\tgzip_outputPtr = ramdisk;\n\t\t\t\t\t\tif (gzip_decompress()) {\n\t\t\t\t\t\t\tdprintf(\"fw-cfg: gzip failure, not mounting ramdisk\\n\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tmemmove(x, ramdisk, size);\n\n\t\t\t\t\t\tdprintf(\"fw-cfg: Unpacked ramdisk at %#zx\\n\", (uintptr_t)ramdisk);\n\t\t\t\t\t\t*ramdisk_phys_base = z;\n\t\t\t\t\t\t*ramdisk_size = size;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdprintf(\"fw-cfg: Ramdisk at %#zx\\n\", (uintptr_t)x);\n\t\t\t\t\t\t*ramdisk_phys_base = z;\n\t\t\t\t\t\t*ramdisk_size = file.size;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic ssize_t read_fwcfg(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tvolatile uint8_t * addr;\n\tif (offset == 0x510) {\n\t\t/* Read selector. Must be 16 bit; selector will be byte swapped from big-endian. */\n\t\taddr = (uint8_t*)node->device + 8;\n\t\tif (size != 2) return -EINVAL;\n\t\tuint16_t b = *(volatile uint16_t *)addr;\n\t\tb = (b >> 8) | ((b & 0xFF) << 8);\n\t\t*(uint16_t*)buffer = b;\n\t} else if (offset == 0x511) {\n\t\t/* Read one data byte */\n\t\taddr = (uint8_t*)node->device;\n\t\tif (size != 1) return -EINVAL;\n\t\t*buffer = *addr;\n\t} else {\n\t\treturn -EINVAL;\n\t}\n\treturn size;\n}\n\nstatic ssize_t write_fwcfg(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tvolatile uint8_t * addr;\n\tif (offset == 0x510) {\n\t\t/* Write selector. Must be 16 bit; selector will be byte swapped to big-endian. */\n\t\taddr = (uint8_t*)node->device + 8;\n\t\tif (size != 2) return -EINVAL;\n\t\tuint16_t b = *(uint16_t *)buffer;\n\t\tb = (b >> 8) | ((b & 0xFF) << 8);\n\t\t*(volatile uint16_t*)addr = b;\n\t} else if (offset == 0x511) {\n\t\t/* Write one data byte. */\n\t\taddr = (uint8_t*)node->device;\n\t\tif (size != 1) return -EINVAL;\n\t\t*addr = *buffer;\n\t} else {\n\t\treturn -EINVAL;\n\t}\n\treturn size;\n}\n\nvoid fwcfg_device(void) {\n\tuint32_t * fw_cfg = dtb_find_node_prefix(\"fw-cfg\");\n\tif (!fw_cfg) return;\n\tuint32_t * regs = dtb_node_find_property(fw_cfg, \"reg\");\n\tif (!regs) return;\n\n\tuint8_t * fw_cfg_addr = (uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3])));\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0, sizeof(fs_node_t));\n\tstrcpy(fnode->name, \"fwcfg\");\n\tfnode->flags  = FS_BLOCKDEVICE;\n\tfnode->mask   = 0660;\n\tfnode->read   = read_fwcfg;\n\tfnode->write  = write_fwcfg;\n\tfnode->device = fw_cfg_addr;\n\n\tchar addr[100];\n\tsnprintf(addr, 99, \"%p\", (void*)fw_cfg_addr);\n\tvfs_mount(\"/dev/fwcfg\", fnode, \"qemu-fwcfg\", addr);\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/gic.c",
    "content": "/**\n * @file  kernel/arch/aarch64/virtio.c\n * @brief Rudimentary, hacky implementations of virtio input devices.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/pci.h>\n#include <kernel/process.h>\n#include <kernel/spinlock.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/aarch64/dtb.h>\n#include <kernel/arch/aarch64/gic.h>\n\nstruct irq_callback * irq_callbacks[256] = {0};\n\nstatic spin_lock_t irq_acquire;\n\nvolatile uint32_t * gic_regs = NULL;\nvolatile uint32_t * gicc_regs = NULL;\n\nvoid gic_map_regs(uintptr_t rpi_tag) {\n\tif (rpi_tag) {\n\t\tgic_regs = (volatile uint32_t*)mmu_map_mmio_region(0xff841000, 0x1000);\n\t\tgicc_regs = (volatile uint32_t*)mmu_map_mmio_region(0xff842000, 0x2000);\n\t} else {\n\t\tgic_regs = (volatile uint32_t*)mmu_map_mmio_region(0x08000000, 0x1000);\n\t\tgicc_regs = (volatile uint32_t*)mmu_map_mmio_region(0x08010000, 0x2000);\n\t}\n}\n\nvoid gic_send_sgi(uint8_t intid, int target) {\n\tuint64_t tlf = ((target == -1) ? 1UL : 0UL) << 24;\n\tuint64_t sii = intid & 0xF;\n\tuint64_t ctl = ((target == -1) ? 0UL : (1UL << target)) << 16;\n\n\tgic_regs[0x3c0] = (tlf|sii|ctl);\n}\n\nvoid gic_assign_interrupt(int irq, int (*callback)(process_t*,int,void*), void * data) {\n\tspin_lock(irq_acquire);\n\tstruct irq_callback * cb = calloc(sizeof(struct irq_callback),1);\n\n\tcb->callback = callback;\n\tcb->owner = (process_t*)this_core->current_process;\n\tcb->data = data;\n\tcb->next = NULL;\n\n\tif (irq_callbacks[irq]) {\n\t\tstruct irq_callback * parent = irq_callbacks[irq];\n\t\twhile (parent->next) {\n\t\t\tparent = parent->next;\n\t\t}\n\t\tparent->next = cb;\n\t} else {\n\t\tirq_callbacks[irq] = cb;\n\t}\n\n\tspin_unlock(irq_acquire);\n}\n\nvoid gic_map_pci_interrupt(const char * name, uint32_t device, int * int_out, int (*callback)(process_t*,int,void*), void * isr_addr) {\n\tuint32_t phys_hi = (pci_extract_bus(device) << 16) | (pci_extract_slot(device) << 11);\n\tuint32_t pin = pci_read_field(device, PCI_INTERRUPT_PIN, 1);\n\t#if 0\n\tdprintf(\"%s: device %#x, slot = %d (0x%04x), irq pin = %d\\n\", name, device, pci_extract_slot(device),\n\t\tphys_hi, pin);\n\t#endif\n\n\tuint32_t * pcie_dtb = dtb_find_node_prefix(\"pcie@\");\n\tif (!pcie_dtb) {\n\t\tdprintf(\"%s: can't find dtb entry\\n\", name);\n\t\treturn;\n\t}\n\n\tuint32_t * intMask = dtb_node_find_property(pcie_dtb, \"interrupt-map-mask\");\n\tif (!intMask) {\n\t\tdprintf(\"%s: can't find property 'interrupt-map-mask'\\n\", name);\n\t\treturn;\n\t}\n\n\tuint32_t * intMap = dtb_node_find_property(pcie_dtb, \"interrupt-map\");\n\n\tif (!intMap) {\n\t\tdprintf(\"%s: can't find property 'interrupt-map'\\n\", name);\n\t\treturn;\n\t}\n\n\tfor (int i = 0; i < (int)swizzle(intMap[0])/4; i += 10) {\n\t\tif (swizzle(intMap[i+2]) == (swizzle(intMask[2]) & phys_hi)) {\n\t\t\tif (swizzle(intMap[i+5]) == (swizzle(intMask[5]) & pin)) {\n\t\t\t\t#if 0\n\t\t\t\tdprintf(\"%s: %#x %#x %#x %#x\\n\", name,\n\t\t\t\t\tswizzle(intMap[i+2]), swizzle(intMap[i+3]), swizzle(intMap[i+4]), swizzle(intMap[i+5]));\n\t\t\t\tdprintf(\"%s: Matching device and pin, Interrupt maps to %d\\n\", name, swizzle(intMap[i+10]));\n\t\t\t\t#endif\n\t\t\t\t*int_out = swizzle(intMap[i+10]);\n\t\t\t\tgic_assign_interrupt(*int_out, callback, isr_addr);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "kernel/arch/aarch64/irq.S",
    "content": "/**\n * @file kernel/arch/aarch64/irq.S\n * @brief Exception entry points.\n *\n * These are still very hacky and rudimentary and we're\n * probably saving way more context than we actually need.\n */\nsync_common:\n    /* Push all regular registers */\n    stp x0, x1, [sp, #-16]!\n    stp x2, x3, [sp, #-16]!\n    stp x4, x5, [sp, #-16]!\n    stp x6, x7, [sp, #-16]!\n    stp x8, x9, [sp, #-16]!\n    stp x10, x11, [sp, #-16]!\n    stp x12, x13, [sp, #-16]!\n    stp x14, x15, [sp, #-16]!\n    stp x16, x17, [sp, #-16]!\n    stp x18, x19, [sp, #-16]!\n    stp x20, x21, [sp, #-16]!\n    stp x22, x23, [sp, #-16]!\n    stp x24, x25, [sp, #-16]!\n    stp x26, x27, [sp, #-16]!\n    stp x28, x29, [sp, #-16]!\n    /* combine x30 (link register) with stack */\n    mrs x0, SP_EL0\n    stp x30, x0, [sp, #-16]!\n    /* Pass the current stack as the first arg (struct regs *r) */\n    mov x0, sp\n    /* Make sure x18 has our kernel CPU-local pointer */\n    mrs x18, TPIDR_EL1\n    .extern aarch64_sync_enter\n    bl aarch64_sync_enter\n\n    mrs x0, SPSR_EL1\n    and w0, w0, #0x200000\n    cbz w0, _sync_cont\n    mrs x0, MDSCR_EL1\n    orr x0, x0, #1\n    msr MDSCR_EL1, x0\n    _sync_cont:\n\n    ldp x30, x0, [sp], #16\n    msr SP_EL0, x0\n    ldp x28, x29, [sp], #16\n    ldp x26, x27, [sp], #16\n    ldp x24, x25, [sp], #16\n    ldp x22, x23, [sp], #16\n    ldp x20, x21, [sp], #16\n    ldp x18, x19, [sp], #16\n    ldp x16, x17, [sp], #16\n    ldp x14, x15, [sp], #16\n    ldp x12, x13, [sp], #16\n    ldp x10, x11, [sp], #16\n    ldp x8, x9, [sp], #16\n    ldp x6, x7, [sp], #16\n    ldp x4, x5, [sp], #16\n    ldp x2, x3, [sp], #16\n    ldp x0, x1, [sp], #16\n    eret\n\nfault_common:\n    stp x0, x1, [sp, #-16]!\n    stp x2, x3, [sp, #-16]!\n    stp x4, x5, [sp, #-16]!\n    stp x6, x7, [sp, #-16]!\n    stp x8, x9, [sp, #-16]!\n    stp x10, x11, [sp, #-16]!\n    stp x12, x13, [sp, #-16]!\n    stp x14, x15, [sp, #-16]!\n    stp x16, x17, [sp, #-16]!\n    stp x18, x19, [sp, #-16]!\n    stp x20, x21, [sp, #-16]!\n    stp x22, x23, [sp, #-16]!\n    stp x24, x25, [sp, #-16]!\n    stp x26, x27, [sp, #-16]!\n    stp x28, x29, [sp, #-16]!\n    mrs x0, SP_EL0\n    stp x30, x0, [sp, #-16]!\n    mov x0, sp\n    mrs x18, TPIDR_EL1\n    .extern aarch64_fault_enter\n    bl aarch64_fault_enter\n    ldp x30, x0, [sp], #16\n    msr SP_EL0, x0\n    ldp x28, x29, [sp], #16\n    ldp x26, x27, [sp], #16\n    ldp x24, x25, [sp], #16\n    ldp x22, x23, [sp], #16\n    ldp x20, x21, [sp], #16\n    ldp x18, x19, [sp], #16\n    ldp x16, x17, [sp], #16\n    ldp x14, x15, [sp], #16\n    ldp x12, x13, [sp], #16\n    ldp x10, x11, [sp], #16\n    ldp x8, x9, [sp], #16\n    ldp x6, x7, [sp], #16\n    ldp x4, x5, [sp], #16\n    ldp x2, x3, [sp], #16\n    ldp x0, x1, [sp], #16\n    eret\n\nirq_common:\n    stp x0, x1, [sp, #-16]!\n    stp x2, x3, [sp, #-16]!\n    stp x4, x5, [sp, #-16]!\n    stp x6, x7, [sp, #-16]!\n    stp x8, x9, [sp, #-16]!\n    stp x10, x11, [sp, #-16]!\n    stp x12, x13, [sp, #-16]!\n    stp x14, x15, [sp, #-16]!\n    stp x16, x17, [sp, #-16]!\n    stp x18, x19, [sp, #-16]!\n    stp x20, x21, [sp, #-16]!\n    stp x22, x23, [sp, #-16]!\n    stp x24, x25, [sp, #-16]!\n    stp x26, x27, [sp, #-16]!\n    stp x28, x29, [sp, #-16]!\n    mrs x0, SP_EL0\n    stp x30, x0, [sp, #-16]!\n    mov x0, sp\n    mrs x18, TPIDR_EL1\n    .extern aarch64_irq_enter\n    bl aarch64_irq_enter\n    mrs x0, SPSR_EL1\n    and w0, w0, #0x200000\n    cbz w0, _irq_cont\n    mrs x0, MDSCR_EL1\n    orr x0, x0, #1\n    msr MDSCR_EL1, x0\n    _irq_cont:\n    ldp x30, x0, [sp], #16\n    msr SP_EL0, x0\n    ldp x28, x29, [sp], #16\n    ldp x26, x27, [sp], #16\n    ldp x24, x25, [sp], #16\n    ldp x22, x23, [sp], #16\n    ldp x20, x21, [sp], #16\n    ldp x18, x19, [sp], #16\n    ldp x16, x17, [sp], #16\n    ldp x14, x15, [sp], #16\n    ldp x12, x13, [sp], #16\n    ldp x10, x11, [sp], #16\n    ldp x8, x9, [sp], #16\n    ldp x6, x7, [sp], #16\n    ldp x4, x5, [sp], #16\n    ldp x2, x3, [sp], #16\n    ldp x0, x1, [sp], #16\n    eret\n\n.globl _exception_vector\n.balign 0x800\n_exception_vector:\n\n/* AArch64 exception vectors are divided up;\n * our first set of vectors is for if we are using SP0\n * in EL1, which we don't currently do so these should\n * all probably just be loop branches to catch this. */\n_exc_sp0_sync:\n    b .\n.balign 0x80\n_exc_sp0_irq:\n    b .\n.balign 0x80\n_exc_sp0_fiq:\n    b .\n.balign 0x80\n_exc_sp0_serror:\n    b .\n\n/* These are EL1-EL1 fault handlers, for when we encounter\n * an exception in the kernel. We normally have interrupts\n * masked in the kernel so we should only see synchronous\n * exceptions - faults. */\n.balign 0x80\n_exc_spx_sync:\n    b fault_common\n.balign 0x80\n_exc_spx_irq:\n    b .\n.balign 0x80\n_exc_spx_fiq:\n    b .\n.balign 0x80\n_exc_spx_serror:\n    b .\n\n/* These are EL0-EL1 transition handlers. Synchronous is going\n * to be faults and system calls (SVC requests); */\n.balign 0x80\n_exc_lower_sync:\n    b sync_common\n\n/* Actual interrupts */\n.balign 0x80\n_exc_lower_irq:\n    b irq_common\n\n/* \"Fast\" interrupts. TODO */\n.balign 0x80\n_exc_lower_fiq:\n    b .\n\n.balign 0x80\n_exc_lower_serror:\n    b .\n.balign 0x80\n\n/* These are going to be calls from 32-bit userspace, which we're\n * not going to support, so just blank all of these out as well. */\n_exc_lower_32_sync:\n    b .\n.balign 0x80\n_exc_lower_32_irq:\n    b .\n.balign 0x80\n_exc_lower_32_fiq:\n    b .\n.balign 0x80\n_exc_lower_32_serror:\n    b .\n"
  },
  {
    "path": "kernel/arch/aarch64/link.ld",
    "content": "OUTPUT_FORMAT(elf64-littleaarch64)\nENTRY(start)\n\nSECTIONS\n{\n\t. = 0xffffffff80000000;\n\tphys = .;\n\n\t.text BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.bootstrap)\n\t\tcode = .;\n\t\t*(.text)\n\t}\n\n\t.rodata BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.rodata)\n\t}\n\n\t.data BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tdata = .;\n\t\t*(.data)\n\t\t*(.symbols)\n\t\tPROVIDE(kernel_symbols_start = .);\n\t\tPROVIDE(kernel_symbols_end = .);\n\t\tPROVIDE(bss_start = .);\n\t}\n\n\t.bss BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tbss = .;\n\t\t*(COMMON)\n\t\t*(.bss)\n\t\t*(.stack)\n\t}\n\n\t/* Some built-in stack space... */\n\t. = ALIGN(0x1000);\n\t. = . + 0x4000;\n\t__bootstrap_stack_top = .;\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.eh_frame)\n\t\t*(.note.gnu.build-id)\n\t}\n\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/main.c",
    "content": "/**\n * @file  kernel/arch/aarch64/main.c\n * @brief Kernel C entry point and initialization for QEMU aarch64 'virt' machine.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/symboltable.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/version.h>\n#include <kernel/pci.h>\n#include <kernel/args.h>\n#include <kernel/ramdisk.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n#include <kernel/generic.h>\n#include <kernel/video.h>\n#include <kernel/signal.h>\n#include <kernel/misc.h>\n#include <kernel/ptrace.h>\n#include <kernel/ksym.h>\n#include <errno.h>\n\n#include <sys/ptrace.h>\n\n#include <kernel/arch/aarch64/regs.h>\n#include <kernel/arch/aarch64/dtb.h>\n#include <kernel/arch/aarch64/gic.h>\n#include <kernel/arch/aarch64/rpi.h>\n\nextern void fbterm_initialize(void);\nextern void mmu_init(size_t memsize, size_t phys, uintptr_t firstFreePage, uintptr_t endOfInitrd);\nextern void aarch64_regs(struct regs *r);\nextern void fwcfg_load_initrd(uintptr_t * ramdisk_phys_base, size_t * ramdisk_size);\nextern void fwcfg_device(void);\nextern void virtio_input(void);\nextern void aarch64_smp_start(void);\n\nextern char end[];\nextern char * _arch_args;\n\n/* ARM says the system clock tick rate is generally in\n * the range of 1-50MHz. Since we throw around integer\n * MHz ratings that's not great, so let's give it a few\n * more digits for long-term accuracy? */\nuint64_t sys_timer_freq = 0;\nuint64_t arch_boot_time = 0; /**< No idea where we're going to source this from, need an RTC. */\nuint64_t basis_time = 0;\n#define SUBSECONDS_PER_SECOND 1000000\n\n/**\n * TODO can this be marked 'inline'?\n *\n * Read the system timer timestamp.\n */\nuint64_t arch_perf_timer(void) {\n\tuint64_t val;\n\tasm volatile (\"mrs %0,CNTPCT_EL0\" : \"=r\"(val));\n\treturn val * 100;\n}\n\n/**\n * @warning This function is incorrectly named.\n * @brief Get the frequency of the perf timer.\n *\n * This is not the CPU frequency. We do present it as such for x86-64,\n * and I think for our TSC timing that is generally true, but not here.\n */\nsize_t arch_cpu_mhz(void) {\n\treturn sys_timer_freq;\n}\n\n/**\n * @brief Figure out the rate of the system timer and get boot time from RTC.\n *\n * We use the system timer as our performance tracker, as it operates at few\n * megahertz at worst which is good enough for us. We do want slightly bigger\n * numbers to make our integer divisions more accurate...\n */\nstatic void arch_clock_initialize(uintptr_t rpi_tag) {\n\n\t/* Get frequency of system timer */\n\tuint64_t val;\n\tasm volatile (\"mrs %0,CNTFRQ_EL0\" : \"=r\"(val));\n\tsys_timer_freq = val / 10000;\n\n\t/* Get boot time from RTC */\n\tif (rpi_tag) {\n\t\tarch_boot_time = 1644908027UL;\n\t} else {\n\t\t/* QEMU RTC */\n\t\tvoid * clock_addr = mmu_map_from_physical(0x09010000);\n\t\tarch_boot_time = *(volatile uint32_t*)clock_addr;\n\t}\n\n\t/* Get the \"basis time\" - the perf timestamp we got the wallclock time at */\n\tbasis_time = arch_perf_timer() / sys_timer_freq;\n\n\t/* Report the reference clock speed */\n\tdprintf(\"timer: Using %ld MHz as arch_perf_timer frequency.\\n\", arch_cpu_mhz());\n}\n\nstatic void update_ticks(uint64_t ticks, uint64_t *timer_ticks, uint64_t *timer_subticks) {\n\t*timer_subticks = ticks - basis_time;\n\t*timer_ticks = *timer_subticks / SUBSECONDS_PER_SECOND;\n\t*timer_subticks = *timer_subticks % SUBSECONDS_PER_SECOND;\n}\n\nint gettimeofday(struct timeval * t, void *z) {\n\tuint64_t tsc = arch_perf_timer();\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(tsc / sys_timer_freq, &timer_ticks, &timer_subticks);\n\tt->tv_sec = arch_boot_time + timer_ticks;\n\tt->tv_usec = timer_subticks;\n\treturn 0;\n}\n\nuint64_t now(void) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\treturn t.tv_sec;\n}\n\nstatic spin_lock_t _time_set_lock;\n\nint settimeofday(struct timeval * t, void *z) {\n\tif (!t) return -EINVAL;\n\tif (t->tv_sec < 0 || t->tv_usec < 0 || t->tv_usec > 1000000) return -EINVAL;\n\n\tspin_lock(_time_set_lock);\n\tuint64_t clock_time = now();\n\tarch_boot_time += t->tv_sec - clock_time;\n\tspin_unlock(_time_set_lock);\n\n\treturn 0;\n}\n\nvoid relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) {\n\tif (!arch_boot_time) {\n\t\t*out_seconds = 0;\n\t\t*out_subseconds = 0;\n\t\treturn;\n\t}\n\n\tuint64_t tsc = arch_perf_timer();\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(tsc / sys_timer_freq, &timer_ticks, &timer_subticks);\n\tif (subseconds + timer_subticks >= SUBSECONDS_PER_SECOND) {\n\t\t*out_seconds    = timer_ticks + seconds + (subseconds + timer_subticks) / SUBSECONDS_PER_SECOND;\n\t\t*out_subseconds = (subseconds + timer_subticks) % SUBSECONDS_PER_SECOND;\n\t} else {\n\t\t*out_seconds    = timer_ticks + seconds;\n\t\t*out_subseconds = timer_subticks + subseconds;\n\t}\n}\n\n#define TIMER_IRQ 27\nstatic void set_tick(void) {\n\tasm volatile (\n\t\t\"mrs x0, CNTFRQ_EL0\\n\"\n\t\t\"mov x1, 100\\n\" // without this, one second\n\t\t\"udiv x0, x0, x1\\n\"\n\t\t\"msr CNTV_TVAL_EL0, x0\\n\"\n\t\t\"mov x0, 1\\n\"\n\t\t\"msr CNTV_CTL_EL0, x0\\n\"\n\t\t:::\"x0\",\"x1\");\n}\n\nvoid timer_start(void) {\n\t/* mask irqs */\n\tasm volatile (\"msr DAIFSet, #0b1111\");\n\n\t/* Enable the local timer */\n\tset_tick();\n\n\t/* This is global, we only need to do this once... */\n\tgic_regs[0] = 1;\n\n\t/* This is specific to this CPU */\n\tgicc_regs[0] = 1;\n\tgicc_regs[1] = 0x1ff;\n\n\t/* Timer interrupts are private peripherals, so each CPU gets one */\n\tgic_regs[64] = 0xFFFFffff; //(1 << TIMER_IRQ);\n\tgic_regs[160] = 0xFFFFffff; //(1 << TIMER_IRQ);\n\n\t/* These are shared? */\n\tgic_regs[65]  = 0xFFFFFFFF;\n\tgic_regs[66]  = 0xFFFFFFFF;\n\tgic_regs[67]  = 0xFFFFFFFF;\n\n\tgic_regs[520] = 0x07070707;\n\tgic_regs[521] = 0x07070707;\n\tgic_regs[543] = 0x07070707;\n}\n\nstatic volatile uint64_t time_slice_basis = 0; /**< When the last clock update happened */\nstatic spin_lock_t ticker_lock;\nstatic void update_clock(void) {\n\tuint64_t clock_ticks = arch_perf_timer() / sys_timer_freq;\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(clock_ticks, &timer_ticks, &timer_subticks);\n\n\tspin_lock(ticker_lock);\n\tif (time_slice_basis + SUBSECONDS_PER_SECOND/4 <= clock_ticks) {\n\t\tupdate_process_usage(clock_ticks - time_slice_basis, sys_timer_freq);\n\t\ttime_slice_basis = clock_ticks;\n\t}\n\tspin_unlock(ticker_lock);\n\n\twakeup_sleepers(timer_ticks, timer_subticks);\n}\n\nstatic volatile unsigned int * _log_device_addr = 0;\nstatic size_t _early_log_write(size_t size, uint8_t * buffer) {\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\t*_log_device_addr = buffer[i];\n\t}\n\treturn size;\n}\n\nstatic void early_log_initialize(void) {\n\t/* QEMU UART */\n\t_log_device_addr = mmu_map_from_physical(0x09000000);\n\tprintf_output = &_early_log_write;\n}\n\nvoid arch_set_core_base(uintptr_t base) {\n\t/* It doesn't actually seem that this register has\n\t * any real meaning, it's just available for us\n\t * to load with our thread pointer. It's possible\n\t * that the 'mrs' for it is just as fast as regular\n\t * register reference? */\n\tasm volatile (\"msr TPIDR_EL1,%0\" : : \"r\"(base));\n\n\t/* this_cpu pointer, which we can tell gcc is reserved\n\t * by our ABI and then bind as a 'register' variable. */\n\tasm volatile (\"mrs x18, TPIDR_EL1\");\n}\n\nvoid arch_set_tls_base(uintptr_t tlsbase) {\n\tasm volatile (\"msr TPIDR_EL0,%0\" : : \"r\"(tlsbase));\n}\n\nvoid arch_set_kernel_stack(uintptr_t stack) {\n\t/* This is currently unused... it seems we're handling\n\t * things correctly and getting the right stack already,\n\t * but XXX should look into this later. */\n\tthis_core->sp_el1 = stack;\n}\n\nstatic void exception_handlers(void) {\n\textern char _exception_vector[];\n\n\tasm volatile(\"msr VBAR_EL1, %0\" :: \"r\"(&_exception_vector));\n}\n\nvoid aarch64_sync_enter(struct regs * r) {\n\tuint64_t esr, far, elr, spsr;\n\tasm volatile (\"mrs %0, ESR_EL1\" : \"=r\"(esr));\n\tasm volatile (\"mrs %0, FAR_EL1\" : \"=r\"(far));\n\tasm volatile (\"mrs %0, ELR_EL1\" : \"=r\"(elr));\n\tasm volatile (\"mrs %0, SPSR_EL1\" : \"=r\"(spsr));\n\n\t#if 0\n\tdprintf(\"EL0-EL1 sync: %d (%s) ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\\n\",\n\t\tthis_core ? (this_core->current_process ? this_core->current_process->id : -1) : -1,\n\t\tthis_core ? (this_core->current_process ? this_core->current_process->name : \"?\") : \"?\",\n\t\tesr, far, elr, spsr);\n\t#endif\n\n\tif (esr == 0x2000000) {\n\t\tarch_fatal_prepare();\n\t\tdprintf(\"Unknown exception: ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\\n\", esr, far, elr, spsr);\n\t\tdprintf(\"Instruction at ELR: 0x%08x\\n\", *(uint32_t*)elr);\n\t\tarch_dump_traceback();\n\t\taarch64_regs(r);\n\t\tarch_fatal();\n\t}\n\n\tif (this_core->current_process) {\n\t\tthis_core->current_process->time_switch = arch_perf_timer();\n\t}\n\n\tif ((esr >> 26) == 0x32) {\n\t\t/* Single step trap */\n\t\tuint64_t val;\n\t\tasm volatile(\"mrs %0, MDSCR_EL1\" : \"=r\"(val));\n\t\tval &= ~(1 << 0);\n\t\tasm volatile(\"msr MDSCR_EL1, %0\" :: \"r\"(val));\n\n\t\tif (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) {\n\t\t\tptrace_signal(SIGTRAP, PTRACE_EVENT_SINGLESTEP);\n\t\t}\n\n\t\tgoto _resume_user;\n\t}\n\n\t/* Magic signal return */\n\tif (elr == 0x516 && far == 0x516) {\n\t\treturn_from_signal_handler(r);\n\t\tgoto _resume_user;\n\t}\n\n\t/* System call */\n\tif ((esr >> 26) == 0x15) {\n\t\t//dprintf(\"pid %d syscall %zd elr=%#zx\\n\",\n\t\t//\tthis_core->current_process->id, r->x0, elr);\n\t\textern void syscall_handler(struct regs *);\n\t\tsyscall_handler(r);\n\t\tgoto _resume_user;\n\t}\n\n\t/* KVM is mad at us; usually means our code is broken or we neglected a cache. */\n\tif (far == 0x1de7ec7edbadc0de) {\n\t\tprintf(\"kvm: blip (esr=%#zx, elr=%#zx; pid=%d [%s])\\n\", esr, elr, this_core->current_process->id, this_core->current_process->name);\n\t\tgoto _resume_user;\n\t}\n\n\t/* Unexpected fault, eg. page fault. */\n\tdprintf(\"In process %d (%s)\\n\", this_core->current_process->id, this_core->current_process->name);\n\tdprintf(\"ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\\n\", esr, far, elr, spsr);\n\taarch64_regs(r);\n\tuint64_t tpidr_el0;\n\tasm volatile (\"mrs %0, TPIDR_EL0\" : \"=r\"(tpidr_el0));\n\tdprintf(\"  TPIDR_EL0=%#zx\\n\", tpidr_el0);\n\n\tsend_signal(this_core->current_process->id, SIGSEGV, 1);\n\n_resume_user:\n\tprocess_check_signals(r);\n\tupdate_process_times_on_exit();\n}\n\nstatic void spin(void) {\n\twhile (1) {\n\t\tasm volatile (\"wfi\");\n\t}\n}\n\n#define EOI(x) do { \\\n\tgicc_regs[4] = (x); \\\n} while (0)\nvoid aarch64_interrupt_dispatch(int from_wfi) {\n\tuint32_t iar = gicc_regs[3];\n\tuint32_t irq = iar & 0x3FF;\n\t/* Currently we aren't using the CPU value and I'm not sure we have any use for it, we know who we are? */\n\t//uint32_t cpu = (iar >> 10) & 0x7;\n\n\tswitch (irq) {\n\t\tcase TIMER_IRQ:\n\t\t\tupdate_clock();\n\t\t\tset_tick();\n\t\t\tEOI(iar);\n\t\t\tif (from_wfi) {\n\t\t\t\tswitch_next();\n\t\t\t} else {\n\t\t\t\tswitch_task(1);\n\t\t\t}\n\t\t\treturn;\n\n\t\tcase 1:\n\t\t\tEOI(iar);\n\t\t\tif (from_wfi) switch_next();\n\t\t\tbreak;\n\n\t\t/* Arbitrarily chosen SGI for panic signal from another core */\n\t\tcase 2:\n\t\t\tspin();\n\t\t\tbreak;\n\n\t\tcase 1022:\n\t\tcase 1023:\n\t\t\treturn;\n\n\t\tdefault:\n\t\t\tif (irq >= 32 && irq < 1022) {\n\t\t\t\tstruct irq_callback * cb = irq_callbacks[irq-32];\n\t\t\t\tif (cb) {\n\t\t\t\t\twhile (cb) {\n\t\t\t\t\t\tint res = cb->callback(cb->owner, irq-32, cb->data);\n\t\t\t\t\t\tif (res) break;\n\t\t\t\t\t\tcb = cb->next;\n\t\t\t\t\t}\n\t\t\t\t\t/* Maybe warn? We have a lot of spurious irqs, though */\n\t\t\t\t} else {\n\t\t\t\t\tdprintf(\"irq: unhandled irq %d\\n\", irq);\n\t\t\t\t}\n\t\t\t\tEOI(iar);\n\t\t\t} else {\n\t\t\t\tdprintf(\"gic: Unhandled interrupt: %d\\n\", irq);\n\t\t\t\tEOI(iar);\n\t\t\t}\n\t\t\treturn;\n\t}\n}\n\nvoid aarch64_irq_enter(struct regs * r) {\n\tif (this_core->current_process) {\n\t\tthis_core->current_process->time_switch = arch_perf_timer();\n\t}\n\n\taarch64_interrupt_dispatch(0);\n\n\tprocess_check_signals(r);\n}\n\n/**\n * @brief Kernel fault handler.\n */\nvoid aarch64_fault_enter(struct regs * r) {\n\tuint64_t esr, far, elr, spsr;\n\tasm volatile (\"mrs %0, ESR_EL1\" : \"=r\"(esr));\n\tasm volatile (\"mrs %0, FAR_EL1\" : \"=r\"(far));\n\tasm volatile (\"mrs %0, ELR_EL1\" : \"=r\"(elr));\n\tasm volatile (\"mrs %0, SPSR_EL1\" : \"=r\"(spsr));\n\n\tarch_fatal_prepare();\n\n\tdprintf(\"EL1-EL1 fault handler, core %d\\n\", this_core->cpu_id);\n\tif (this_core && this_core->current_process) {\n\t\tdprintf(\"In process %d (%s)\\n\", this_core->current_process->id, this_core->current_process->name);\n\t}\n\tdprintf(\"ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\\n\", esr, far, elr, spsr);\n\taarch64_regs(r);\n\n\tuint64_t tpidr_el0;\n\tasm volatile (\"mrs %0, TPIDR_EL0\" : \"=r\"(tpidr_el0));\n\tdprintf(\"  TPIDR_EL0=%#zx\\n\", tpidr_el0);\n\n\textern void aarch64_safe_dump_traceback(uintptr_t elr, struct regs * r);\n\taarch64_safe_dump_traceback(elr, r);\n\n\tarch_fatal();\n}\n\nvoid aarch64_sp0_fault_enter(struct regs * r) {\n\tarch_fatal_prepare();\n\tdprintf(\"EL1-EL1 sp0 entry?\\n\");\n\tarch_fatal();\n}\n\n/**\n * @brief Enable FPU and NEON (SIMD)\n *\n * This enables the FPU in EL0. I'm not sure if we can enable it\n * there but not in EL1... that would be nice to avoid accidentally\n * introducing FPU code in the kernel that would corrupt our FPU state.\n */\nvoid fpu_enable(void) {\n\tuint64_t cpacr_el1;\n\tasm volatile (\"mrs %0, CPACR_EL1\" : \"=r\"(cpacr_el1));\n\tcpacr_el1 |= (3 << 20) | (3 << 16);\n\tasm volatile (\"msr CPACR_EL1, %0\" :: \"r\"(cpacr_el1));\n\n\t/* Enable access to physical timer */\n\tuint64_t clken = 0;\n\tasm volatile (\"mrs %0,CNTKCTL_EL1\" : \"=r\"(clken));\n\tclken |= (1 << 0);\n\tasm volatile (\"msr CNTKCTL_EL1,%0\" :: \"r\"(clken));\n}\n\n/**\n * @brief Called in a loop by kernel idle tasks.\n */\nvoid arch_pause(void) {\n\n\t/* XXX This actually works even if we're masking interrupts, but\n\t * the interrupt function won't be called, so we'll need to change\n\t * it once we start getting actual hardware interrupts. */\n\tasm volatile (\"wfi\");\n\n\taarch64_interrupt_dispatch(1);\n}\n\n/**\n * @brief Force a cache clear across an address range.\n *\n * GCC has a __clear_cache() function that is supposed to do this\n * but I haven't figured out what bits I need to set in what system\n * register to allow that to be callable from EL0, so we actually expose\n * it as a new sysfunc system call for now. We'll be generous and quietly\n * skip regions that are not accessible to the calling process.\n *\n * This is critical for the dynamic linker to reset the icache for\n * regions that have been loaded from new libraries.\n */\nvoid arch_clear_icache(uintptr_t start, uintptr_t end) {\n\tfor (uintptr_t x = start; x < end; x += 64) {\n\t\tif (!mmu_validate_user_pointer((void*)x, 64, MMU_PTR_WRITE)) continue;\n\t\tasm volatile (\"dc cvau, %0\" :: \"r\"(x));\n\t}\n\tfor (uintptr_t x = start; x < end; x += 64) {\n\t\tif (!mmu_validate_user_pointer((void*)x, 64, MMU_PTR_WRITE)) continue;\n\t\tasm volatile (\"ic ivau, %0\" :: \"r\"(x));\n\t}\n}\n\nvoid aarch64_processor_data(void) {\n\tasm volatile (\"mrs %0, MIDR_EL1\" : \"=r\"(this_core->midr));\n}\n\nstatic void symbols_install(void) {\n\tksym_install();\n\tkernel_symbol_t * k = (kernel_symbol_t *)&kernel_symbols_start;\n\twhile ((uintptr_t)k < (uintptr_t)&kernel_symbols_end) {\n\t\tksym_bind(k->name, (void*)k->addr);\n\t\tk = (kernel_symbol_t *)((uintptr_t)k + sizeof *k + strlen(k->name) + 1);\n\t}\n}\n\n/**\n * Main kernel C entrypoint for qemu's -machine virt\n *\n * By this point, a 'bootstub' has already set up some\n * initial page tables so the linear physical mapping\n * is where we would normally expect it to be, we're\n * at -2GiB, and there's some other mappings so that\n * a bit of RAM is 1:1.\n */\nint kmain(uintptr_t dtb_base, uintptr_t phys_base, uintptr_t rpi_tag) {\n\n\textern uintptr_t aarch64_kernel_phys_base;\n\taarch64_kernel_phys_base = phys_base;\n\n\textern uintptr_t aarch64_dtb_phys;\n\taarch64_dtb_phys = dtb_base;\n\n\tif (rpi_tag) {\n\t\textern uint8_t * lfb_vid_memory;\n\t\textern uint16_t lfb_resolution_x;\n\t\textern uint16_t lfb_resolution_y;\n\t\textern uint16_t lfb_resolution_b;\n\t\textern uint32_t lfb_resolution_s;\n\t\textern size_t lfb_memsize;\n\n\t\tstruct rpitag * tag = (struct rpitag*)rpi_tag;\n\n\t\tlfb_vid_memory = mmu_map_from_physical(tag->phys_addr);\n\t\tlfb_resolution_x = tag->x;\n\t\tlfb_resolution_y = tag->y;\n\t\tlfb_resolution_s = tag->s;\n\t\tlfb_resolution_b = tag->b;\n\t\tlfb_memsize = tag->size;\n\n\t\tfbterm_initialize();\n\t} else {\n\t\t/* Uncomment to get serial log on startup; otherwise, you can set\n\t\t * 'qemu-serial-log' in the command line to get it later, which will\n\t\t * still dump the early log messages. */\n\t\t//early_log_initialize();\n\t}\n\n\tdprintf(\"%s %d.%d.%d-%s %s %s\\n\",\n\t\t__kernel_name,\n\t\t__kernel_version_major,\n\t\t__kernel_version_minor,\n\t\t__kernel_version_lower,\n\t\t__kernel_version_suffix,\n\t\t__kernel_version_codename,\n\t\t__kernel_arch);\n\n\tdprintf(\"boot: dtb @ %#zx kernel @ %#zx\\n\",\n\t\tdtb_base, phys_base);\n\n\t/* Initialize TPIDR_EL1 */\n\tarch_set_core_base((uintptr_t)&processor_local_data[0]);\n\n\t/* Set up the system timer and get an RTC time. */\n\tarch_clock_initialize(rpi_tag);\n\n\t/* Set up exception handlers early... */\n\texception_handlers();\n\n\t/* Load ramdisk over fw-cfg. */\n\tuintptr_t ramdisk_phys_base = 0;\n\tsize_t ramdisk_size = 0;\n\tif (rpi_tag) {\n\t\t/* XXX Should this whole set of things be a \"platform_init()\" thing, where we\n\t\t *     figure out the platform and just do the stuff? */\n\t\tstruct rpitag * tag = (struct rpitag*)rpi_tag;\n\t\trpi_load_ramdisk(tag, &ramdisk_phys_base, &ramdisk_size);\n\n\t\t/* TODO figure out memory size - mailbox commands */\n\t\tmmu_init(0, 512 * 1024 * 1024,\n\t\t\t0x80000,\n\t\t\t(uintptr_t)&end + ramdisk_size - 0xffffffff80000000UL);\n\n\t\tdprintf(\"rpi: mmu reinitialized\\n\");\n\n\t\trpi_set_cmdline(&_arch_args);\n\n\t} else {\n\t\t/*\n\t\t * TODO virt shim should load the ramdisk for us, so we can use the same code\n\t\t *      as we have for the RPi above and not have to use fwcfg to load it...\n\t\t */\n\t\tfwcfg_load_initrd(&ramdisk_phys_base, &ramdisk_size);\n\n\t\t/* Probe DTB for memory layout. */\n\t\tsize_t memaddr, memsize;\n\t\tdtb_memory_size(&memaddr, &memsize);\n\n\t\t/* Initialize the MMU based on the memory we got from dtb */\n\t\tmmu_init(\n\t\t\tmemaddr, memsize,\n\t\t\t0x40100000 /* Should be end of DTB, but we're really just guessing */,\n\t\t\t(uintptr_t)&end + ramdisk_size - 0xffffffff80000000UL);\n\n\t\t/* Find the cmdline */\n\t\tdtb_locate_cmdline(&_arch_args);\n\n\t\tif (args_present(\"qemu-serial-log\")) {\n\t\t\tearly_log_initialize();\n\t\t}\n\n\t\t/* Check for PCIe address? */\n\t\tdtb_pcie_base();\n\t}\n\n\tgic_map_regs(rpi_tag);\n\n\t/* Set up all the other arch-specific stuff here */\n\tfpu_enable();\n\n\tsymbols_install();\n\n\tgeneric_startup();\n\n\t/* Initialize the framebuffer and fbterm here */\n\tframebuffer_initialize();\n\n\tif (!rpi_tag) {\n\t\tfbterm_initialize();\n\t}\n\n\t/* Ramdisk */\n\tramdisk_mount(ramdisk_phys_base, ramdisk_size);\n\n\textern void dtb_device(void);\n\tdtb_device();\n\n\t/* Load MIDR */\n\taarch64_processor_data();\n\n\t/* Set up the system virtual timer to produce interrupts for userspace scheduling */\n\ttimer_start();\n\n\t/* Start other cores here */\n\tif (!rpi_tag ){\n\t\taarch64_smp_start();\n\n\t\t/* Install drivers that may need to sleep here */\n\t\tvirtio_input();\n\n\t\t/* QEMU fwcfg interface */\n\t\tfwcfg_device();\n\n\t\t/* Set up serial input */\n\t\textern void pl011_start(void);\n\t\tpl011_start();\n\t} else {\n\n\t\textern void rpi_smp_init(void);\n\t\trpi_smp_init();\n\n\t\textern void null_input(void);\n\t\tnull_input();\n\n\t\textern void miniuart_start(void);\n\t\tminiuart_start();\n\t}\n\n\tgeneric_main();\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "kernel/arch/aarch64/mmu.c",
    "content": "/**\n * @file  kernel/arch/aarch64/mmu.c\n * @brief Nearly identical to the x86-64 implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/assert.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/spinlock.h>\n#include <kernel/misc.h>\n#include <kernel/mmu.h>\n\nstatic volatile uint32_t *frames;\nstatic size_t nframes;\nstatic size_t total_memory = 0;\nstatic size_t unavailable_memory = 0;\nstatic uint64_t ram_starts_at = 0;\n\nuintptr_t aarch64_kernel_phys_base = 0;\n\n/* TODO Used for CoW later. */\n//static uint8_t * mem_refcounts = NULL;\n\n#define PAGE_SHIFT     12\n#define PAGE_SIZE      0x1000UL\n#define PAGE_SIZE_MASK 0xFFFFffffFFFFf000UL\n#define PAGE_LOW_MASK  0x0000000000000FFFUL\n\n#define LARGE_PAGE_SIZE 0x200000UL\n\n#define PHYS_MASK 0x7fffffffffUL\n#define CANONICAL_MASK 0xFFFFffffFFFFUL\n\n#define INDEX_FROM_BIT(b)  ((b) >> 5)\n#define OFFSET_FROM_BIT(b) ((b) & 0x1F)\n\n#define _pagemap __attribute__((aligned(PAGE_SIZE))) = {0}\nunion PML init_page_region[512] _pagemap;\nunion PML high_base_pml[512] _pagemap;\nunion PML heap_base_pml[512] _pagemap;\nunion PML heap_base_pd[512] _pagemap;\nunion PML heap_base_pt[512*3] _pagemap;\nunion PML kbase_pmls[65][512] _pagemap;\n\n#define PTE_VALID      (1UL << 0)\n#define PTE_TABLE      (1UL << 1)\n\n/* Table attributes */\n#define PTE_NSTABLE    (1UL << 63)\n#define PTE_APTABLE    (3UL << 61) /* two bits */\n#define  PTE_APTABLE_A (1UL << 62)\n#define  PTE_APTABLE_B (1UL << 61)\n#define PTE_UXNTABLE   (1UL << 60)\n#define PTE_PXNTABLE   (1UL << 59)\n\n/* Block attributes */\n#define PTE_UXN        (1UL << 54)\n#define PTE_PXN        (1UL << 53)\n#define PTE_CONTIGUOUS (1UL << 52)\n#define PTE_NG         (1UL << 11)\n#define PTE_AF         (1UL << 10)\n#define PTE_SH         (3UL << 8)  /* two bits */\n#define  PTE_SH_A      (1UL << 9)\n#define  PTE_SH_B      (1UL << 8)\n#define PTE_AP         (3UL << 6)  /* two bits */\n#define  PTE_AP_A      (1UL << 7)\n#define  PTE_AP_B      (1UL << 6)\n#define PTE_NS         (1UL << 5)\n#define PTE_ATTRINDX   (7UL << 2) /* three bits */\n#define  PTE_ATTR_A    (1UL << 4)\n#define  PTE_ATTR_B    (1UL << 3)\n#define  PTE_ATTR_C    (1UL << 2)\n\nvoid mmu_frame_set(uintptr_t frame_addr) {\n\tif (frame_addr < ram_starts_at) return;\n\tframe_addr -= ram_starts_at;\n\tif (frame_addr < nframes * PAGE_SIZE) {\n\t\tuint64_t frame  = frame_addr >> 12;\n\t\tuint64_t index  = INDEX_FROM_BIT(frame);\n\t\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\t\t__sync_or_and_fetch(&frames[index], ((uint32_t)1 << offset));\n\t\tasm (\"isb\" ::: \"memory\");\n\t}\n}\n\nstatic uintptr_t lowest_available = 0;\n\nvoid mmu_frame_clear(uintptr_t frame_addr) {\n\tif (frame_addr < ram_starts_at) return;\n\tframe_addr -= ram_starts_at;\n\tif (frame_addr < nframes * PAGE_SIZE) {\n\t\tuint64_t frame  = frame_addr >> PAGE_SHIFT;\n\t\tuint64_t index  = INDEX_FROM_BIT(frame);\n\t\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\t\t__sync_and_and_fetch(&frames[index], ~((uint32_t)1 << offset));\n\t\tasm (\"isb\" ::: \"memory\");\n\t\tif (frame < lowest_available) lowest_available = frame;\n\t}\n}\n\nint mmu_frame_test(uintptr_t frame_addr) {\n\tif (frame_addr < ram_starts_at) return 1;\n\tframe_addr -= ram_starts_at;\n\tif (!(frame_addr < nframes * PAGE_SIZE)) return 1;\n\tuint64_t frame  = frame_addr >> PAGE_SHIFT;\n\tuint64_t index  = INDEX_FROM_BIT(frame);\n\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\tasm (\"\" ::: \"memory\");\n\treturn !!(frames[index] & ((uint32_t)1 << offset));\n}\n\nstatic spin_lock_t frame_alloc_lock = { 0 };\nstatic spin_lock_t kheap_lock = { 0 };\nstatic spin_lock_t mmio_space_lock = { 0 };\nstatic spin_lock_t module_space_lock = { 0 };\n\nvoid mmu_frame_release(uintptr_t frame_addr) {\n\tspin_lock(frame_alloc_lock);\n\tmmu_frame_clear(frame_addr);\n\tspin_unlock(frame_alloc_lock);\n}\n\nuintptr_t mmu_first_n_frames(int n) {\n\tfor (uint64_t i = 0; i < nframes * PAGE_SIZE; i += PAGE_SIZE) {\n\t\tint bad = 0;\n\t\tfor (int j = 0; j < n; ++j) {\n\t\t\tif (mmu_frame_test(i + ram_starts_at + PAGE_SIZE * j)) {\n\t\t\t\tbad = j + 1;\n\t\t\t}\n\t\t}\n\t\tif (!bad) {\n\t\t\treturn (i + ram_starts_at) / PAGE_SIZE;\n\t\t}\n\t}\n\n\tarch_fatal_prepare();\n\tdprintf(\"Failed to allocate %d contiguous frames.\\n\", n);\n\tarch_dump_traceback();\n\tarch_fatal();\n\treturn (uintptr_t)-1;\n}\n\nuintptr_t mmu_first_frame(void) {\n\tuintptr_t i, j;\n\tfor (i = INDEX_FROM_BIT(lowest_available); i < INDEX_FROM_BIT(nframes); ++i) {\n\t\tif (frames[i] != (uint32_t)-1) {\n\t\t\tfor (j = 0; j < (sizeof(uint32_t)*8); ++j) {\n\t\t\t\tuint32_t testFrame = (uint32_t)1 << j;\n\t\t\t\tif (!(frames[i] & testFrame)) {\n\t\t\t\t\tuintptr_t out = (i << 5) + j;\n\t\t\t\t\tlowest_available = out + 1;\n\t\t\t\t\treturn out + (ram_starts_at >> 12);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (lowest_available != 0) {\n\t\tlowest_available = 0;\n\t\treturn mmu_first_frame();\n\t}\n\n\tarch_fatal_prepare();\n\tdprintf(\"Out of memory.\\n\");\n\tarch_dump_traceback();\n\tarch_fatal();\n\treturn (uintptr_t)-1;\n}\n\nvoid mmu_frame_allocate(union PML * page, unsigned int flags) {\n\t/* If page is not set... */\n\tif (page->bits.page == 0) {\n\t\tspin_lock(frame_alloc_lock);\n\t\tuintptr_t index = mmu_first_frame();\n\t\tmmu_frame_set(index << PAGE_SHIFT);\n\t\tpage->bits.page     = index;\n\t\tspin_unlock(frame_alloc_lock);\n\t}\n\n\tpage->bits.table_page = 1;\n\tpage->bits.present    = 1;\n\n\tpage->bits.ap = (!(flags & MMU_FLAG_WRITABLE) ? 2 : 0) | (!(flags & MMU_FLAG_KERNEL) ? 1 : 0);\n\tpage->bits.af = 1;\n\tpage->bits.sh = 2;\n\tpage->bits.attrindx = ((flags & MMU_FLAG_NOCACHE) | (flags & MMU_FLAG_WRITETHROUGH)) ? 0 : 1;\n\n\tif (!(flags & MMU_FLAG_KERNEL)) {\n\t\tpage->bits.attrindx = 1;\n\n\t\tif ((flags & MMU_FLAG_WC) == MMU_FLAG_WC) {\n\t\t\tpage->bits.attrindx = 2;\n\t\t}\n\t}\n\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\n\t#if 0\n\tpage->bits.writable = (flags & MMU_FLAG_WRITABLE) ? 1 : 0;\n\tpage->bits.user     = (flags & MMU_FLAG_KERNEL)   ? 0 : 1;\n\tpage->bits.nocache  = (flags & MMU_FLAG_NOCACHE)  ? 1 : 0;\n\tpage->bits.writethrough  = (flags & MMU_FLAG_WRITETHROUGH)  ? 1 : 0;\n\tpage->bits.size     = (flags & MMU_FLAG_SPEC) ? 1 : 0;\n\tpage->bits.nx       = (flags & MMU_FLAG_NOEXECUTE) ? 1 : 0;\n\t#endif\n\n}\n\nvoid mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr) {\n\t/* frame set physAddr, set page in entry, call frame_allocate to set attribute bits */\n\tmmu_frame_set(physAddr);\n\tpage->bits.page = physAddr >> PAGE_SHIFT;\n\tmmu_frame_allocate(page, flags);\n}\n\nvoid * mmu_map_from_physical(uintptr_t frameaddress) {\n\treturn (void*)(frameaddress | HIGH_MAP_REGION);\n}\n\n#define PDP_MASK 0x3fffffffUL\n#define  PD_MASK 0x1fffffUL\n#define  PT_MASK PAGE_LOW_MASK\n#define ENTRY_MASK 0x1FF\nunion PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr) {\n\t//printf(\"mmu_get_page_other(%#zx, %#zx);\\n\", (uintptr_t)root, virtAddr);\n\t/* Walk it */\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\t/* Get the PML4 entry for this address */\n\tif (!root[pml4_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pdp[pdp_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tif (!pdp[pdp_entry].bits.table_page) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pd[pd_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tif (!pd[pd_entry].bits.table_page) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\treturn (union PML *)&pt[pt_entry];\n}\n\nuintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr) {\n\tif (!root) {\n\t\tif (virtAddr >= MODULE_BASE_START) {\n\t\t\treturn (virtAddr - MODULE_BASE_START) + aarch64_kernel_phys_base;\n\t\t} else if (virtAddr >= HIGH_MAP_REGION) {\n\t\t\treturn (virtAddr - HIGH_MAP_REGION);\n\t\t}\n\t\treturn (uintptr_t)virtAddr;\n\t}\n\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\tif (!root[pml4_entry].bits.present) return (uintptr_t)-1;\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pdp[pdp_entry].bits.present) return (uintptr_t)-2;\n\tif (!pdp[pdp_entry].bits.table_page) return ((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT) | (virtAddr & PDP_MASK);\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pd[pd_entry].bits.present) return (uintptr_t)-3;\n\tif (!pd[pd_entry].bits.table_page) return ((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT) | (virtAddr & PD_MASK);\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pt[pt_entry].bits.present) return (uintptr_t)-4;\n\treturn ((uintptr_t)pt[pt_entry].bits.page << PAGE_SHIFT) | (virtAddr & PT_MASK);\n}\n\nunion PML * mmu_get_page(uintptr_t virtAddr, int flags) {\n\t/* This is all the same as x86, thankfully? */\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\tunion PML * root = this_core->current_pml;\n\n\t/* Get the PML4 entry for this address */\n\tspin_lock(frame_alloc_lock);\n\tif (!root[pml4_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\troot[pml4_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\t}\n\tspin_unlock(frame_alloc_lock);\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tspin_lock(frame_alloc_lock);\n\tif (!pdp[pdp_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\tpdp[pdp_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\t}\n\tspin_unlock(frame_alloc_lock);\n\n\tif (!pdp[pdp_entry].bits.table_page) {\n\t\tprintf(\"Warning: Tried to get page for a 1GiB block! %d\\n\", pdp_entry);\n\t\treturn NULL;\n\t}\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tspin_lock(frame_alloc_lock);\n\tif (!pd[pd_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\tpd[pd_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\t}\n\tspin_unlock(frame_alloc_lock);\n\n\tif (!pd[pd_entry].bits.table_page) {\n\t\tprintf(\"Warning: Tried to get page for a 2MiB block!\\n\");\n\t\treturn NULL;\n\t}\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\treturn (union PML *)&pt[pt_entry];\n\n_noentry:\n\tspin_unlock(frame_alloc_lock);\n\tprintf(\"no entry for requested page\\n\");\n\treturn NULL;\n}\n\nstatic int copy_page_maybe(union PML * pt_in, union PML * pt_out, size_t l, uintptr_t address) {\n\tspin_lock(frame_alloc_lock);\n\n\t/* TODO cow bits */\n\n\tchar * page_in = mmu_map_from_physical((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT);\n\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\tmmu_frame_set(newPage);\n\tchar * page_out = mmu_map_from_physical(newPage);\n\tmemcpy(page_out,page_in,PAGE_SIZE);\n\tasm volatile (\"dmb sy\\nisb\" ::: \"memory\");\n\n\tfor (uintptr_t x = (uintptr_t)page_out; x < (uintptr_t)page_out + PAGE_SIZE; x += 64) {\n\t\tasm volatile (\"dc cvau, %0\" :: \"r\"(x));\n\t}\n\tfor (uintptr_t x = (uintptr_t)page_out; x < (uintptr_t)page_out + PAGE_SIZE; x += 64) {\n\t\tasm volatile (\"ic ivau, %0\" :: \"r\"(x));\n\t}\n\n\tpt_out[l].raw = 0;\n\tpt_out[l].bits.table_page = 1;\n\tpt_out[l].bits.present = 1;\n\tpt_out[l].bits.ap = pt_in[l].bits.ap;\n\tpt_out[l].bits.af = pt_in[l].bits.af;\n\tpt_out[l].bits.sh = pt_in[l].bits.sh;\n\tpt_out[l].bits.attrindx = pt_in[l].bits.attrindx;\n\tpt_out[l].bits.page = newPage >> PAGE_SHIFT;\n\tasm volatile (\"\" ::: \"memory\");\n\n\tspin_unlock(frame_alloc_lock);\n\treturn 0;\n}\n\nunion PML * mmu_clone(union PML * from) {\n\t/* Clone the current PMLs... */\n\tif (!from) from = this_core->current_pml;\n\n\t/* First get a page for ourselves. */\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\tmmu_frame_set(newPage);\n\tspin_unlock(frame_alloc_lock);\n\tunion PML * pml4_out = mmu_map_from_physical(newPage);\n\n\t/* Zero bottom half */\n\tmemset(&pml4_out[0], 0, 256 * sizeof(union PML));\n\n\t/* Copy top half */\n\tmemcpy(&pml4_out[256], &from[256], 256 * sizeof(union PML));\n\n\t/* Copy PDPs */\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tspin_lock(frame_alloc_lock);\n\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\tmmu_frame_set(newPage);\n\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\tunion PML * pdp_out = mmu_map_from_physical(newPage);\n\t\t\tmemset(pdp_out, 0, 512 * sizeof(union PML));\n\t\t\tpml4_out[i].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t\t\t/* Copy the PDs */\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tspin_lock(frame_alloc_lock);\n\t\t\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\t\t\tmmu_frame_set(newPage);\n\t\t\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\t\t\tunion PML * pd_out = mmu_map_from_physical(newPage);\n\t\t\t\t\tmemset(pd_out, 0, 512 * sizeof(union PML));\n\t\t\t\t\tpdp_out[j].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t\t\t\t\t/* Now copy the PTs */\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tspin_lock(frame_alloc_lock);\n\t\t\t\t\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\t\t\t\t\tmmu_frame_set(newPage);\n\t\t\t\t\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\t\t\t\t\tunion PML * pt_out = mmu_map_from_physical(newPage);\n\t\t\t\t\t\t\tmemset(pt_out, 0, 512 * sizeof(union PML));\n\t\t\t\t\t\t\tpd_out[k].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t\t\t\t\t\t\t/* Now, finally, copy pages */\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (1) { //pt_in[l].bits.user) {\n\t\t\t\t\t\t\t\t\t\tcopy_page_maybe(pt_in, pt_out, l, address);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t/* If it's not a user page, just copy directly */\n\t\t\t\t\t\t\t\t\t\tpt_out[l].raw = pt_in[l].raw;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} /* Else, mmap'd files? */\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn pml4_out;\n}\n\nuintptr_t mmu_allocate_a_frame(void) {\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t index = mmu_first_frame();\n\tmmu_frame_set(index << PAGE_SHIFT);\n\tspin_unlock(frame_alloc_lock);\n\treturn index;\n}\n\nuintptr_t mmu_allocate_n_frames(int n) {\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t index = mmu_first_n_frames(n);\n\tfor (int i = 0; i < n; ++i) {\n\t\tmmu_frame_set((index+i) << PAGE_SHIFT);\n\t}\n\tspin_unlock(frame_alloc_lock);\n\treturn index;\n}\n\nsize_t mmu_count_user(union PML * from) {\n\t/* We walk 'from' and count user pages */\n\tsize_t out = 0;\n\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tout++;\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tout++;\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\t/* Calculate final address to skip SHM */\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.ap & 1) {\n\t\t\t\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn out;\n}\n\nsize_t mmu_count_shm(union PML * from) {\n\t/* We walk 'from' and count shm region stuff */\n\tsize_t out = 0;\n\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\t/* Calculate final address to skip SHM */\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address < USER_DEVICE_MAP && address >= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.ap & 1) {\n\t\t\t\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn out;\n}\n\nsize_t mmu_total_memory(void) {\n\treturn total_memory;\n}\n\nsize_t mmu_used_memory(void) {\n\tsize_t ret = 0;\n\tsize_t i, j;\n\tfor (i = 0; i < INDEX_FROM_BIT(nframes); ++i) {\n\t\tfor (j = 0; j < 32; ++j) {\n\t\t\tuint32_t testFrame = (uint32_t)0x1 << j;\n\t\t\tif (frames[i] & testFrame) {\n\t\t\t\tret++;\n\t\t\t}\n\t\t}\n\t}\n\treturn ret * 4 - unavailable_memory;\n}\n\nvoid mmu_free(union PML * from) {\n\t/* walk and free pages */\n\tif (!from) {\n\t\tprintf(\"can't clear NULL directory\\n\");\n\t\treturn;\n\t}\n\n\tspin_lock(frame_alloc_lock);\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\t/* Do not free shared mappings; SHM subsystem does that for SHM, devices don't need it. */\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\t/* Free only user pages */\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.ap & 1) {\n\t\t\t\t\t\t\t\t\t\tmmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\t\t\t\tpt_in[l].raw = 0;\n\t\t\t\t\t\t\t\t\t\t//free_page_maybe(pt_in,l,address);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmmu_frame_clear((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tpd_in[k].raw = 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmmu_frame_clear((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tpdp_in[j].raw = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmmu_frame_clear((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfrom[i].raw = 0;\n\t\t}\n\t}\n\n\tuintptr_t physAddr = (((uintptr_t)from) & PHYS_MASK);\n\tmmu_frame_clear(physAddr);\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\tspin_unlock(frame_alloc_lock);\n}\n\nunion PML * mmu_get_kernel_directory(void) {\n\treturn mmu_map_from_physical((uintptr_t)&init_page_region - MODULE_BASE_START + aarch64_kernel_phys_base);\n}\n\nvoid mmu_set_directory(union PML * new_pml) {\n\t/* Set the EL0 and EL1 directy things?\n\t *   There are two of these... */\n\tif (!new_pml) new_pml = mmu_get_kernel_directory();\n\tthis_core->current_pml = new_pml;\n\tuintptr_t pml_phys = mmu_map_to_physical(new_pml, (uintptr_t)new_pml);\n\n\tasm volatile (\n\t\t\"msr TTBR0_EL1,%0\\n\"\n\t\t\"msr TTBR1_EL1,%0\\n\"\n\t\t\"isb sy\\n\"\n\t\t\"dsb ishst\\n\"\n\t\t\"tlbi vmalle1is\\n\"\n\t\t\"dsb ish\\n\"\n\t\t\"isb\\n\" :: \"r\"(pml_phys) : \"memory\");\n}\n\nvoid mmu_invalidate(uintptr_t addr) {\n}\n\nint mmu_get_page_deep(uintptr_t virtAddr, union PML ** pml4_out, union PML ** pdp_out, union PML ** pd_out, union PML ** pt_out) {\n\t/* This is all the same as x86, thankfully? */\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\t/* Zero all the outputs */\n\t*pdp_out  = NULL;\n\t*pd_out   = NULL;\n\t*pt_out   = NULL;\n\n\tspin_lock(frame_alloc_lock);\n\tunion PML * root = this_core->current_pml;\n\t*pml4_out = (union PML *)&root[pml4_entry];\n\tif (!root[pml4_entry].bits.present) goto _noentry;\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\t*pdp_out = (union PML *)&pdp[pdp_entry];\n\tif (!pdp[pdp_entry].bits.present) goto _noentry;\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\t*pd_out = (union PML *)&pd[pd_entry];\n\tif (!pd[pd_entry].bits.present) goto _noentry;\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\t*pt_out = (union PML *)&pt[pt_entry];\n\n\tspin_unlock(frame_alloc_lock);\n\treturn 0;\n\n_noentry:\n\tspin_unlock(frame_alloc_lock);\n\treturn 1;\n}\n\nstatic int maybe_release_directory(union PML * parent, union PML * child) {\n\t/* child points to one entry, to get the base, we can page align it */\n\tunion PML * table = (union PML *)((uintptr_t)child & PAGE_SIZE_MASK);\n\n\t/* Is everything in the table free? */\n\tfor (int i = 0; i < 512; ++i) {\n\t\tif (table[i].bits.present) return 0;\n\t}\n\n\tuintptr_t old_page = (parent->bits.page << PAGE_SHIFT);\n\n\t/* Then we can mark 'parent' as freed, clear the whole thing. */\n\tparent->raw = 0;\n\tmmu_frame_clear(old_page);\n\n\treturn 1;\n}\n\nvoid mmu_unmap_user(uintptr_t addr, size_t size) {\n\tfor (uintptr_t a = addr; a < addr + size; a += PAGE_SIZE) {\n\t\tunion PML * pml4, * pdp, * pd, * pt;\n\n\t\tif (a >= USER_DEVICE_MAP && a <= USER_SHM_HIGH) continue;\n\t\tif (mmu_get_page_deep(a, &pml4, &pdp, &pd, &pt)) continue;\n\n\t\tspin_lock(frame_alloc_lock);\n\n\t\t/* Free this page if it was present */\n\t\tif (pt && pt->bits.present) {\n\t\t\tif (pt->bits.ap & 1) {\n\t\t\t\tmmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT);\n\t\t\t\tpt->bits.present = 0;\n\t\t\t\tpt->bits.ap = 0;\n\t\t\t}\n\n\t\t\tif (maybe_release_directory(pd, pt)) {\n\t\t\t\tif (maybe_release_directory(pdp, pd)) {\n\t\t\t\t\tmaybe_release_directory(pml4, pdp);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmmu_invalidate(a);\n\t\t}\n\n\t\tspin_unlock(frame_alloc_lock);\n\t}\n}\n\n\nstatic char * heapStart = NULL;\nextern char end[];\n\nvoid * sbrk(size_t bytes) {\n\tif (!heapStart) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"sbrk: Called before heap was ready.\\n\");\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tif (!bytes) {\n\t\t/* Skip lock acquisition if we just wanted to know where the break was. */\n\t\treturn heapStart;\n\t}\n\n\tif (bytes & PAGE_LOW_MASK) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"sbrk: Size must be multiple of 4096, was %#zx\\n\", bytes);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tspin_lock(kheap_lock);\n\tvoid * out = heapStart;\n\n\tfor (uintptr_t p = (uintptr_t)out; p < (uintptr_t)out + bytes; p += PAGE_SIZE) {\n\t\tunion PML * page = mmu_get_page(p, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE | MMU_FLAG_KERNEL);\n\t}\n\n\theapStart += bytes;\n\tspin_unlock(kheap_lock);\n\treturn out;\n}\n\nstatic uintptr_t mmio_base_address = MMIO_BASE_START;\nvoid * mmu_map_mmio_region(uintptr_t physical_address, size_t size) {\n\tif (size & PAGE_LOW_MASK) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"mmu_map_mmio_region: MMIO region size must be multiple of 4096 bytes, was %#zx.\\n\", size);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tspin_lock(mmio_space_lock);\n\tvoid * out = (void*)mmio_base_address;\n\tfor (size_t i = 0; i < size; i += PAGE_SIZE) {\n\t\tunion PML * p = mmu_get_page(mmio_base_address + i, MMU_GET_MAKE);\n\t\tmmu_frame_map_address(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE | MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH, physical_address + i);\n\t}\n\tmmio_base_address += size;\n\tspin_unlock(mmio_space_lock);\n\n\treturn out;\n}\n\nstatic uintptr_t module_base_address = MODULE_BASE_START;\n\nvoid * mmu_map_module(size_t size) {\n\tif (size & PAGE_LOW_MASK) {\n\t\tsize += (PAGE_LOW_MASK + 1) - (size & PAGE_LOW_MASK);\n\t}\n\n\tspin_lock(module_space_lock);\n\tvoid * out = (void*)module_base_address;\n\tfor (size_t i = 0; i < size; i += PAGE_SIZE) {\n\t\tunion PML * p = mmu_get_page(module_base_address + i, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE);\n\t}\n\tmodule_base_address += size;\n\tspin_unlock(module_space_lock);\n\n\treturn out;\n}\n\nvoid mmu_unmap_module(uintptr_t start_address, size_t size) {\n}\n\nint mmu_copy_on_write(uintptr_t address) {\n\t\n\treturn 1;\n}\n\nint mmu_validate_user_pointer(const void * addr, size_t size, int flags) {\n\t//printf(\"mmu_validate_user_pointer(%#zx, %lu, %u);\\n\", (uintptr_t)addr, size, flags);\n\tif (addr == NULL && !(flags & MMU_PTR_NULL)) return 0;\n\tif (size >     0x800000000000) return 0;\n\n\tuintptr_t base = (uintptr_t)addr;\n\tuintptr_t end  = size ? (base + (size - 1)) : base;\n\n\t/* Get start page, end page */\n\tuintptr_t page_base = base >> 12;\n\tuintptr_t page_end  =  end >> 12;\n\n\tfor (uintptr_t page = page_base; page <= page_end; ++page) {\n\t\tif ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0;\n\t\tunion PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12);\n\t\tif (!page_entry) {\n\t\t\treturn 0;\n\t\t}\n\t\tif (!page_entry->bits.present) {\n\t\t\treturn 0;\n\t\t}\n\t\tif (!(page_entry->bits.ap & 1)) {\n\t\t\treturn 0;\n\t\t}\n\t\tif ((page_entry->bits.ap & 2) && (flags & MMU_PTR_WRITE)) {\n\t\t\treturn 0;\n\t\t\t//if (mmu_copy_on_write((uintptr_t)(page << 12))) return 0;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nstatic uintptr_t k2p(void * x) {\n\treturn ((uintptr_t)x - MODULE_BASE_START) + aarch64_kernel_phys_base;\n}\n\nvoid mmu_init(uintptr_t memaddr, size_t memsize, uintptr_t firstFreePage, uintptr_t endOfRamDisk) {\n\tthis_core->current_pml = (union PML*)mmu_get_kernel_directory();\n\n\t/* Convert from bytes to kibibytes */\n\ttotal_memory = memsize / 1024;\n\n\t/* We don't currently support gaps in this setup. */\n\tunavailable_memory = 0;\n\n\t/* MAIR setup? */\n\tuint64_t mair = (0x000000000044ff00);\n\tasm volatile (\"msr MAIR_EL1,%0\" :: \"r\"(mair));\n\tasm volatile (\"mrs %0,MAIR_EL1\" : \"=r\"(mair));\n\tdprintf(\"mmu: MAIR_EL1=0x%016lx\\n\", mair);\n\n\tasm volatile (\"\" ::: \"memory\");\n\n\t/* Replicate the mapping we already have */\n\tinit_page_region[511].raw = k2p(&high_base_pml) | PTE_VALID | PTE_TABLE | PTE_AF;\n\tinit_page_region[510].raw = k2p(&heap_base_pml) | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* \"Identity\" map at -512GiB */\n\tfor (size_t i = 0; i < 500; ++i) {\n\t\thigh_base_pml[i].raw = (i << 30) | PTE_VALID | PTE_AF | (1 << 2);\n\t}\n\n\n\t/* Set up some space to map us */\n\n\t/* How many 2MiB spans do we need to cover to endOfRamDisk? */\n\tsize_t twoms  = (endOfRamDisk + (LARGE_PAGE_SIZE - 1)) / LARGE_PAGE_SIZE;\n\n\t/* init_page_region[511] -> high_base_pml[510] -> kbase_pmls[0] -> kbase_pmls[1+n] */\n\thigh_base_pml[510].raw = k2p(&kbase_pmls[0]) | PTE_VALID | PTE_TABLE | PTE_AF;\n\tfor (size_t j = 0; j < twoms; ++j) {\n\t\tkbase_pmls[0][j].raw = k2p(&kbase_pmls[1+j]) | PTE_VALID | PTE_TABLE | PTE_AF;\n\t\tfor (int i = 0; i < 512; ++i) {\n\t\t\tkbase_pmls[1+j][i].raw = (uintptr_t)(aarch64_kernel_phys_base + LARGE_PAGE_SIZE * j + PAGE_SIZE * i) |\n\t\t\t\tPTE_VALID | PTE_AF | PTE_SH_A | PTE_TABLE | (1 << 2);\n\t\t}\n\t}\n\n\t/* We should be ready to switch to our page directory? */\n\tasm volatile (\"msr TTBR0_EL1,%0\" : : \"r\"(k2p(&init_page_region)));\n\tasm volatile (\"msr TTBR1_EL1,%0\" : : \"r\"(k2p(&init_page_region)));\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\n\t/* Let's map some heap. */\n\theap_base_pml[0].raw = k2p(&heap_base_pd)       | PTE_VALID | PTE_TABLE | PTE_AF;\n\theap_base_pd[0].raw  = k2p(&heap_base_pt[0])    | PTE_VALID | PTE_TABLE | PTE_AF;\n\theap_base_pd[1].raw  = k2p(&heap_base_pt[512])  | PTE_VALID | PTE_TABLE | PTE_AF;\n\theap_base_pd[2].raw  = k2p(&heap_base_pt[1024]) | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* Physical frame allocator. We're gonna do this the same as the one we have x86-64, because\n\t * I can't be bothered to think of anything better right now... */\n\tram_starts_at = memaddr;\n\tnframes = (memsize) >> 12;\n\tsize_t bytesOfFrames = INDEX_FROM_BIT(nframes * 8);\n\tbytesOfFrames = (bytesOfFrames + PAGE_LOW_MASK) & PAGE_SIZE_MASK;\n\n\t/* TODO we should figure out where the DTB ends on virt, as that's where we can\n\t *      start doing this... */\n\tsize_t pagesOfFrames = bytesOfFrames >> 12;\n\n\t/* Map pages for it... */\n\tfor (size_t i = 0; i < pagesOfFrames; ++i) {\n\t\theap_base_pt[i].raw = (firstFreePage + (i << 12)) | PTE_VALID | PTE_AF | PTE_SH_A | PTE_TABLE | (1 << 2);\n\t}\n\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\n\t/* Just assume everything is in use. */\n\tframes = (void*)((uintptr_t)KERNEL_HEAP_START);\n\tmemset((void*)frames, 0x00, bytesOfFrames);\n\n\t/* Set frames as in use... */\n\tfor (uintptr_t i = memaddr; i < firstFreePage + bytesOfFrames; i+= PAGE_SIZE) {\n\t\tmmu_frame_set(i);\n\t}\n\n\t/* Set kernel space as in use */\n\tfor (uintptr_t i = 0; i < twoms * LARGE_PAGE_SIZE; i += PAGE_SIZE) {\n\t\tmmu_frame_set(aarch64_kernel_phys_base + i);\n\t}\n\n\theapStart = (char*)KERNEL_HEAP_START + bytesOfFrames;\n\n\tlowest_available = (firstFreePage + bytesOfFrames) - memaddr;\n\tmodule_base_address = endOfRamDisk + MODULE_BASE_START;\n\tif (module_base_address & PAGE_LOW_MASK) {\n\t\tmodule_base_address = (module_base_address & PAGE_SIZE_MASK) + PAGE_SIZE;\n\t}\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/pl011.c",
    "content": "/**\n * @file  kernel/arch/aarch64/pl011.c\n * @brief Rudimentary serial driver for the pl011 uart\n *\n * TODO: Headers with sensible register names and macros for bits.\n * TODO: TTY configuration support.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022-2025 K. Lange\n */\n#include <stdint.h>\n#include <kernel/process.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/pty.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/aarch64/dtb.h>\n#include <kernel/arch/aarch64/gic.h>\n\nstatic int pl011_irq(process_t * this, int irq, void * data) {\n\tvolatile uint32_t * uart_mapped = (volatile uint32_t *)data;\n\tuint32_t mis = uart_mapped[16];\n\tif (mis) {\n\t\tif (mis & (1 << 4)) {\n\t\t\tmake_process_ready(this);\n\t\t}\n\t\tuart_mapped[17] = mis;\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void pl011_fill_name(pty_t * pty, char * name) {\n\tsnprintf(name, 100, \"/dev/ttyS0\");\n}\n\nstatic void pl011_write_out(pty_t * pty, uint8_t c) {\n\tvolatile uint32_t * uart_mapped = (volatile uint32_t *)pty->_private;\n\tuart_mapped[0] = c;\n}\n\nstatic void pl011_thread(void * arg) {\n\tvolatile uint32_t * uart_mapped = (volatile uint32_t *)arg;\n\tpty_t * pty = pty_new(NULL, 0);\n\tpty->write_out = pl011_write_out;\n\tpty->fill_name = pl011_fill_name;\n\tpty->slave->gid = 2; /* dialout group */\n\tpty->slave->mask = 0660;\n\tpty->_private = arg;\n\tvfs_mount(\"/dev/ttyS0\", pty->slave, \"pl011\", \"\");\n\n\t/* Set up interrupt callback */\n\tgic_assign_interrupt(1, pl011_irq, (void*)uart_mapped);\n\n\t/* Enable interrupts */\n\tuart_mapped[14] |= (1 << 4);\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\t/* Handle incoming data */\n\twhile (1) {\n\t\twhile ((uart_mapped[6] & (1 << 4))) {\n\t\t\tswitch_task(0);\n\t\t}\n\n\t\tuint8_t rx = uart_mapped[0];\n\t\ttty_input_process(pty, rx);\n\t}\n}\n\nvoid pl011_start(void) {\n\tuint32_t * uart = dtb_find_node_prefix(\"pl011\");\n\tif (!uart) return;\n\n\t/* I know this is going to be 0x09000000, but let's find it anyway */\n\tuint32_t * reg = dtb_node_find_property(uart, \"reg\");\n\tuintptr_t  uart_base = swizzle(reg[3]);\n\tvolatile uint32_t * uart_mapped = (volatile uint32_t*)mmu_map_mmio_region(uart_base, 0x1000);\n\n\t/*\n\t * PL011 UART configuration\n\t *\n\t * Setup line control here so we do it atomically, otherwise doing it in the thread\n\t * can cause other cores printing debug messages to write when the port is disabled,\n\t * which causes QEMU to print warning messages.\n\t *\n\t * We are missing a few things because this is only intended to work with QEMU.\n\t * We should probably add support for baud rate configuration and actually map\n\t * the line control configuration to the TTY state.\n\t */\n\tuart_mapped[12] = 0;     /* UARTCR  - Disable the UART */\n\tuart_mapped[11] = 0x70;  /* UARTLCR - Stick parity disabled, 8-bit words, tx+rx FIFO enabled; one stop bit, (odd parity), parity disabled, no break */\n\tuart_mapped[12] = 0x301; /* UARTCR  - Enable tx/rx, enable UART */\n\n\tspawn_worker_thread(pl011_thread, \"[pl011]\", (void*)uart_mapped);\n}\n\n\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi.c",
    "content": "/**\n * @file  kernel/arch/aarch64/rpi.c\n * @brief Raspberry Pi-specific stuff.\n *\n * Probably going to be mailbox interfaces and such.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/gzip.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/aarch64/rpi.h>\n\nextern char end[];\n\nvoid rpi_load_ramdisk(struct rpitag * tag, uintptr_t * ramdisk_phys_base, size_t * ramdisk_size) {\n\tdprintf(\"rpi: compressed ramdisk is at %#x \\n\", tag->ramdisk_start);\n\tdprintf(\"rpi: end of ramdisk is at %#x \\n\", tag->ramdisk_end);\n\tdprintf(\"rpi: uncompress ramdisk to %#zx \\n\", (uintptr_t)&end);\n\tuint32_t size;\n\tmemcpy(&size, (void*)(uintptr_t)(tag->ramdisk_end - sizeof(uint32_t)), sizeof(uint32_t));\n\tdprintf(\"rpi: size of uncompressed ramdisk is %#x\\n\", size);\n\n\tgzip_inputPtr  = (uint8_t*)(uintptr_t)tag->ramdisk_start;\n\tgzip_outputPtr = (uint8_t*)&end;\n\n\tif (gzip_decompress()) {\n\t\tdprintf(\"rpi: gzip failure, not mounting ramdisk\\n\");\n\t\twhile (1);\n\t}\n\n\tdprintf(\"rpi: ramdisk decompressed\\n\");\n\n\tfor (size_t i = 0; i < size; i += 64) {\n\t\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"((uintptr_t)&end + i) : \"memory\");\n\t}\n\n\t*ramdisk_phys_base = mmu_map_to_physical(NULL, (uintptr_t)&end);\n\t*ramdisk_size = size;\n\n\tdprintf(\"rpi: ramdisk_phys_base set to %#zx\\n\", *ramdisk_phys_base);\n}\n\nvoid rpi_set_cmdline(char ** args_out) {\n\t*args_out = (char *)\"vid=preset start=live-session migrate root=/dev/ram0\";\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi400/fbterm.c",
    "content": "#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n\nstatic int fbterm_scroll = 0;\nstatic void (*write_char)(int, int, int, uint32_t) = NULL;\nstatic int (*get_width)(void) = NULL;\nstatic int (*get_height)(void) = NULL;\nstatic void (*scroll_terminal)(void) = NULL;\n\nstatic int x = 0;\nstatic int y = 0;\nstatic int term_state = 0;\nstatic char term_buf[1024] = {0};\nstatic int term_buf_c = 0;\n\n/* Is this in a header somewhere? */\nextern uint8_t * lfb_vid_memory;\nextern uint16_t lfb_resolution_x;\nextern uint16_t lfb_resolution_y;\nextern uint16_t lfb_resolution_b;\nextern uint32_t lfb_resolution_s;\nextern size_t lfb_memsize;\n\n/* Bitmap font details */\n#include \"../../../../apps/terminal-font.h\"\n#define char_height LARGE_FONT_CELL_HEIGHT\n#define char_width  LARGE_FONT_CELL_WIDTH\n\n/* Default colors */\n#define BG_COLOR 0xFF000000 /* Background */\n#define FG_COLOR 0xFFCCCCCC /* Main text color */\n\nstatic uint32_t fg_color = FG_COLOR;\nstatic uint32_t bg_color = BG_COLOR;\n\nextern uint32_t lfb_resolution_s;\n\nstatic inline void set_point(int x, int y, uint32_t value) {\n\tif (lfb_resolution_b == 32) {\n\t\t((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x] = value;\n\t} else if (lfb_resolution_b == 24) {\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 0] = (value >> 0) & 0xFF;\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 1] = (value >> 8) & 0xFF;\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 2] = (value >> 16) & 0xFF;\n\t}\n}\n\nstatic void fb_write_char(int _x, int _y, int val, uint32_t color) {\n\tif (val > 128) {\n\t\tval = 4;\n\t}\n\n\tint x = 1 + _x * char_width;\n\tint y = _y * char_height;\n\n\tuint8_t * c = large_font[val];\n\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\tif (c[i] & (1 << (LARGE_FONT_MASK-j))) {\n\t\t\t\tset_point(x+j,y+i,color);\n\t\t\t} else {\n\t\t\t\tset_point(x+j,y+i,bg_color);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * @brief Basic 16-color ANSI palette with Tango colors.\n */\nstatic uint32_t term_colors[] = {\n\t0xFF000000,\n\t0xFFCC0000,\n\t0xFF4E9A06,\n\t0xFFC4A000,\n\t0xFF3465A4,\n\t0xFF75507B,\n\t0xFF06989A,\n\t0xFFD3D7CF,\n\n\t0xFF555753,\n\t0xFFEF2929,\n\t0xFF8AE234,\n\t0xFFFCE94F,\n\t0xFF729FCF,\n\t0xFFAD7FA8,\n\t0xFF34E2E2,\n\t0xFFEEEEEC,\n};\n\nstatic int fb_get_width(void) {\n\treturn (lfb_resolution_x - 1) / char_width;\n}\n\nstatic int fb_get_height(void) {\n\treturn lfb_resolution_y / char_height;\n}\n\nstatic void fb_scroll_terminal(void) {\n\tmemmove(lfb_vid_memory, lfb_vid_memory + sizeof(uint32_t) * lfb_resolution_x * char_height, (lfb_resolution_y - char_height) * lfb_resolution_x * 4);\n\tmemset(lfb_vid_memory + sizeof(uint32_t) * (lfb_resolution_y - char_height) * lfb_resolution_x, 0x00, char_height * lfb_resolution_x * 4);\n}\n\nstatic void draw_square(int x, int y) {\n\tint center_x = lfb_resolution_x / 2;\n\tint center_y = lfb_resolution_y / 2;\n\tfor (size_t _y = 0; _y < 7; ++_y) {\n\t\tuint32_t color = 0xFF00B2FF - (y * 8 + _y) * 0x200;\n\t\tfor (size_t _x = 0; _x < 7; ++_x) {\n\t\t\tset_point(center_x - 32 + x * 8 + _x, center_y - 32 + y * 8 + _y, color);\n\t\t}\n\t}\n}\n\nstatic void fbterm_draw_logo(void) {\n\tuint64_t logo_squares = 0x981818181818FFFFUL;\n\tfor (size_t y = 0; y < 8; ++y) {\n\t\tfor (size_t x = 0; x < 8; ++x) {\n\t\t\tif (logo_squares & (1 << x)) {\n\t\t\t\tdraw_square(x,y);\n\t\t\t}\n\t\t}\n\t\tlogo_squares >>= 8;\n\t}\n}\n\nstatic void fbterm_init_framebuffer(void) {\n\twrite_char = fb_write_char;\n\tget_width = fb_get_width;\n\tget_height = fb_get_height;\n\tscroll_terminal = fb_scroll_terminal;\n\tfbterm_draw_logo();\n}\n\nstatic void cursor_update(void) {\n\tif (x >= get_width()) {\n\t\tx = 0;\n\t\ty++;\n\t}\n\tif (y >= get_height()) {\n\t\tif (fbterm_scroll) {\n\t\t\ty--;\n\t\t\tscroll_terminal();\n\t\t} else {\n\t\t\ty = 0;\n\t\t}\n\t}\n}\n\nstatic void process_char(char ch) {\n\tif (term_state == 1) {\n\t\tif (ch == '[') {\n\t\t\tterm_buf_c = 0;\n\t\t\tterm_buf[term_buf_c] = '\\0';\n\t\t\tterm_state = 2;\n\t\t} else {\n\t\t\tterm_state = 0;\n\t\t\tprocess_char(ch);\n\t\t}\n\t\treturn;\n\t} else if (term_state == 2) {\n\t\tif ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {\n\t\t\t/* do the thing */\n\t\t\tswitch (ch) {\n\t\t\t\tcase 'm': {\n\t\t\t\t\tchar * arg = &term_buf[0];\n\t\t\t\t\tchar * next;\n\t\t\t\t\tint argC = 0;\n\t\t\t\t\tint isBold = 0;\n\t\t\t\t\tdo {\n\t\t\t\t\t\tnext = strchr(arg, ';');\n\t\t\t\t\t\tif (next) { *next = '\\0'; next++; }\n\t\t\t\t\t\tint asInt = atoi(arg);\n\t\t\t\t\t\tif (asInt == 0) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t\tbg_color = BG_COLOR;\n\t\t\t\t\t\t\tisBold = 0;\n\t\t\t\t\t\t} else if (asInt == 1) {\n\t\t\t\t\t\t\tisBold = 1;\n\t\t\t\t\t\t} else if (asInt == 22) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t\tisBold = 0;\n\t\t\t\t\t\t} else if (asInt >= 30 && asInt <= 37) {\n\t\t\t\t\t\t\tfg_color = term_colors[asInt-30 + (isBold ? 8 : 0)];\n\t\t\t\t\t\t} else if (asInt >= 90 && asInt <= 97) {\n\t\t\t\t\t\t\tfg_color = term_colors[asInt-90 + 8];\n\t\t\t\t\t\t} else if (asInt >= 40 && asInt <= 47) {\n\t\t\t\t\t\t\tbg_color = term_colors[asInt-40 + (isBold ? 8 : 0)];\n\t\t\t\t\t\t} else if (asInt >= 100 && asInt <= 107) {\n\t\t\t\t\t\t\tbg_color = term_colors[asInt-100 + 8];\n\t\t\t\t\t\t} else if (asInt == 38) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t} else if (asInt == 48) {\n\t\t\t\t\t\t\tbg_color = BG_COLOR;\n\t\t\t\t\t\t} else if (asInt == 7) {\n\t\t\t\t\t\t\tuint32_t tmp = fg_color;\n\t\t\t\t\t\t\tfg_color = bg_color;\n\t\t\t\t\t\t\tbg_color = tmp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\targ = next;\n\t\t\t\t\t\targC++;\n\t\t\t\t\t} while (arg);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'G': {\n\t\t\t\t\t/* Set cursor column */\n\t\t\t\t\tx = atoi(term_buf) - 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'K': {\n\t\t\t\t\tif (atoi(term_buf) == 0) {\n\t\t\t\t\t\tfor (int i = x; i < get_width(); ++i) {\n\t\t\t\t\t\t\twrite_char(i,y,' ',bg_color);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tterm_state = 0;\n\t\t} else {\n\t\t\tterm_buf[term_buf_c++] = ch;\n\t\t\tterm_buf[term_buf_c] = '\\0';\n\t\t}\n\t\treturn;\n\t} else if (ch == '\\033') {\n\t\tterm_state = 1;\n\t\treturn;\n\t}\n\n\twrite_char(x,y,' ',bg_color);\n\tswitch (ch) {\n\t\tcase '\\n':\n\t\t\tx = 0;\n\t\t\ty++;\n\t\t\tbreak;\n\t\tcase '\\r':\n\t\t\tx = 0;\n\t\t\tbreak;\n\t\tcase '\\b':\n\t\t\tif (x) {\n\t\t\t\tx--;\n\t\t\t\twrite_char(x,y,' ',fg_color);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif ((unsigned int)ch > 127) return;\n\t\t\twrite_char(x,y,ch,fg_color);\n\t\t\tx++;\n\t\t\tbreak;\n\t}\n\tcursor_update();\n}\n\nstatic size_t (*previous_writer)(size_t,uint8_t*) = NULL;\n\nsize_t fbterm_write(size_t size, uint8_t *buffer) {\n\tif (!buffer) return 0;\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tprocess_char(buffer[i]);\n\t}\n\tif (previous_writer) previous_writer(size,buffer);\n\treturn size;\n}\n\nvoid fbterm_initialize(void) {\n\tif (!lfb_resolution_x) {\n\t\treturn;\n\t}\n\tfbterm_init_framebuffer();\n\tprevious_writer = printf_output;\n\tprintf_output = fbterm_write;\n\tprintf(\"fbterm: Generic framebuffer text output enabled.\\n\");\n}\n\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi400/link.ld",
    "content": "OUTPUT_FORMAT(elf64-littleaarch64)\nENTRY(start)\n\nSECTIONS\n{\n\t. = 0x80000;\n\tphys = .;\n\n\t.text BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.bootstrap)\n\t\tcode = .;\n\t\t*(.text)\n\t}\n\n\t.rodata BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.rodata)\n\t}\n\n\t.data BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tdata = .;\n\t\t*(.data)\n\t\t*(.symbols)\n\t\tPROVIDE(kernel_symbols_start = .);\n\t\tPROVIDE(kernel_symbols_end = .);\n\t\tPROVIDE(bss_start = .);\n\t}\n\n\t__bss_start = .;\n\t.bss BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tbss = .;\n\t\t*(COMMON)\n\t\t*(.bss)\n\t\t*(.stack)\n\t}\n\t__bss_end = .;\n\t__bss_size = __bss_end - __bss_start;\n\n\t/* Some built-in stack space... */\n\t. = ALIGN(0x1000);\n\t. = . + 0x1000;\n\t__bootstrap_stack_top = .;\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.eh_frame)\n\t\t*(.note.gnu.build-id)\n\t}\n\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi400/main.c",
    "content": "/**\n * @file  kernel/arch/aarch64/rpi400/main.c\n * @brief Boot stub for Raspberry Pi 400.\n *\n * This gets built into kernel8.img, which embeds the actual kernel and\n * a compress ramdisk. The bootstub is responsible for acquiring the\n * initial framebuffer, setting the cores to max speed, setting up the\n * MMU, and loading the actual kernel at -2GiB.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/elf.h>\n\n#include <kernel/arch/aarch64/rpi.h>\n\n#define MMIO_BASE 0xFE000000UL\n#define MBOX_BASE    (MMIO_BASE + 0xB880)\n#define MBOX_READ    (MBOX_BASE + 0x00)\n#define MBOX_STATUS  (MBOX_BASE + 0x18)\n#define MBOX_WRITE   (MBOX_BASE + 0x20)\n#define MBOX_FULL    0x80000000\n#define MBOX_EMPTY   0x40000000\n#define MBOX_RESPONSE 0x80000000\n#define MBOX_REQUEST  0\n\nvolatile uint32_t __attribute__((aligned(16))) mbox[36];\n\nstatic uint32_t mmio_read32(uintptr_t addr) {\n\tuint32_t res = *((volatile uint32_t*)(addr));\n\treturn res;\n}\nstatic void mmio_write32(uintptr_t addr, uint32_t val) {\n\t(*((volatile uint32_t*)(addr))) = val;\n}\n\nuint32_t mbox_call(uint8_t ch) {\n\tuint32_t r = ((uint32_t)((uintptr_t)&mbox) & ~0xF) | (ch & 0xF);\n\n\twhile (mmio_read32(MBOX_STATUS) == MBOX_FULL); /* wait for mailbox to be ready */\n\tmmio_write32(MBOX_WRITE, r);\n\n\twhile (1) {\n\t\twhile (mmio_read32(MBOX_STATUS) & MBOX_EMPTY);\n\t\tif (r == mmio_read32(MBOX_READ)) return mbox[1] == MBOX_RESPONSE;\n\t}\n}\n\nuint8_t * lfb_vid_memory = 0;\nuint16_t lfb_resolution_x = 0;\nuint16_t lfb_resolution_y = 0;\nuint16_t lfb_resolution_b = 0;\nuint32_t lfb_resolution_s = 0;\nsize_t lfb_memsize = 0;\n\nvoid * malloc(size_t x) {\n\twhile (1);\n}\n\n#define MB(j) mbox[i++] = j\nint rpi_fb_init(void) {\n\tint i = 0;\n\n\tMB(35 * 4);\n\tMB(MBOX_REQUEST);\n\n\tMB(0x48003);\n\tMB(8);\n\tMB(0);\n\tint fb_width = i;\n\tMB(1920);\n\tint fb_height = i;\n\tMB(1080);\n\n\tMB(0x48004);\n\tMB(8);\n\tMB(8);\n\tMB(1920);\n\tMB(1080);\n\n\tMB(0x48009);\n\tMB(8);\n\tMB(8);\n\tMB(0);\n\tMB(0);\n\n\tMB(0x48005);\n\tMB(4);\n\tMB(4);\n\tint fb_bpp = i;\n\tMB(32);\n\n\tMB(0x48006);\n\tMB(4);\n\tMB(4);\n\tMB(1);\n\n\tMB(0x40001);\n\tMB(8);\n\tMB(8);\n\tint fb_pointer = i;\n\tMB(4096);\n\tint fb_size = i;\n\tMB(0);\n\n\tMB(0x40008);\n\tMB(4);\n\tMB(4);\n\tint fb_pitch = i;\n\tMB(0);\n\n\tMB(0);\n\n\tif (mbox_call(8) && mbox[fb_bpp] == 32 && mbox[fb_pointer] != 0) {\n\t\tlfb_vid_memory = (uint8_t*)(uintptr_t)(mbox[fb_pointer] & 0x3FFFFFFF);\n\t\tlfb_resolution_x = mbox[fb_width];\n\t\tlfb_resolution_y = mbox[fb_height];\n\t\tlfb_resolution_s = mbox[fb_pitch];\n\t\tlfb_resolution_b = mbox[fb_bpp];\n\t\tlfb_memsize = mbox[fb_size];\n\n\t\tfor (unsigned int y = 0; y < lfb_resolution_y; ++y) {\n\t\t\tfor (unsigned int x = 0; x < lfb_resolution_x; ++x) {\n\t\t\t\t*(volatile uint32_t *)((uintptr_t)lfb_vid_memory + y * lfb_resolution_s + x * 4) = 0x3ea3f0;\n\t\t\t}\n\t\t}\n\t\textern void fbterm_initialize(void);\n\t\tfbterm_initialize();\n\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nvoid rpi_cpu_freq(void) {\n\tint max_rate = 0;\n\t{\n\t\tint i = 0;\n\t\tMB(13 * 4);\n\t\tMB(MBOX_REQUEST);\n\n\t\tMB(0x30004);\n\t\tMB(8);\n\t\tMB(0);\n\t\tMB(3); /* arm core */\n\t\tint max_hz = i;\n\t\tMB(0);\n\n\t\tMB(0x30047);\n\t\tMB(8);\n\t\tMB(0);\n\t\tMB(3); /* arm core */\n\t\tint cur_hz = i;\n\t\tMB(0);\n\n\t\tMB(0);\n\n\t\tmbox_call(8);\n\n\t\tprintf(\"bootstub: max clock rate is %u Hz, current is %u Hz\\n\", mbox[max_hz], mbox[cur_hz]);\n\t\tmax_rate = mbox[max_hz];\n\t}\n\n\tif (max_rate) {\n\t\tint i = 0;\n\t\tMB(9 * 4);\n\t\tMB(MBOX_REQUEST);\n\n\t\tMB(0x38002);\n\t\tMB(12);\n\t\tMB(0);\n\t\tMB(3);\n\t\tint rate = i;\n\t\tMB(max_rate);\n\t\tMB(0); /* do not skip turbo setting */\n\n\t\tMB(0);\n\n\t\tmbox_call(8);\n\t\tprintf(\"bootstub: clock rate set to %u Hz\\n\", mbox[rate]);\n\t}\n}\n\nextern char _kernel_start[];\nextern char _kernel_end[];\nextern char _ramdisk_start[];\nextern char _ramdisk_end[];\n\nstatic struct BaseTables {\n\tuintptr_t l0_base[512];\n\tuintptr_t l1_high_gbs[512];\n\tuintptr_t l1_low_gbs[512];\n\tuintptr_t l2_kernel[512];\n} _baseTables __attribute__((aligned(4096)));\n\n#define PTE_VALID      (1UL << 0)\n#define PTE_TABLE      (1UL << 1)\n\n/* Table attributes */\n#define PTE_NSTABLE    (1UL << 63)\n#define PTE_APTABLE    (3UL << 61) /* two bits */\n#define  PTE_APTABLE_A (1UL << 62)\n#define  PTE_APTABLE_B (1UL << 61)\n#define PTE_UXNTABLE   (1UL << 60)\n#define PTE_PXNTABLE   (1UL << 59)\n\n/* Block attributes */\n#define PTE_UXN        (1UL << 54)\n#define PTE_PXN        (1UL << 53)\n#define PTE_CONTIGUOUS (1UL << 52)\n#define PTE_NG         (1UL << 11)\n#define PTE_AF         (1UL << 10)\n#define PTE_SH         (3UL << 8)  /* two bits */\n#define  PTE_SH_A      (1UL << 9)\n#define  PTE_SH_B      (1UL << 8)\n#define PTE_AP         (3UL << 6)  /* two bits */\n#define  PTE_AP_A      (1UL << 7)\n#define  PTE_AP_B      (1UL << 6)\n#define PTE_NS         (1UL << 5)\n#define PTE_ATTRINDX   (7UL << 2) /* three bits */\n#define  PTE_ATTR_A    (1UL << 4)\n#define  PTE_ATTR_B    (1UL << 3)\n#define  PTE_ATTR_C    (1UL << 2)\n\n\n#define KERNEL_PHYS_BASE 0x2000000UL\n\nstatic void bootstub_mmu_init(void) {\n\t/* Map memory */\n\t_baseTables.l0_base[0]   = (uintptr_t)&_baseTables.l1_low_gbs | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* equivalent to high_base_pml */\n\t_baseTables.l0_base[511] = (uintptr_t)&_baseTables.l1_high_gbs | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\t/* Mapping for us */\n\t_baseTables.l1_low_gbs[0] = 0x00000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t_baseTables.l1_low_gbs[1] = 0x40000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t_baseTables.l1_low_gbs[2] = 0x80000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t_baseTables.l1_low_gbs[3] = 0xc0000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\n\t/* -512GB is a map of 64GB of memory */\n\tfor (size_t i = 0; i < 64; ++i) {\n\t\t_baseTables.l1_high_gbs[i] = (i << 30) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t}\n\n\t/* -2GiB, map kernel here */\n\t_baseTables.l1_high_gbs[510] = (uintptr_t)&_baseTables.l2_kernel | PTE_VALID | PTE_TABLE | PTE_AF;\n\n\tfor (size_t i = 0; i < 512; ++i) {\n\t\t_baseTables.l2_kernel[i] = (KERNEL_PHYS_BASE + (i << 21)) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2);\n\t}\n\n\n\tuint64_t sctlr = 0\n\t\t| (1UL << 0)  /* mmu enabled */\n\t\t| (1UL << 2)  /* cachability */\n\t\t//| (1UL << 6)\n\t\t| (1UL << 12) /* instruction cachability */\n\t\t| (1UL << 23) /* SPAN */\n\t\t| (1UL << 28) /* nTLSMD */\n\t\t| (1UL << 29) /* LSMAOE */\n\t\t| (1UL << 20) /* TSCXT */\n\t\t| (1UL << 7)  /* ITD */\n\t;\n\n\t/* Translate control register */\n\tuint64_t tcr = 0\n\t\t|  (3UL << 32)  /* 36 bits? */\n\t\t|  (2UL << 30) /* TG1 4KB granules in TTBR1 */\n\t\t| (16UL << 16) /* T1SZ 48-bit */\n\t\t|  (3UL << 28)  /* SH1 */\n\t\t|  (1UL << 26)  /* ORGN1 */\n\t\t|  (1UL << 24)  /* IRGN1 */\n\t\t|  (0UL << 14) /* TG0 4KB granules in TTBR0 */\n\t\t| (16UL <<  0)  /* T0SZ 48-bit */\n\t\t|  (3UL << 12)  /* SH0 */\n\t\t|  (1UL << 10)  /* ORGN0 */\n\t\t|  (1UL <<  8)   /* IRGN0 */\n\t;\n\n\t/* MAIR setup? */\n\tuint64_t mair  = (0x000000000044ff00);\n\tasm volatile (\"msr MAIR_EL1,%0\" :: \"r\"(mair));\n\n\t/* Frob bits */\n\tprintf(\"bootstub: setting base values\\n\");\n\tasm volatile (\"msr TCR_EL1,%0\" : : \"r\"(tcr));\n\tasm volatile (\"msr TTBR0_EL1,%0\" : : \"r\"(&_baseTables.l0_base));\n\tasm volatile (\"msr TTBR1_EL1,%0\" : : \"r\"(&_baseTables.l0_base));\n\tprintf(\"bootstub: frobbing bits\\n\");\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\tprintf(\"bootstub: enabling mmu\\n\");\n\tasm volatile (\"msr SCTLR_EL1,%0\" : : \"r\"(sctlr));\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tprintf(\"bootstub: MMU initialized\\n\");\n\n}\n\nstatic void bootstub_load_kernel(Elf64_Header * header) {\n\t/* Find load headers */\n\tfor (int i = 0; i < header->e_phnum; ++i) {\n\t\tElf64_Phdr * phdr = (void*)((uintptr_t)header + (header->e_phoff + header->e_phentsize * i));\n\t\tif (phdr->p_type == PT_LOAD) {\n\t\t\tprintf(\"bootstub: Load %zu bytes @ %zx from off %zx\\n\", phdr->p_memsz, phdr->p_vaddr, phdr->p_offset);\n\t\t\tmemset((void*)phdr->p_vaddr, 0, phdr->p_memsz);\n\t\t\tmemcpy((void*)phdr->p_vaddr, (void*)((uintptr_t)header + phdr->p_offset), phdr->p_filesz);\n\t\t} else {\n\t\t\tprintf(\"bootstub: Skip phdr %d\\n\", i);\n\t\t}\n\t}\n}\n\nstruct rpitag tag_data = {0};\n\nstatic void bootstub_start_kernel(uintptr_t dtb, Elf64_Header * header) {\n\tprintf(\"bootstub: Jump to kernel entry point at %zx\\n\",\n\t\theader->e_entry);\n\n\tvoid (*entry)(uintptr_t,uintptr_t,uintptr_t) = (void(*)(uintptr_t,uintptr_t,uintptr_t))header->e_entry;\n\tentry(dtb, KERNEL_PHYS_BASE, (uintptr_t)&tag_data);\n}\n\nstatic void bootstub_exit_el2(void) {\n\tuint64_t spsr_el2, sctlr_el1;\n\tasm volatile (\"mrs %0, SPSR_EL2\\n\" :\"=r\"(spsr_el2));\n\tprintf(\"bootstub: SPSR_EL2=%#zx\\n\", spsr_el2);\n\n\tasm volatile (\"mrs %0, SCTLR_EL1\\n\" :\"=r\"(sctlr_el1));\n\tprintf(\"bootstub: SCTLR_EL1=%#zx\\n\", sctlr_el1);\n\n\t/* get us out of EL2 */\n\n\tasm volatile (\n\t\t\"ldr x0, =0x1004\\n\"\n\t\t\"mrs x1, SCTLR_EL2\\n\"\n\t\t\"orr x1, x1, x0\\n\"\n\t\t\"msr SCTLR_EL2, x1\\n\"\n\t\t\"ldr x0, =0x30d01804\\n\"\n\t\t\"msr SCTLR_EL1, x0\\n\" ::: \"x0\", \"x1\");\n\n\tprintf(\"bootstub: sctlr_el1 set\\n\");\n\n\tasm volatile (\n\t\t\"ldr x0, =0x80000000\\n\"\n\t\t\"msr HCR_EL2, x0\\n\" ::: \"x0\");\n\n\tprintf(\"bootstub: hcr set\\n\");\n\n\t#if 0\n\tasm volatile (\n\t\t\"ldr x0, =0x431\\n\"\n\t\t\"msr SCR_EL3, x0\\n\" ::: \"x0\");\n\tprintf(\"bootstub: SCR_EL3 set\\n\");\n\t#endif\n\n\tasm volatile (\n\t\t\"ldr x0, =0x3c5\\n\"\n\t\t\"msr SPSR_EL2, x0\\n\" ::: \"x0\");\n\n\tprintf(\"bootstub: spsr_el2 set\\n\");\n\n\tasm volatile (\n\t\t\"mov x0, sp\\n\"\n\t\t\"msr SP_EL1, x0\\n\"\n\t\t\"adr x0, in_el1\\n\"\n\t\t\"msr ELR_EL2, x0\\n\"\n\t\t\"eret\\n\"\n\t\t\"in_el1:\\n\"\n\t\t::: \"x0\", \"memory\", \"cc\"\n\t);\n\n\tprintf(\"bootstub: out of EL2?\\n\");\n\n\tuint64_t CurrentEL;\n\tasm volatile (\"mrs %0, CurrentEL\" : \"=r\"(CurrentEL));\n\tprintf(\"in el%zu\\n\", CurrentEL >> 2);\n}\n\nvoid kmain(uint32_t dtb_address, uint32_t base_addr) {\n\n\tif (rpi_fb_init()) {\n\t\t/* Panic */\n\t\twhile (1);\n\t}\n\n\tprintf(\"rpi4 bootstub, kernel base address is %#x, dtb is at %#x\\n\", base_addr, dtb_address);\n\n\tprintf(\"framebuffer (%u x %u) @ %#zx\\n\",\n\t\tlfb_resolution_x,\n\t\tlfb_resolution_y,\n\t\t(uintptr_t)lfb_vid_memory);\n\n\tuint64_t CurrentEL;\n\tasm volatile (\"mrs %0, CurrentEL\" : \"=r\"(CurrentEL));\n\tprintf(\"in el%zu\\n\", CurrentEL >> 2);\n\n\tprintf(\"kernel @ %#zx (%zu bytes) ramdisk @ %#zx (%zu bytes)\\n\",\n\t\t(uintptr_t)&_kernel_start,\n\t\t(size_t)((uintptr_t)&_kernel_end - (uintptr_t)&_kernel_start),\n\t\t(uintptr_t)&_ramdisk_start,\n\t\t(size_t)((uintptr_t)&_ramdisk_end - (uintptr_t)&_ramdisk_start));\n\n\trpi_cpu_freq();\n\n\tbootstub_exit_el2();\n\n\tbootstub_mmu_init();\n\n\ttag_data.phys_addr = (uint32_t)(uintptr_t)lfb_vid_memory;\n\ttag_data.x = lfb_resolution_x;\n\ttag_data.y = lfb_resolution_y;\n\ttag_data.s = lfb_resolution_s;\n\ttag_data.b = lfb_resolution_b;\n\ttag_data.size = lfb_memsize;\n\ttag_data.ramdisk_start = (uintptr_t)&_ramdisk_start;\n\ttag_data.ramdisk_end   = (uintptr_t)&_ramdisk_end;\n\n\tElf64_Header *header = (void*)&_kernel_start;\n\tbootstub_load_kernel(header);\n\n\t/* Jump to kernel */\n\tbootstub_start_kernel(dtb_address, header);\n\n\twhile (1);\n\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi400/start.S",
    "content": ".extern __bootstrap_stack_top\n.extern __bss_start\n.extern __bss_size\n\n.section \".bootstrap\"\n.globl start\nstart:\n    ldr x30, =__bootstrap_stack_top\n    mov x1, x4\n    mov sp, x30\n    ldr x5, =__bss_start\n    ldr w6, =__bss_size\n3:\n    cbz w6, 4f\n    str xzr, [x5], #8\n    sub w6, w6, #1\n    cbnz w6, 3b\n4:\n    bl kmain\nhang:\n    b hang\n\n.section \".rodata\"\n.align 12\n.globl _kernel_start\n_kernel_start:\n.incbin \"misaka-kernel\"\n.globl _kernel_end\n_kernel_end:\n\n.align 12\n.globl _ramdisk_start\n_ramdisk_start:\n.incbin \"ramdisk.igz\"\n.global _ramdisk_end\n_ramdisk_end:\n"
  },
  {
    "path": "kernel/arch/aarch64/rpi_miniuart.c",
    "content": "/**\n * @file  kernel/arch/aarch64/rpi_miniuart.c\n * @brief Rudimentary serial driver for the rpi's miniuart\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/process.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/pty.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/aarch64/dtb.h>\n#include <kernel/arch/aarch64/gic.h>\n\n#define UART_BAUD 921600\n//#define UART_BAUD 115200\n\n\nstatic uintptr_t gpio_base = 0;\n\nstatic uint32_t mmio_read(uintptr_t addr) {\n\tuint32_t res = *((volatile uint32_t*)(addr));\n\treturn res;\n}\nstatic void mmio_write(uintptr_t addr, uint32_t val) {\n\t(*((volatile uint32_t*)(addr))) = val;\n}\n\n/**\n * GPIO initialization mostly from Adam Greenwood-Byrne's rpi4-osdev\n */\n#define PERI_BASE 0xFE000000\n#define GPIO_BASE (PERI_BASE + 0x200000)\n#define GPFSEL0   (0x00)\n#define GPSET0    (0x1c)\n#define GPCLR0    (0x28)\n#define GPPUPPDN0 (0xe4)\n\n/**\n * AUX register offsets from the peripheral manual\n */\n#define AUX_IRQ          0x00\n#define AUX_ENABLES      0x04\n#define AUX_MU_IO_REG    0x40\n#define AUX_MU_IER_REG   0x44\n#define AUX_MU_IIR_REG   0x48\n#define AUX_MU_LCR_REG   0x4c\n#define AUX_MU_MCR_REG   0x50\n#define AUX_MU_LSR_REG   0x54\n#define AUX_MU_CNTL_REG  0x60\n#define AUX_MU_BAUD_REG  0x68\n\n#define BAUD_CALC(rate) ((500000000UL/(rate*8))-1)\n\nstatic int gpio_call(uint32_t pin, uint32_t value, uint32_t base, uint32_t field_size, uint32_t field_max) {\n\tuint32_t mask = (1 << field_size) - 1;\n\tif (pin > field_max) return 0;\n\tif (value > mask) return 0;\n\n\tuint32_t fields = 32 / field_size;\n\tuint32_t reg    = base + (pin / fields) * 4;\n\tuint32_t shift  = (pin % fields) * field_size;\n\tuint32_t cur    = mmio_read(gpio_base + reg);\n\tcur &= ~(mask << shift);\n\tcur |= (value << shift);\n\tmmio_write(gpio_base + reg, cur);\n\n\treturn 1;\n}\n\nstatic int miniuart_irq(process_t * this, int irq, void * data) {\n\tuintptr_t uart_mapped = (uintptr_t)data;\n\tasm volatile (\"dmb sy\" ::: \"memory\");\n\tuint32_t aux_cause = mmio_read(uart_mapped + AUX_IRQ);\n\tif (aux_cause & 1) {\n\t\tuint32_t uart_iir =  mmio_read(uart_mapped + AUX_MU_IIR_REG);\n\t\tif (uart_iir & (1 << 2)) {\n\t\t\tmake_process_ready(this);\n\t\t}\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void miniuart_fill_name(pty_t * pty, char * name) {\n\tsnprintf(name, 100, \"/dev/ttyUART1\");\n}\n\nstatic void miniuart_write_out(pty_t * pty, uint8_t c) {\n\tuintptr_t uart_mapped = (uintptr_t)pty->_private;\n\twhile (!(mmio_read(uart_mapped + AUX_MU_LSR_REG) & 0x20));\n\tmmio_write(uart_mapped + AUX_MU_IO_REG, (uint8_t)c);\n}\n\nstatic void miniuart_thread(void * arg) {\n\tuintptr_t uart_mapped = (uintptr_t)arg;\n\n\tgic_assign_interrupt(0x5D, miniuart_irq, (void*)uart_mapped);\n\n\tmmio_write(uart_mapped + AUX_ENABLES,     1); /* Enable mini uart */\n\tmmio_write(uart_mapped + AUX_MU_IER_REG,  0); /* Disable interrupts while we set up */\n\tmmio_write(uart_mapped + AUX_MU_CNTL_REG, 0); /* Disable transmit/receive */\n\tmmio_write(uart_mapped + AUX_MU_LCR_REG,  3); /* 8-bit output (XXX shouldn't this just be '1'?) */\n\tmmio_write(uart_mapped + AUX_MU_MCR_REG,  0); /* RTS is high */\n\tmmio_write(uart_mapped + AUX_MU_IER_REG,  0); /* Disable interrupts again? */\n\tmmio_write(uart_mapped + AUX_MU_IIR_REG,  0xC6); /* ack and clear interrupts */\n\tmmio_write(uart_mapped + AUX_MU_BAUD_REG, BAUD_CALC(UART_BAUD));\n\n\tasm volatile (\"dmb sy\" ::: \"memory\");\n\n\tgpio_call(14, 0, GPPUPPDN0, 2, 53);\n\tgpio_call(14, 2, GPFSEL0, 3, 53);\n\tgpio_call(15, 0, GPPUPPDN0, 2, 53);\n\tgpio_call(15, 2, GPFSEL0, 3, 53);\n\n\tasm volatile (\"dmb sy\" ::: \"memory\");\n\n\tmmio_write(uart_mapped + AUX_MU_CNTL_REG, 3); /* tx, rx enable */\n\n\tpty_t * pty = pty_new(NULL, 0);\n\tpty->write_out = miniuart_write_out;\n\tpty->fill_name = miniuart_fill_name;\n\tpty->slave->gid = 2; /* dialout group */\n\tpty->slave->mask = 0660;\n\tpty->_private = arg;\n\tpty->tios.c_cflag = CREAD | CS8 | B921600;\n\tvfs_mount(\"/dev/ttyUART1\", pty->slave, \"rpiminiuart\", \"\");\n\n\t/* Enable interrupts */\n\tmmio_write(uart_mapped + AUX_MU_IER_REG, 1); /* enable receive interrupt */\n\tmmio_write(uart_mapped + AUX_MU_IIR_REG, 0xC6); /* ack and clear interrupts */\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\t/* Handle incoming data */\n\twhile (1) {\n\t\twhile (!(mmio_read(uart_mapped + AUX_MU_LSR_REG) & 0x01)) {\n\t\t\tswitch_task(0);\n\t\t}\n\n\t\tuint8_t rx = mmio_read(uart_mapped + AUX_MU_IO_REG);\n\t\ttty_input_process(pty, rx);\n\t}\n}\n\nvoid miniuart_start(void) {\n\tgpio_base = (uintptr_t)mmu_map_mmio_region(GPIO_BASE, 0x1000);\n\tvoid * uart_mapped = mmu_map_mmio_region(0xFE215000, 0x1000);\n\n\tspawn_worker_thread(miniuart_thread, \"[miniuart]\", uart_mapped);\n}\n\n"
  },
  {
    "path": "kernel/arch/aarch64/smp.c",
    "content": "/**\n * @file  kernel/arch/aarch64/smp.c\n * @brief Routines for locating and starting other CPUs.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/mmu.h>\n#include <kernel/time.h>\n\n#include <sys/ptrace.h>\n\n#include <kernel/arch/aarch64/regs.h>\n#include <kernel/arch/aarch64/dtb.h>\n\nextern process_t * spawn_kidle(int);\nextern void timer_start(void);\nextern void aarch64_processor_data(void);\n\nstatic uint32_t cpu_on = 0;\nstatic int method = 0;\n\nstatic volatile uint32_t _smp_mutex = 0;\n\nvolatile uintptr_t aarch64_jmp_target = 0;\nvolatile uint64_t  aarch64_sctlr      = 0;\nvolatile uint64_t  aarch64_tcr        = 0;\nvolatile uint64_t  aarch64_mair       = 0;\nvolatile uint64_t  aarch64_vbar       = 0;\nvolatile uintptr_t aarch64_ttbr0      = 0;\nvolatile uintptr_t aarch64_ttbr1      = 0;\nvolatile uintptr_t aarch64_stack      = 0;\n\nvoid ap_start(uint64_t core_id) {\n\n\tdprintf(\"smp: core %zu is online\\n\", core_id);\n\n\textern void arch_set_core_base(uintptr_t base);\n\tarch_set_core_base((uintptr_t)&processor_local_data[core_id]);\n\n\tthis_core->cpu_id = core_id;\n\n\textern void fpu_enable(void);\n\tfpu_enable();\n\n\taarch64_processor_data();\n\n\tthis_core->current_pml = mmu_get_kernel_directory();\n\tthis_core->kernel_idle_task = spawn_kidle(0);\n\tthis_core->current_process = this_core->kernel_idle_task;\n\tasm volatile (\"isb\");\n\n\ttimer_start();\n\n\t_smp_mutex = 1;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tswitch_next();\n}\n\nvoid smp_bootstrap(void) {\n\tasm volatile (\n\t\t/* Store x0, which is our core ID */\n\t\t\"mov x3, x0\\n\"\n\t\t/* Set up TTBR1 with high memory directory */\n\t\t\"ldr x0, aarch64_ttbr1\\n\"\n\t\t\"msr TTBR1_EL1, x0\\n\"\n\t\t/* Load stack pointer */\n\t\t\"ldr x0, aarch64_stack\\n\"\n\t\t\"mov sp, x0\\n\"\n\t\t/* Set up TTBR0 with our temporary directory */\n\t\t\"ldr x0, aarch64_ttbr0\\n\"\n\t\t\"msr TTBR0_EL1, x0\\n\"\n\t\t\"dsb ishst\\n\"\n\t\t\"tlbi vmalle1is\\n\"\n\t\t\"dsb ish\\n\"\n\t\t\"isb\\n\"\n\t\t/* Load VBAR from first core */\n\t\t\"ldr x0, aarch64_vbar\\n\"\n\t\t\"msr VBAR_EL1, x0\\n\"\n\t\t/* Load MAIR from first core */\n\t\t\"ldr x0, aarch64_mair\\n\"\n\t\t\"msr MAIR_EL1, x0\\n\"\n\t\t/* Load TCR from first core */\n\t\t\"ldr x0, aarch64_tcr\\n\"\n\t\t\"msr TCR_EL1, x0\\n\"\n\t\t/* Load SCTLR from first core, enable mmu */\n\t\t\"ldr x0, aarch64_sctlr\\n\"\n\t\t\"ldr x1, aarch64_jmp_target\\n\"\n\t\t\"msr SCTLR_EL1, x0\\n\"\n\t\t\"isb\\n\"\n\t\t/* Restore core ID as argument */\n\t\t\"mov x0, x3\\n\"\n\t\t/* Jump to C entrypoint */\n\t\t\"br x1\\n\");\n\t__builtin_unreachable();\n}\n\nstatic void start_cpu(uint32_t * node) {\n\tuint32_t * cpuid = dtb_node_find_property(node, \"reg\");\n\tif (!cpuid) {\n\t\tdprintf(\"smp: skipping cpus node without 'reg' parameter\\n\");\n\t\treturn;\n\t}\n\tuint32_t num = swizzle(cpuid[2]);\n\tdprintf(\"smp: cpu node %d %#zx '%s'\\n\", num, (uintptr_t)node, (char *)(node));\n\tif (num == 0) return;\n\n\tif (method == 0x637668) {\n\t\t_smp_mutex = 0;\n\t\taarch64_stack = (uintptr_t)sbrk(4096) + 4096;\n\t\tuint64_t x0 = cpu_on;\n\t\tuint64_t x1 = num;\n\t\tuint64_t x2 = mmu_map_to_physical(NULL, (uintptr_t)&smp_bootstrap);\n\t\tuint64_t x3 = num;\n\t\tasm volatile (\"dc civac, %0\\ndsb sy\" :: \"r\"(&aarch64_stack) : \"memory\");\n\t\tasm volatile (\"isb\" ::: \"memory\");\n\n\t\tasm volatile (\n\t\t\t\"mov x0, %0\\n\"\n\t\t\t\"mov x1, %1\\n\"\n\t\t\t\"mov x2, %2\\n\"\n\t\t\t\"mov x3, %3\\n\"\n\t\t\t\"hvc 0\" :: \"r\"(x0), \"r\"(x1), \"r\"(x2), \"r\"(x3) : \"x0\",\"x1\",\"x2\",\"x3\");\n\t\twhile (_smp_mutex == 0);\n\n\t\tprocessor_count = num + 1;\n\t} else {\n\t\tdprintf(\"smp: Don't know how to turn on with '%#x'\\n\", method);\n\t\t/* smc? */\n\t}\n}\n\n#define _pagemap __attribute__((aligned(4096))) = {0}\nstatic union PML startup_ttbr0[2][512] _pagemap;\n\nvoid aarch64_smp_start(void) {\n\n\tuint32_t * psci = dtb_find_node(\"psci\");\n\n\tif (!psci) {\n\t\tdprintf(\"smp: no 'psci' interface node\\n\");\n\t\treturn;\n\t}\n\n\tuint32_t * psci_method = dtb_node_find_property(psci, \"method\");\n\tuint32_t * psci_cpu_on = dtb_node_find_property(psci, \"cpu_on\");\n\n\tif (!psci_method || !psci_cpu_on) {\n\t\tdprintf(\"smp: don't know how to turn on these cores\\n\");\n\t\treturn;\n\t}\n\n\tdprintf(\"smp: startup method is '0x%x'\\n\", psci_method[2]);\n\tmethod = psci_method[2];\n\tcpu_on = swizzle(psci_cpu_on[2]);\n\n\tuint32_t * cpus = dtb_find_node(\"cpus\");\n\tif (!cpus) {\n\t\tdprintf(\"smp: no 'cpus' node\\n\");\n\t\treturn;\n\t}\n\n\taarch64_jmp_target = (uintptr_t)ap_start;\n\tasm volatile (\"mrs %0, MAIR_EL1\"  : \"=r\"(aarch64_mair));\n\tasm volatile (\"mrs %0, TCR_EL1\"   : \"=r\"(aarch64_tcr));\n\tasm volatile (\"mrs %0, SCTLR_EL1\" : \"=r\"(aarch64_sctlr));\n\tasm volatile (\"mrs %0, VBAR_EL1\"  : \"=r\"(aarch64_vbar));\n\n\tstartup_ttbr0[0][0].raw = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[1]) | (0x3) | (1 << 10);\n\tfor (long i = 0; i < 512; ++i) {\n\t\tstartup_ttbr0[1][i].raw = (i << 30) | (1 << 2) | 1 | (1 << 10);\n\t}\n\n\taarch64_ttbr0 = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[0]);\n\taarch64_ttbr1 = mmu_map_to_physical(NULL, (uintptr_t)mmu_get_kernel_directory());\n\n\tasm volatile (\n\t\t\"dsb ishst\\n\"\n\t\t\"tlbi vmalle1is\\n\"\n\t\t\"dsb ish\\n\"\n\t\t\"isb\\n\"\n\t);\n\n\tdtb_callback_direct_children(cpus, start_cpu);\n}\n\n\nvoid rpi_smp_exit_el2(void) {\n\tasm volatile (\n\t\t\"ldr x0, =0x1004\\n\"\n\t\t\"mrs x1, SCTLR_EL2\\n\"\n\t\t\"orr x1, x1, x0\\n\"\n\t\t\"msr SCTLR_EL2, x1\\n\"\n\t\t\"ldr x0, =0x30d01804\\n\"\n\t\t\"msr SCTLR_EL1, x0\\n\"\n\t\t\"ldr x0, =0x80000000\\n\"\n\t\t\"msr HCR_EL2, x0\\n\"\n\t\t\"ldr x0, =0x3c5\\n\"\n\t\t\"msr SPSR_EL2, x0\\n\"\n\t\t\"adr x0, smp_bootstrap\\n\"\n\t\t\"msr ELR_EL2, x0\\n\"\n\t\t\"mov x0, x6\\n\"\n\t\t\"eret\\n\"\n\t\t::: \"x0\", \"x1\");\n\t__builtin_unreachable();\n}\n\nvoid rpi_smp_init(void) {\n\taarch64_jmp_target = (uintptr_t)ap_start;\n\tasm volatile (\"mrs %0, MAIR_EL1\"  : \"=r\"(aarch64_mair));\n\tasm volatile (\"mrs %0, TCR_EL1\"   : \"=r\"(aarch64_tcr));\n\tasm volatile (\"mrs %0, SCTLR_EL1\" : \"=r\"(aarch64_sctlr));\n\tasm volatile (\"mrs %0, VBAR_EL1\"  : \"=r\"(aarch64_vbar));\n\tstartup_ttbr0[0][0].raw = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[1]) | (0x3) | (1 << 10);\n\tfor (long i = 0; i < 512; ++i) {\n\t\tstartup_ttbr0[1][i].raw = (i << 30) | (2 << 2) | 1 | (1 << 10);\n\t}\n\taarch64_ttbr0 = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[0]);\n\taarch64_ttbr1 = mmu_map_to_physical(NULL, (uintptr_t)mmu_get_kernel_directory());\n\tasm volatile (\n\t\t\"dsb ishst\\n\"\n\t\t\"tlbi vmalle1is\\n\"\n\t\t\"dsb ish\\n\"\n\t\t\"isb\\n\"\n\t);\n\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_jmp_target));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_mair));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_tcr));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_sctlr));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_vbar));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_ttbr0));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_ttbr1));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&startup_ttbr0[0]));\n\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&startup_ttbr0[1]));\n\n\tuintptr_t spinners[] = {0xd8, 0xe0, 0xe8, 0xf0};\n\tuintptr_t low_mem = (uintptr_t)mmu_map_mmio_region(0, 0x1000);\n\tunion PML * p = mmu_get_page(low_mem, 0);\n\tp->bits.page = 0;\n\tasm volatile (\"dsb ishst\\ntlbi vmalle1is\\ndsb ish\\nisb\" ::: \"memory\");\n\n\tdprintf(\"smp: zero page mapped at %#zx, page is %#zx\\n\",\n\t\tlow_mem, mmu_map_to_physical(mmu_get_kernel_directory(), low_mem));\n\n\tfor (int i = 1; i < 4; ++i) {\n\t\t_smp_mutex = 0;\n\t\taarch64_stack = (uintptr_t)sbrk(4096) + 4096;\n\t\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"(&aarch64_stack));\n\t\tuintptr_t target = mmu_map_to_physical(NULL, (uintptr_t)&rpi_smp_exit_el2);\n\t\t*(volatile uintptr_t*)(low_mem + spinners[i]) = target;\n\t\tasm volatile (\"dmb sy\\nisb\\ndc cvac, %0\\nisb\\nsev\" :: \"r\"(low_mem) : \"memory\");\n\n\t\twhile (_smp_mutex == 0);\n\n\t\tprocessor_count = i + 1;\n\t}\n}\n"
  },
  {
    "path": "kernel/arch/aarch64/traceback.c",
    "content": "/**\n * @file  kernel/arch/aarch64/traceback.c\n * @brief Kernel fault traceback generator.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/mmu.h>\n#include <kernel/ksym.h>\n\n#include <kernel/arch/aarch64/regs.h>\n\nextern char end[];\n\nstatic uintptr_t matching_symbol(uintptr_t ip, char ** name) {\n\thashmap_t * symbols = ksym_get_map();\n\tuintptr_t best_match = 0;\n\tfor (size_t i = 0; i < symbols->size; ++i) {\n\t\thashmap_entry_t * x = symbols->entries[i];\n\t\twhile (x) {\n\t\t\tvoid* sym_addr = x->value;\n\t\t\tchar* sym_name = x->key;\n\t\t\tif ((uintptr_t)sym_addr < ip && (uintptr_t)sym_addr > best_match) {\n\t\t\t\tbest_match = (uintptr_t)sym_addr;\n\t\t\t\t*name = sym_name;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t}\n\t}\n\treturn best_match;\n}\nstatic int validate_pointer(uintptr_t base, size_t size) {\n\tuintptr_t end  = size ? (base + (size - 1)) : base;\n\tuintptr_t page_base = base >> 12;\n\tuintptr_t page_end  =  end >> 12;\n\tfor (uintptr_t page = page_base; page <= page_end; ++page) {\n\t\tif ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0;\n\t\tunion PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12);\n\t\tif (!page_entry) return 0;\n\t\tif (!page_entry->bits.present) return 0;\n\t}\n\treturn 1;\n}\n\nstatic void dump_traceback(uintptr_t ip, uintptr_t bp, uintptr_t x30) {\n\tint depth = 0;\n\tint max_depth = 20;\n\n\twhile (bp && ip && depth < max_depth) {\n\t\tdprintf(\" 0x%016zx \", ip);\n\t\t#if 0\n\t\tif (ip >= 0xffffffff80000000UL) {\n\t\t\tchar * name = NULL;\n\t\t\tstruct LoadedModule * mod = find_module(ip, &name);\n\t\t\tif (mod) {\n\t\t\t\tdprintf(\"\\a in module '%s', base address %#zx (offset %#zx)\\n\",\n\t\t\t\t\tname, mod->baseAddress, ip - mod->baseAddress);\n\t\t\t} else {\n\t\t\t\tdprintf(\"\\a (unknown)\\n\");\n\t\t\t}\n\t\t#else\n\t\tif (0) {\n\t\t#endif\n\t\t} else if (ip <= 0x800000000000) {\n\t\t\tdprintf(\"\\a in userspace\\n\");\n\t\t} else if (ip >= 0xffffffff80000000UL && ip <= (uintptr_t)&end) {\n\t\t\t/* Find symbol match */\n\t\t\tchar * name;\n\t\t\tuintptr_t addr = matching_symbol(ip, &name);\n\t\t\tif (!addr) {\n\t\t\t\tdprintf(\"\\a (no match)\\n\");\n\t\t\t} else {\n\t\t\t\tdprintf(\"\\a %s+0x%zx\\n\", name, ip-addr);\n\t\t\t}\n\t\t} else {\n\t\t\tdprintf(\"\\a (unknown)\\n\");\n\t\t}\n\t\tif (!validate_pointer(bp, sizeof(uintptr_t)) || !validate_pointer(bp + sizeof(uintptr_t), sizeof(uintptr_t))) {\n\t\t\tbreak;\n\t\t}\n\t\tif (depth == 0) {\n\t\t\tip = x30;\n\t\t} else {\n\t\t\tip = *(uintptr_t*)(bp + sizeof(uintptr_t));\n\t\t\tbp = *(uintptr_t*)(bp);\n\t\t}\n\t\tdepth++;\n\t}\n}\n\nvoid aarch64_safe_dump_traceback(uintptr_t elr, struct regs * r) {\n\tdump_traceback(elr, r->x29, r->x30);\n}\n\n\n/* We need to pull the frame address from the caller or this isn't going work, but\n * gcc is going to warn that that is unsafe... */\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wframe-address\"\n\n/**\n * @brief Display a traceback from the current call context.\n */\nvoid arch_dump_traceback(void) {\n\tdump_traceback((uintptr_t)arch_dump_traceback+1, (uintptr_t)__builtin_frame_address(1), (uintptr_t)__builtin_return_address(0));\n}\n\n#pragma GCC diagnostic pop\n"
  },
  {
    "path": "kernel/arch/aarch64/virtio.c",
    "content": "/**\n * @file  kernel/arch/aarch64/virtio.c\n * @brief Rudimentary, hacky implementations of virtio input devices.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n#include <kernel/pci.h>\n#include <kernel/pipe.h>\n#include <kernel/video.h>\n#include <kernel/mouse.h>\n#include <kernel/time.h>\n\n#include <kernel/arch/aarch64/gic.h>\n\nstatic fs_node_t * mouse_pipe;\nstatic fs_node_t * vmmouse_pipe;\nstatic fs_node_t * keyboard_pipe;\n\nstruct virtio_device_cfg {\n\tvolatile uint8_t select;\n\tvolatile uint8_t subsel;\n\tvolatile uint8_t size;\n\tvolatile uint8_t pad[5];\n\tunion {\n\t\tstruct {\n\t\t\tvolatile uint32_t min;\n\t\t\tvolatile uint32_t max;\n\t\t\tvolatile uint32_t fuzz;\n\t\t\tvolatile uint32_t flat;\n\t\t\tvolatile uint32_t res;\n\t\t} tablet_data;\n\t\tuint8_t str[128];\n\t} data;\n};\n\nstruct virtio_common_cfg {\n\tvolatile uint32_t dev_feature_select;\n\tvolatile uint32_t dev_feature;\n\tvolatile uint32_t guest_feature_select;\n\tvolatile uint32_t guest_feature;\n\tvolatile uint16_t msix;\n\tvolatile uint16_t queues;\n\tvolatile uint8_t  device_status;\n\tvolatile uint8_t  config_generation;\n\n\tvolatile uint16_t queue_select;\n\tvolatile uint16_t queue_size;\n\tvolatile uint16_t queue_msix_vector;\n\tvolatile uint16_t queue_enable;\n\tvolatile uint16_t queue_notify_off;\n\t/* queue stuff */\n\n\tvolatile uint64_t queue_desc;\n\tvolatile uint64_t queue_avail;\n\tvolatile uint64_t queue_used;\n};\n\nstruct virtio_buffer {\n\tuint64_t  addr;\n\tuint32_t  length;\n\tuint16_t  flags;\n\tuint16_t  next;\n};\n\nstruct virtio_avail {\n\tuint16_t flags;\n\tvolatile uint16_t index;\n\tuint16_t ring[64];\n\tuint16_t int_index;\n};\n\nstruct virtio_ring {\n\tuint32_t index;\n\tuint32_t length;\n};\n\nstruct virtio_used {\n\tuint16_t flags;\n\tvolatile uint16_t index;\n\tstruct virtio_ring ring[64];\n\tuint16_t int_index;\n};\n\nstruct virtio_queue {\n\tstruct virtio_buffer buffers[64];\n\tstruct virtio_avail  available;\n\tstruct virtio_used   used;\n};\n\nstruct virtio_input_event {\n\tuint16_t type;\n\tuint16_t code;\n\tuint32_t value;\n};\n\nint virtio_tablet_responder(process_t * this, int irq, void * data) {\n\tuint8_t cause = *(volatile uint8_t *)data;\n\tif (cause == 1) {\n\t\tmake_process_ready(this);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nint virtio_keyboard_responder(process_t * this, int irq, void * data) {\n\tuint8_t cause = *(volatile uint8_t *)data;\n\tif (cause == 1) {\n\t\tmake_process_ready(this);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic void try_to_get_boot_processor(void) {\n\t/* We would prefer these virtio startup processes run on the boot CPU, but\n\t * it's not the end of the world if they don't. If we're not on the boot\n\t * CPU, yield for a bit to try to get on it... */\n\tuint64_t expire = arch_perf_timer() + 100000UL * arch_cpu_mhz();\n\twhile (this_core->cpu_id != 0) {\n\t\tif (arch_perf_timer() >= expire) break;\n\t\tswitch_task(1);\n\t}\n}\n\nstatic void virtio_tablet_thread(void * data) {\n\ttry_to_get_boot_processor();\n\n\tuint32_t device = (uintptr_t)data;\n\tuintptr_t t = 0x12000000;\n\tpci_write_field(device, PCI_BAR4, 4, t|8);\n\tpci_write_field(device, PCI_COMMAND, 2, 4|2|1);\n\n\tstruct virtio_device_cfg * cfg = (void*)((char*)mmu_map_mmio_region(t + 0x2000, 0x1000));\n\tcfg->select = 1; /* ask for name */\n\tcfg->subsel = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tdprintf(\"virtio: found '%s'\\n\", cfg->data.str);\n\n\tvoid * irq_region = mmu_map_mmio_region(t + 0x1000, 0x1000);\n\tint irq;\n\tgic_map_pci_interrupt(\"virtio-tablet\", device, &irq, virtio_tablet_responder, irq_region);\n\tdprintf(\"virtio-tablet: irq is %d\\n\", irq);\n\n\t/* figure out range values */\n\tcfg->select = 0x12;\n\tcfg->subsel = 0; /* X */\n\tasm volatile (\"isb\" ::: \"memory\");\n\tuint32_t max_x = cfg->data.tablet_data.max;\n\tcfg->select = 0x12;\n\tcfg->subsel = 1; /* X */\n\tasm volatile (\"isb\" ::: \"memory\");\n\tuint32_t max_y = cfg->data.tablet_data.max;\n\n\tdprintf(\"virtio: %d x %d max coordinates\\n\",\n\t\tmax_x, max_y);\n\n\tcfg->select = 0;\n\tcfg->subsel = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tstruct virtio_common_cfg * common = (void*)((char*)mmu_map_mmio_region(t, 0x1000));\n\n\tcommon->device_status = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tint queue_size = common->queue_size;\n\tdprintf(\"virtio: queue size is %u\\n\",\n\t\tqueue_size);\n\n\t/* get us one page */\n\tsize_t queue_phys = mmu_allocate_a_frame() << 12;\n\tstruct virtio_queue * queue = mmu_map_mmio_region(queue_phys, 4096);\n\tasm volatile (\"isb\" ::: \"memory\");\n\tmemset(queue, 0, sizeof(struct virtio_queue));\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tcommon->queue_select = 0;\n\tcommon->queue_desc = queue_phys;\n\tcommon->queue_avail = (queue_phys) + offsetof(struct virtio_queue, available);\n\tcommon->queue_used = (queue_phys) + offsetof(struct virtio_queue, used);\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tsize_t buffers_base = mmu_allocate_a_frame() << 12;\n\tvolatile struct virtio_input_event * buffers = mmu_map_mmio_region(buffers_base, 4096);\n\tmmu_get_page((uintptr_t)buffers, 0)->bits.attrindx = 2;\n\n\tfor (int i = 0; i < queue_size; ++i) {\n\t\tqueue->buffers[i].addr = buffers_base + i * sizeof(struct virtio_input_event);\n\t\tqueue->buffers[i].length = sizeof(struct virtio_input_event);\n\t\tqueue->buffers[i].flags = 2;\n\t\tqueue->buffers[i].next = 0;\n\t\tqueue->available.ring[i] = i;\n\t}\n\n\tqueue->available.index = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tcommon->queue_enable = 1;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tcommon->device_status = 4;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tuint16_t index = 0;\n\n\tuint32_t x = 0;\n\tuint32_t y = 0;\n\tint button_left = 0;\n\tint button_right = 0;\n\tint button_middle = 0;\n\tint button_scroll_down = 0;\n\tint button_scroll_up = 0;\n\n\tqueue->available.index = queue_size-1;\n\n\twhile (1) {\n\t\t/* Inform the device we have room */\n\t\twhile (queue->used.index == index) {\n\t\t\tswitch_task(0);\n\t\t\tasm volatile (\"dc ivac, %0\\ndsb sy\" :: \"r\"(&queue->used) : \"memory\");\n\t\t}\n\n\t\tuint16_t them = queue->used.index;\n\n\t\tfor (; index != them; index++) {\n\t\t\tasm volatile (\"dc ivac, %0\\ndsb sy\" :: \"r\"(&buffers[index%queue_size]) : \"memory\");\n\t\t\tstruct virtio_input_event evt = buffers[index%queue_size];\n\t\t\twhile (evt.type == 0xFF) {\n\t\t\t\tevt = buffers[index%queue_size];\n\t\t\t\tdprintf(\"virtio-tablet: bad packet %d (them=%d)\\n\", index, them);\n\t\t\t}\n\t\t\tbuffers[index%queue_size].type = 0xFF;\n\t\t\tasm volatile (\"isb\\ndsb sy\" :: \"r\"(buffers) : \"memory\");\n\t\t\tif (evt.type == 3) {\n\t\t\t\t/* movement */\n\t\t\t\tif (evt.code == 0) {\n\t\t\t\t\tx = (evt.value * lfb_resolution_x) / max_x;\n\t\t\t\t} else if (evt.code == 1) {\n\t\t\t\t\ty = (evt.value * lfb_resolution_y) / max_y;\n\t\t\t\t}\n\t\t\t} else if (evt.type == 1) {\n\t\t\t\t/* button */\n\t\t\t\tif (evt.code == 0x110) {\n\t\t\t\t\tbutton_left = evt.value;\n\t\t\t\t} else if (evt.code == 0x111) {\n\t\t\t\t\tbutton_right = evt.value;\n\t\t\t\t} else if (evt.code == 0x112) {\n\t\t\t\t\tbutton_middle = evt.value;\n\t\t\t\t} else if (evt.code == 0x150) {\n\t\t\t\t\tbutton_scroll_down = 1;\n\t\t\t\t} else if (evt.code == 0x151) {\n\t\t\t\t\tbutton_scroll_up = 1;\n\t\t\t\t}\n\n\t\t\t} else if (evt.type == 0) {\n#define DISCARD_POINT 32\n\t\t\t\tmouse_device_packet_t packet;\n\t\t\t\tpacket.magic = MOUSE_MAGIC;\n\t\t\t\tpacket.x_difference = x;\n\t\t\t\tpacket.y_difference = y;\n\t\t\t\tpacket.buttons =\n\t\t\t\t\t(button_left ? LEFT_CLICK : 0) |\n\t\t\t\t\t(button_right ? RIGHT_CLICK : 0) |\n\t\t\t\t\t(button_middle ? MIDDLE_CLICK : 0) |\n\t\t\t\t\t(button_scroll_down ? MOUSE_SCROLL_DOWN : 0) |\n\t\t\t\t\t(button_scroll_up ? MOUSE_SCROLL_UP : 0);\n\n\t\t\t\tbutton_scroll_down = 0;\n\t\t\t\tbutton_scroll_up = 0;\n\n\t\t\t\tmouse_device_packet_t bitbucket;\n\t\t\t\twhile (pipe_size(vmmouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {\n\t\t\t\t\tread_fs(vmmouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);\n\t\t\t\t}\n\t\t\t\twrite_fs(vmmouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);\n\t\t\t}\n\t\t\t//buffers[index%queue_size].type = 0xFF;\n\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\t\t\tqueue->available.index++;\n\t\t}\n\t}\n}\n\nstatic const uint8_t ext_key_map[256] = {\n\t[0x63] = 0x37, /* print screen */\n\t[0x66] = 0x47, /* home */\n\t[0x67] = 0x48, /* UP */\n\t[0x68] = 0x49, /* page up */\n\t[0x6c] = 0x50, /* DOWN */\n\t[0x69] = 0x4B, /* LEFT */\n\t[0x6a] = 0x4D, /* RIGHT */\n\t[0x6b] = 0x4F, /* end */\n\t[0x6d] = 0x51, /* page down */\n\t[0x7d] = 0x5b, /* left super */\n};\n\nstatic void virtio_keyboard_thread(void * data) {\n\ttry_to_get_boot_processor();\n\n\tuint32_t device = (uintptr_t)data;\n\tuintptr_t t = 0x12100000;\n\tpci_write_field(device, PCI_BAR4, 4, t|8);\n\tpci_write_field(device, PCI_COMMAND, 2, 4|2|1);\n\tstruct virtio_device_cfg * cfg = (void*)((char*)mmu_map_mmio_region(t + 0x2000, 0x1000));\n\tcfg->select = 1; /* ask for name */\n\tcfg->subsel = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tdprintf(\"virtio: found '%s'\\n\", cfg->data.str);\n\n\tvoid * irq_region = mmu_map_mmio_region(t + 0x1000, 0x1000);\n\tint irq;\n\tgic_map_pci_interrupt(\"virtio-keyboard\", device, &irq, virtio_keyboard_responder, irq_region);\n\tdprintf(\"virtio-keyboard: irq is %d\\n\", irq);\n\n\tcfg->select = 0;\n\tcfg->subsel = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tstruct virtio_common_cfg * common = (void*)((char*)mmu_map_mmio_region(t, 0x1000));\n\n\tcommon->device_status = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tint queue_size = common->queue_size;\n\tdprintf(\"virtio: queue size is %u\\n\",\n\t\tqueue_size);\n\n\t/* get us one page */\n\tsize_t queue_phys = mmu_allocate_a_frame() << 12;\n\tstruct virtio_queue * queue = mmu_map_mmio_region(queue_phys, 4096);\n\tasm volatile (\"isb\" ::: \"memory\");\n\tmemset(queue, 0, sizeof(struct virtio_queue));\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tcommon->queue_select = 0;\n\tcommon->queue_desc = queue_phys;\n\tcommon->queue_avail = (queue_phys) + offsetof(struct virtio_queue, available);\n\tcommon->queue_used = (queue_phys) + offsetof(struct virtio_queue, used);\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tsize_t buffers_base = mmu_allocate_a_frame() << 12;\n\tvolatile struct virtio_input_event * buffers = mmu_map_mmio_region(buffers_base, 4096);\n\tmmu_get_page((uintptr_t)buffers, 0)->bits.attrindx = 2;\n\n\tfor (int i = 0; i < queue_size; ++i) {\n\t\tqueue->buffers[i].addr = buffers_base + i * sizeof(struct virtio_input_event);\n\t\tqueue->buffers[i].length = sizeof(struct virtio_input_event);\n\t\tqueue->buffers[i].flags = 2;\n\t\tqueue->buffers[i].next = 0;\n\t\tqueue->available.ring[i] = i;\n\t}\n\n\tqueue->available.index = 0;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tcommon->queue_enable = 1;\n\tasm volatile (\"isb\" ::: \"memory\");\n\tcommon->device_status = 4;\n\tasm volatile (\"isb\" ::: \"memory\");\n\n\tuint16_t index = 0;\n\n\tqueue->available.index = queue_size-1;\n\n\twhile (1) {\n\t\t/* Inform the device we have room */\n\t\twhile (queue->used.index == index) {\n\t\t\tswitch_task(0);\n\t\t\tasm volatile (\"dc ivac, %0\\ndsb sy\" :: \"r\"(&queue->used) : \"memory\");\n\t\t}\n\n\t\tuint16_t them = queue->used.index;\n\n\t\tfor (; index != them; index++) {\n\t\t\tasm volatile (\"dc ivac, %0\\ndsb sy\" :: \"r\"(&buffers[index%queue_size]) : \"memory\");\n\t\t\tstruct virtio_input_event evt = buffers[index%queue_size];\n\t\t\twhile (evt.type == 0xFF) {\n\t\t\t\tevt = buffers[index%queue_size];\n\t\t\t\tdprintf(\"virtio-tablet: bad packet %d (them=%d)\\n\", index, them);\n\t\t\t}\n\t\t\tbuffers[index%queue_size].type = 0xFF;\n\t\t\tasm volatile (\"isb\\ndsb sy\" :: \"r\"(buffers) : \"memory\");\n\t\t\tif (evt.type == 1) {\n\t\t\t\t/* need to back-convert which is a pain in the ass */\n\t\t\t\tif (evt.code < 0x49) {\n\t\t\t\t\tuint8_t scancode = evt.code;\n\t\t\t\t\tif (evt.value == 0) {\n\t\t\t\t\t\tscancode |= 0x80;\n\t\t\t\t\t}\n\t\t\t\t\tuint8_t bitbucket;\n\t\t\t\t\twhile (pipe_size(keyboard_pipe) > (int)(DISCARD_POINT)) {\n\t\t\t\t\t\tread_fs(keyboard_pipe, 0, 1, (uint8_t *)&bitbucket);\n\t\t\t\t\t}\n\t\t\t\t\twrite_fs(keyboard_pipe, 0, 1, (uint8_t *)&scancode);\n\t\t\t\t} else if (ext_key_map[evt.code]) {\n\t\t\t\t\tuint8_t data[] = {0xE0, 0};\n\t\t\t\t\tdata[1] = ext_key_map[evt.code] | ((evt.value == 0) ? 0x80 : 0);\n\t\t\t\t\tuint8_t bitbucket;\n\t\t\t\t\twhile (pipe_size(keyboard_pipe) > (int)(DISCARD_POINT)) {\n\t\t\t\t\t\tread_fs(keyboard_pipe, 0, 1, (uint8_t *)&bitbucket);\n\t\t\t\t\t}\n\t\t\t\t\twrite_fs(keyboard_pipe, 0, 2, (uint8_t *)data);\n\t\t\t\t} else {\n\t\t\t\t\tdprintf(\"virtio: unmapped keycode %d\\n\", evt.code);\n\t\t\t\t}\n\t\t\t}\n\t\t\tasm volatile (\"isb\" ::: \"memory\");\n\t\t\tqueue->available.index++;\n\t\t}\n\t}\n}\n\nstatic void virtio_input_maybe(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif (v == 0x1af4 && d == 0x1052) {\n\t\tif (pci_find_type(device) == 0x0900) {\n\t\t\tspawn_worker_thread(virtio_keyboard_thread, \"[virtio-keyboard]\", (void*)(uintptr_t)device);\n\t\t} else if (pci_find_type(device) == 0x0980) {\n\t\t\tspawn_worker_thread(virtio_tablet_thread, \"[virtio-tablet]\", (void*)(uintptr_t)device);\n\t\t}\n\t}\n\n}\n\nvoid null_input(void) {\n\tmouse_pipe = make_pipe(128);\n\tmouse_pipe->flags = FS_CHARDEVICE;\n\tvfs_mount(\"/dev/mouse\", mouse_pipe, \"virtio-mouse\", \"\");\n\n\tvmmouse_pipe = make_pipe(4096);\n\tvmmouse_pipe->flags = FS_CHARDEVICE;\n\tvfs_mount(\"/dev/vmmouse\", vmmouse_pipe, \"virtio-tablet\", \"\");\n\n\tkeyboard_pipe = make_pipe(128);\n\tkeyboard_pipe->flags = FS_CHARDEVICE;\n\tvfs_mount(\"/dev/kbd\", keyboard_pipe, \"virtio-kbd\", \"\");\n\n}\n\nvoid virtio_input(void) {\n\tnull_input(); /* setup pipes */\n\tpci_scan(virtio_input_maybe, -1, NULL);\n}\n\n\n"
  },
  {
    "path": "kernel/arch/x86_64/bootstrap.S",
    "content": "/**\n * @file  kernel/arch/x86_64/bootstrap.c\n * @brief x86-64 entrypoint from bootloader\n *\n * This is primarily adapted from Toaru's 32-bit mulitboot bootstrap.\n * Instead of jumping straight to our C entry point, however, we need\n * to (obviously) get ourselves set up for long mode first by setting\n * up initial page tables, etc.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n.section .multiboot\n.code32\n\n.extern bss_start\n.extern end\n.extern phys\n\n/* Multiboot 1 header */\n.set MB_MAGIC,              0x1BADB002\n.set MB_FLAG_PAGE_ALIGN,    1 << 0\n.set MB_FLAG_MEMORY_INFO,   1 << 1\n.set MB_FLAG_GRAPHICS,      1 << 2\n.set MB_FLAG_AOUT,          1 << 16\n.set MB_FLAGS,              MB_FLAG_PAGE_ALIGN | MB_FLAG_MEMORY_INFO | MB_FLAG_GRAPHICS | MB_FLAG_AOUT\n.set MB_CHECKSUM,           -(MB_MAGIC + MB_FLAGS)\n\n/* Multiboot section */\n.align 4\nmultiboot_header:\n.long MB_MAGIC\n.long MB_FLAGS\n.long MB_CHECKSUM\n.long multiboot_header /* header_addr */\n.long phys             /* load_addr */\n.long bss_start        /* load_end_addr */\n.long end              /* bss_end_addr */\n.long start            /* entry_addr */\n\n/* Request linear graphics mode */\n.long 0x00000000\n.long 1024\n.long 768\n.long 32\n\n/* Multiboot 2 header */\n.set MB2_MAGIC,    0xe85250d6\n.set MB2_ARCH,     0\n.set MB2_LENGTH,   (multiboot2_header_end - multiboot2_header)\n.set MB2_CHECKSUM, -(MB2_MAGIC + MB2_ARCH + MB2_LENGTH)\n.align 8\nmultiboot2_header:\n.long MB2_MAGIC\n.long MB2_ARCH\n.long MB2_LENGTH\n.long MB2_CHECKSUM\n\n/* Address tag */\n.align 8\nmb2_tag_addr:\n.word 2\n.word 0\n.long 24\n.long multiboot2_header\n.long phys\n.long bss_start\n.long end\n\n/* Entry tag */\n.align 8\nmb2_tag_entry:\n.word 3\n.word 0\n.long 12\n.long start_mbi2\n\n/* Framebuffer tag */\n.align 8\nmb2_tag_fb:\n.word 5\n.word 0\n.long 20\n.long 1024\n.long 768\n.long 32\n\n.align 8\n.word 4\n.word 1\n.long 12\n.long 2 /* We support EGA text, but don't require it */\n\n/* Modules must be aligned */\n.align 8\n.word 6\n.word 0\n.long 8\n\n/* Relocatable tag */\n.align 8\n.word 10\n.word 0\n.long 24\n.long 0x100000  /* Start */\n.long 0x1000000 /* Maximum load address */\n.long 4096      /* Request page alignment */\n.long 1         /* Load at lowest available */\n\n/* End tag */\n.align 8\n.word 0\n.word 0\n.long 8\n\nmultiboot2_header_end:\n\n/* .stack resides in .bss */\n.section .stack, \"aw\", @nobits\nstack_bottom:\n.skip 16384 /* 16KiB */\n.global stack_top\nstack_top:\n\n.section .bootstrap\n.code32\n.align 4\n\n.extern jmp_to_long\n.type jmp_to_long, @function\n\n.extern kmain\n.type kmain, @function\n\n.global start_mbi2\n.type start_mbi2, @function\n\nstart_mbi2:\n    /* Use reserved 0 space of boot information struct to thunk eip */\n    movl %ebx, %ecx\n    addl $8, %ecx\n    jmp good_to_go\n\n.global start\n.type start, @function\n\nstart:\n    /* Use boot_drive as a temporary place to thunk eip */\n    movl %ebx, %ecx\n    addl $16, %ecx\n\ngood_to_go:\n    mov  %ecx, %esp\n\n    call _forward\n_forward:\n    popl %ecx\n    subl $_forward, %ecx\n\n    /* Setup our stack */\n    mov $stack_top, %esp\n    addl %ecx, %esp\n\n    /* Make sure our stack is 16-byte aligned */\n    and $-16, %esp\n\n    pushl $0\n    pushl %esp\n    pushl $0\n    pushl %eax /* Multiboot header magic */\n    pushl $0\n    pushl %ebx /* Multiboot header pointer */\n\n    jmp jmp_to_long\n\n\n.align 4\n\njmp_to_long:\n    .extern init_page_region\n\n    /* Set up initial page region, which was zero'd for us by the loader */\n    mov $init_page_region, %edi\n    addl %ecx, %edi\n    pushl %ecx\n\n    /* PML4[0] = &PDP[0] | (PRESENT, WRITABLE, USER) */\n    mov $0x1007, %eax\n    add %edi, %eax\n    mov %eax, (%edi)\n\n    /* PDP[0] = &PD[0] | (PRESENT, WRITABLE, USER) */\n    add $0x1000, %edi\n    mov $0x1003, %eax\n    add %edi, %eax\n    mov %eax, (%edi)\n\n    /* Set 32 2MiB pages to map 64MiB of low memory temporarily, which should\n       be enough to get us through our C MMU initialization where we then\n       use 2MiB pages to map all of the 4GiB standard memory space and map\n       a much more restricted subset of the kernel in the lower address space. */\n    add $0x1000, %edi\n\n    mov $0x87, %ebx\n    mov $32, %ecx\n\n.set_entry:\n    mov %ebx, (%edi)\n    add $0x200000, %ebx\n    add $8, %edi\n    loop .set_entry\n\n    mov $init_page_region, %edi\n    popl %ecx\n    addl %ecx, %edi\n    pushl %ecx\n    mov %edi, %cr3\n\n    /* Enable PAE */\n    mov %cr4, %eax\n    or $32, %eax\n    mov %eax, %cr4\n\n    /* EFER */\n    mov $0xC0000080, %ecx\n    rdmsr\n    or $256, %eax\n    wrmsr\n\n    /* Check PG */\n    mov %cr0, %eax\n\n    /* If paging was enabled, assume we were already in long mode (eg. booted by EFI) */\n    test $0x80000000, %eax\n    jnz .continue\n\n    /* Otherwise enable paging */\n    or $0x80000000, %eax\n    mov %eax, %cr0\n\nsuper_duper_bullshit:\n    popl %ecx\n\n    lea (_lgdt_instr+3)(%ecx), %eax\n    movl (%eax), %ebx\n    addl %ecx, %ebx\n    movl %ebx, (%eax)\n\n    lea (gdtr+2)(%ecx), %eax\n    movl (%eax), %ebx\n    addl %ecx, %ebx\n    movl %ebx, (%eax)\n\n    lea (_ljmp_instr+1)(%ecx), %eax\n    movl (%eax), %ebx\n    addl %ecx, %ebx\n    movl %ebx, (%eax)\n\n    pushl $0\n    pushl %ecx\n\n_lgdt_instr:\n    lgdt gdtr\n\n_ljmp_instr:\n    ljmp $0x08,$realm64\n\n.align 8\ngdtr:\n    .word gdt_end-gdt_base\n    .quad gdt_base\n\ngdt_base:\n    /* Null */\n    .quad 0\n    /* Code */\n    .word 0\n    .word 0\n    .byte 0\n    .byte 0x9a\n    .byte 0x20\n    .byte 0\n    /*  Data */\n    .word 0xffff\n    .word 0\n    .byte 0\n    .byte 0x92\n    .byte 0\n    .byte 0\ngdt_end:\n\n\n.code64\n.align 8\n.section .bootstrap\n\nrealm64:\n    cli\n    mov $0x10, %ax\n    mov %ax, %ds\n    mov %ax, %es\n    mov %ax, %fs\n    mov %ax, %gs\n    mov %ax, %ss\n\n.continue:\n    cli\n    pop %rcx\n    pop %rdi\n    pop %rsi\n    pop %rdx\n    callq kmain\n\nhalt:\n    cli\n    hlt\n    jmp halt\n"
  },
  {
    "path": "kernel/arch/x86_64/cmos.c",
    "content": "/**\n * @file kernel/arch/x86_64/cmos.c\n * @author K. Lange\n * @brief Real-time clock.\n *\n * Provides access to the CMOS RTC for initial boot time and\n * calibrates the TSC to use as a general timing source. IRQ 0\n * handler is also in here because it updates the wall clock time\n * and triggers timeout-based wakeups.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/process.h>\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n#include <sys/time.h>\n\nuint64_t arch_boot_time = 0; /**< Time (in seconds) according to the CMOS right before we examine the TSC */\nuint64_t tsc_basis_time = 0; /**< Accumulated time (in microseconds) on the TSC, when we timed it; eg. how long did boot take */\nuint64_t tsc_mhz = 3500;     /**< MHz rating we determined for the TSC. Usually also the core speed? */\n\n/* Crusty old CMOS code follows. */\n\n#define from_bcd(val)  ((val / 16) * 10 + (val & 0xf))\n#define CMOS_ADDRESS   0x70\n#define CMOS_DATA      0x71\n\nenum {\n\tCMOS_SECOND = 0,\n\tCMOS_MINUTE = 2,\n\tCMOS_HOUR = 4,\n\tCMOS_DAY = 7,\n\tCMOS_MONTH = 8,\n\tCMOS_YEAR = 9\n};\n\n/**\n * @brief Read the contents of the RTC CMOS\n *\n * @param values (out) Where to stick the values read.\n */\nstatic void cmos_dump(uint16_t * values) {\n\tfor (uint16_t index = 0; index < 128; ++index) {\n\t\toutportb(CMOS_ADDRESS, index);\n\t\tvalues[index] = inportb(CMOS_DATA);\n\t}\n}\n\n/**\n * @brief Check if the CMOS is currently being updated.\n */\nstatic int is_update_in_progress(void) {\n\toutportb(CMOS_ADDRESS, 0x0a);\n\treturn inportb(CMOS_DATA) & 0x80;\n}\n\n/**\n * @brief Poorly convert years to Unix timestamps.\n *\n * @param years Years since 2000\n * @returns Seconds since the Unix epoch, maybe...\n */\nstatic uint64_t secs_of_years(int years) {\n\tuint64_t days = 0;\n\tyears += 2000;\n\twhile (years > 1969) {\n\t\tdays += 365;\n\t\tif (years % 4 == 0) {\n\t\t\tif (years % 100 == 0) {\n\t\t\t\tif (years % 400 == 0) {\n\t\t\t\t\tdays++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdays++;\n\t\t\t}\n\t\t}\n\t\tyears--;\n\t}\n\treturn days * 86400;\n}\n\n/**\n * @brief How long was a month in a given year?\n *\n * Tries to do leap year stuff for February.\n *\n * @param months 1~12 calendar month\n * @param year   Years since 2000\n * @return Number of seconds in that month.\n */\nstatic uint64_t secs_of_month(int months, int year) {\n\tyear += 2000;\n\n\tuint64_t days = 0;\n\tswitch(months) {\n\t\tcase 11:\n\t\t\tdays += 30; /* fallthrough */\n\t\tcase 10:\n\t\t\tdays += 31; /* fallthrough */\n\t\tcase 9:\n\t\t\tdays += 30; /* fallthrough */\n\t\tcase 8:\n\t\t\tdays += 31; /* fallthrough */\n\t\tcase 7:\n\t\t\tdays += 31; /* fallthrough */\n\t\tcase 6:\n\t\t\tdays += 30; /* fallthrough */\n\t\tcase 5:\n\t\t\tdays += 31; /* fallthrough */\n\t\tcase 4:\n\t\t\tdays += 30; /* fallthrough */\n\t\tcase 3:\n\t\t\tdays += 31; /* fallthrough */\n\t\tcase 2:\n\t\t\tdays += 28;\n\t\t\tif ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) {\n\t\t\t\tdays++;\n\t\t\t} /* fallthrough */\n\t\tcase 1:\n\t\t\tdays += 31; /* fallthrough */\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\treturn days * 86400;\n}\n\n/**\n * @brief Convert the CMOS time to a Unix timestamp.\n *\n * Reads BCD data from the RTC CMOS and does some dumb\n * math to convert the display time to a Unix timestamp.\n *\n * @return Current Unix time\n */\nuint64_t read_cmos(void) {\n\tuint16_t values[128];\n\tuint16_t old_values[128];\n\n\twhile (is_update_in_progress());\n\tcmos_dump(values);\n\n\tdo {\n\t\tmemcpy(old_values, values, 128);\n\t\twhile (is_update_in_progress());\n\t\tcmos_dump(values);\n\t} while ((old_values[CMOS_SECOND] != values[CMOS_SECOND]) ||\n\t\t(old_values[CMOS_MINUTE] != values[CMOS_MINUTE]) ||\n\t\t(old_values[CMOS_HOUR] != values[CMOS_HOUR])     ||\n\t\t(old_values[CMOS_DAY] != values[CMOS_DAY])       ||\n\t\t(old_values[CMOS_MONTH] != values[CMOS_MONTH])   ||\n\t\t(old_values[CMOS_YEAR] != values[CMOS_YEAR]));\n\n\t/* Math Time */\n\tuint64_t time =\n\t\tsecs_of_years(from_bcd(values[CMOS_YEAR]) - 1) +\n\t\tsecs_of_month(from_bcd(values[CMOS_MONTH]) - 1,\n\t\tfrom_bcd(values[CMOS_YEAR])) +\n\t\t(from_bcd(values[CMOS_DAY]) - 1) * 86400 +\n\t\t(from_bcd(values[CMOS_HOUR])) * 3600 +\n\t\t(from_bcd(values[CMOS_MINUTE])) * 60 +\n\t\tfrom_bcd(values[CMOS_SECOND]) + 0;\n\n\treturn time;\n}\n\n/**\n * @brief Helper to read timestamp counter\n */\nstatic inline uint64_t read_tsc(void) {\n\tuint32_t lo, hi;\n\tasm volatile ( \"rdtsc\" : \"=a\"(lo), \"=d\"(hi) );\n\treturn ((uint64_t)hi << 32UL) | (uint64_t)lo;\n}\n\n/**\n * @brief Exported interface to read timestamp counter.\n *\n * Used by various things in the kernel to get a quick performance\n * timer value. This is always scaled by @c arch_cpu_mhz when it\n * needs to be converted to something user-friendly.\n */\nuint64_t arch_perf_timer(void) {\n\treturn read_tsc();\n}\n\n/**\n * @brief What to scale performance counter times by.\n *\n * I've called this \"arch_cpu_mhz\" but I don't know if that's\n * always going to be true, so this may need to be renamed at\n * some point...\n */\nsize_t arch_cpu_mhz(void) {\n\treturn tsc_mhz;\n}\n\n/**\n * @brief Initializes boot time, system time, TSC rate, etc.\n *\n * We determine TSC rate with a one-shot PIT, which seems\n * to work fine... the PIT is the only thing with both reasonable\n * precision and actual known wall-clock configuration.\n *\n * In Bochs, this has a tendency to be 1) completely wrong (usually\n * about half the time that actual execution will run at, in my\n * experiences) and 2) loud, as despite the attempt to turn off\n * the speaker it still seems to beep it (the second channel of the\n * PIT controls the beeper).\n *\n * In QEMU, VirtualBox, VMware, and on all real hardware I've tested,\n * including everything from a ThinkPad T410 to a Surface Pro 6, this\n * has been surprisingly accurate and good enough to use the TSC as\n * our only wall clock source.\n */\nvoid arch_clock_initialize(void) {\n\tdprintf(\"tsc: Calibrating system timestamp counter.\\n\");\n\tarch_boot_time = read_cmos();\n\tuintptr_t end_lo, end_hi;\n\tuint32_t start_lo, start_hi;\n\tasm volatile (\n\t\t/* Disables and sets gating for channel 2 */\n\t\t\"inb   $0x61, %%al\\n\"\n\t\t\"andb  $0xDD, %%al\\n\"\n\t\t\"orb   $0x01, %%al\\n\"\n\t\t\"outb  %%al, $0x61\\n\"\n\t\t/* Configure channel 2 to one-shot, next two bytes are low/high */\n\t\t\"movb  $0xB2, %%al\\n\" /* 0b10110010 */\n\t\t\"outb  %%al, $0x43\\n\"\n\t\t/* 0x__9b */\n\t\t\"movb  $0x9B, %%al\\n\"\n\t\t\"outb  %%al, $0x42\\n\"\n\t\t\"inb   $0x60, %%al\\n\"\n\t\t/*  0x2e__ */\n\t\t\"movb  $0x2E, %%al\\n\"\n\t\t\"outb  %%al, $0x42\\n\"\n\t\t/* Re-enable */\n\t\t\"inb   $0x61, %%al\\n\"\n\t\t\"andb  $0xDE, %%al\\n\"\n\t\t\"outb  %%al, $0x61\\n\"\n\t\t/* Pulse high */\n\t\t\"orb   $0x01, %%al\\n\"\n\t\t\"outb  %%al, $0x61\\n\"\n\t\t/* Read TSC and store in vars */\n\t\t\"rdtsc\\n\"\n\t\t\"movl  %%eax, %2\\n\"\n\t\t\"movl  %%edx, %3\\n\"\n\t\t/* In QEMU and VirtualBox, this seems to flip low.\n\t\t * On real hardware and VMware it flips high. */\n\t\t\"inb   $0x61, %%al\\n\"\n\t\t\"andb  $0x20, %%al\\n\"\n\t\t\"jz   2f\\n\"\n\t\t/* Loop until output goes low? */\n\t\"1:\\n\"\n\t\t\"inb   $0x61, %%al\\n\"\n\t\t\"andb  $0x20, %%al\\n\"\n\t\t\"jnz   1b\\n\"\n\t\t\"rdtsc\\n\"\n\t\t\"jmp   3f\\n\"\n\t\t/* Loop until output goes high */\n\t\"2:\\n\"\n\t\t\"inb   $0x61, %%al\\n\"\n\t\t\"andb  $0x20, %%al\\n\"\n\t\t\"jz   2b\\n\"\n\t\t\"rdtsc\\n\"\n\t\"3:\\n\"\n\t\t: \"=a\"(end_lo), \"=d\"(end_hi), \"=r\"(start_lo), \"=r\"(start_hi)\n\t);\n\n\tuintptr_t end   = ((end_hi & 0xFFFFffff)   << 32) | (end_lo & 0xFFFFffff);\n\tuintptr_t start = ((uintptr_t)(start_hi & 0xFFFFffff) << 32) | (start_lo & 0xFFFFffff);\n\ttsc_mhz = (end - start) / 10000;\n\tif (tsc_mhz == 0) tsc_mhz = 2000; /* uh oh */\n\ttsc_basis_time = start / tsc_mhz;\n\n\tdprintf(\"tsc: TSC timed at %lu MHz..\\n\", tsc_mhz);\n\tdprintf(\"tsc: Boot time is %lus.\\n\", arch_boot_time);\n\tdprintf(\"tsc: Initial TSC timestamp was %luus.\\n\", tsc_basis_time);\n}\n\n#define SUBSECONDS_PER_SECOND 1000000\n\n/**\n * @brief Subdivide ticks into seconds in subticks.\n */\nstatic void update_ticks(uint64_t ticks, uint64_t *timer_ticks, uint64_t *timer_subticks) {\n\t*timer_subticks = ticks - tsc_basis_time;\n\t*timer_ticks = *timer_subticks / SUBSECONDS_PER_SECOND;\n\t*timer_subticks = *timer_subticks % SUBSECONDS_PER_SECOND;\n}\n\n/**\n * @brief Exposed interface for wall clock time.\n *\n * Note that while the kernel version of this takes a *z option that is\n * supposed to have timezone information, we don't actually use  it,\n * and I'm pretty sure it's NULL everywhere?\n *\n * We calculate wall time using the TSC, the calculate TSC tick rate,\n * and the boot time retrieved from the CMOS, subdivide the result\n * into seconds and \"subseconds\" (microseconds), and store that.\n */\nint gettimeofday(struct timeval * t, void *z) {\n\tuint64_t tsc = read_tsc();\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks);\n\tt->tv_sec = arch_boot_time + timer_ticks;\n\tt->tv_usec = timer_subticks;\n\treturn 0;\n}\n\n/**\n * @brief Dumb convenience function for things that just want a Unix timestamp.\n *\n * @return Wall clock time as a Unix timestamp (seconds since the epoch).\n */\nuint64_t now(void) {\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\treturn t.tv_sec;\n}\n\nstatic spin_lock_t _time_set_lock;\n\n/**\n * Set the system clock time\n *\n * TODO: A lot of this time stuff needs to be made more generic,\n *       it's shared pretty directly with aarch64...\n */\nint settimeofday(struct timeval * t, void *z) {\n\tif (!t) return -EINVAL;\n\tif (t->tv_sec < 0 || t->tv_usec < 0 || t->tv_usec > 1000000) return -EINVAL;\n\n\tspin_lock(_time_set_lock);\n\tuint64_t clock_time = now();\n\tarch_boot_time += t->tv_sec - clock_time;\n\tspin_unlock(_time_set_lock);\n\n\treturn 0;\n}\n\n\n/**\n * @brief Calculate a time in the future.\n *\n * Takes the current raw TSC time and adds @p seconds seconds and @p subseconds microseconds to it.\n * Stores the result seconds and microseconds in @p out_seconds and @p out_subseconds. If\n * @p seconds and @p subseconds are both zero, this is effectively equivalent to @c update_ticks.\n *\n * This is an exposed interface used throughout the kernel, usually to calculate\n * timeouts and yielding delays.\n *\n * This uses raw TSC time, which is not adjusted for either boot time or wall clock time.\n */\nvoid relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) {\n\tif (!arch_boot_time) {\n\t\t*out_seconds = 0;\n\t\t*out_subseconds = 0;\n\t\treturn;\n\t}\n\n\tuint64_t tsc = read_tsc();\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks);\n\tif (subseconds + timer_subticks >= SUBSECONDS_PER_SECOND) {\n\t\t*out_seconds    = timer_ticks + seconds + (subseconds + timer_subticks) / SUBSECONDS_PER_SECOND;\n\t\t*out_subseconds = (subseconds + timer_subticks) % SUBSECONDS_PER_SECOND;\n\t} else {\n\t\t*out_seconds    = timer_ticks + seconds;\n\t\t*out_subseconds = timer_subticks + subseconds;\n\t}\n}\n\nstatic uint64_t time_slice_basis = 0; /**< When the last clock update happened */\nstatic spin_lock_t clock_lock = { 0 }; /**< Allow only one core to do this */\n\n/**\n * @brief Update the global timer tick values.\n *\n * Updates process CPU usage times and wakes up any timed sleepers\n * based on the current TSC time.\n */\nvoid arch_update_clock(void) {\n\tspin_lock(clock_lock);\n\n\t/* Obtain TSC timestamp, in microseconds */\n\tuint64_t clock_ticks = read_tsc() / tsc_mhz;\n\n\t/* Convert it to seconds and subseconds */\n\tuint64_t timer_ticks, timer_subticks;\n\tupdate_ticks(clock_ticks, &timer_ticks, &timer_subticks);\n\n\t/**\n\t * Update per-process quarter-second usage statistics\n\t *\n\t * XXX I think this was a bad idea and it should be removed.\n\t *     We store four quarter-second usage values in a sliding\n\t *     array and update them for every process, so that we can\n\t *     query CPU% without having to sample, but that's a lot\n\t *     more work in the kernel than we need...\n\t */\n\tif (time_slice_basis + SUBSECONDS_PER_SECOND/4 <= clock_ticks) {\n\t\tupdate_process_usage(clock_ticks - time_slice_basis, tsc_mhz);\n\t\ttime_slice_basis = clock_ticks;\n\t}\n\tspin_unlock(clock_lock);\n\n\t/* Wake up any processes that have expired timeouts */\n\twakeup_sleepers(timer_ticks, timer_subticks);\n}\n\n"
  },
  {
    "path": "kernel/arch/x86_64/gdt.c",
    "content": "/**\n * @file kernel/arch/x86_64/gdt.c\n * @brief x86-64 GDT\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/process.h>\n\n/**\n * @brief 64-bit TSS\n */\ntypedef struct tss_entry {\n\tuint32_t reserved_0;\n\tuint64_t rsp[3];\n\tuint64_t reserved_1;\n\tuint64_t ist[7];\n\tuint64_t reserved_2;\n\tuint16_t reserved_3;\n\tuint16_t iomap_base;\n} __attribute__ ((packed)) tss_entry_t;\n\ntypedef struct {\n\tuint16_t limit_low;\n\tuint16_t base_low;\n\tuint8_t base_middle;\n\tuint8_t access;\n\tuint8_t granularity;\n\tuint8_t base_high;\n} __attribute__((packed)) gdt_entry_t;\n\ntypedef struct {\n\tuint32_t base_highest;\n\tuint32_t reserved0;\n} __attribute__((packed)) gdt_entry_high_t;\n\ntypedef struct {\n\tuint16_t limit;\n\tuintptr_t base;\n} __attribute__((packed)) gdt_pointer_t;\n\ntypedef struct  {\n\tgdt_entry_t entries[7];\n\tgdt_entry_high_t tss_extra;\n\tgdt_pointer_t pointer;\n\ttss_entry_t tss;\n} __attribute__((packed)) __attribute__((aligned(0x10))) FullGDT;\n\nFullGDT gdt[32] __attribute__((used)) = {{\n\t{\n\t\t{0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00},\n\t\t{0xFFFF, 0x0000, 0x00, 0x9A, (1 << 5) | (1 << 7) | 0x0F, 0x00},\n\t\t{0xFFFF, 0x0000, 0x00, 0x92, (1 << 5) | (1 << 7) | 0x0F, 0x00},\n\t\t{0xFFFF, 0x0000, 0x00, 0xFA, (1 << 5) | (1 << 7) | 0x0F, 0x00},\n\t\t{0xFFFF, 0x0000, 0x00, 0xF2, (1 << 5) | (1 << 7) | 0x0F, 0x00},\n\t\t{0xFFFF, 0x0000, 0x00, 0xFA, (1 << 5) | (1 << 7) | 0x0F, 0x00},\n\t\t{0x0067, 0x0000, 0x00, 0xE9, 0x00, 0x00},\n\t},\n\t{0x00000000, 0x00000000},\n\t{0x0000, 0x0000000000000000},\n\t{0,{0,0,0},0,{0,0,0,0,0,0,0},0,0,0},\n}};\n\nvoid gdt_install(void) {\n\tfor (int i = 1; i < 32; ++i) {\n\t\tmemcpy(&gdt[i], &gdt[0], sizeof(*gdt));\n\t}\n\n\tfor (int i = 0; i < 32; ++i) {\n\t\tgdt[i].pointer.limit = sizeof(gdt[i].entries)+sizeof(gdt[i].tss_extra)-1;\n\t\tgdt[i].pointer.base  = (uintptr_t)&gdt[i].entries;\n\n\t\tuintptr_t addr = (uintptr_t)&gdt[i].tss;\n\t\tgdt[i].entries[6].limit_low = sizeof(gdt[i].tss);\n\t\tgdt[i].entries[6].base_low = (addr & 0xFFFF);\n\t\tgdt[i].entries[6].base_middle = (addr >> 16) & 0xFF;\n\t\tgdt[i].entries[6].base_high = (addr >> 24) & 0xFF;\n\t\tgdt[i].tss_extra.base_highest = (addr >> 32) & 0xFFFFFFFF;\n\t}\n\n\textern void * stack_top;\n\tgdt[0].tss.rsp[0] = (uintptr_t)&stack_top;\n\n\tasm volatile (\n\t\t\"lgdt %0\\n\"\n\t\t\"mov $0x10, %%ax\\n\"\n\t\t\"mov %%ax, %%ds\\n\"\n\t\t\"mov %%ax, %%es\\n\"\n\t\t\"mov %%ax, %%ss\\n\"\n\t\t\"mov $0x33, %%ax\\n\" /* TSS offset */\n\t\t\"ltr %%ax\\n\"\n\t\t: : \"m\"(gdt[0].pointer) : \"rax\", \"memory\"\n\t);\n}\n\nvoid gdt_copy_to_trampoline(int ap, char * trampoline) {\n\tmemcpy(trampoline, &gdt[ap].pointer, sizeof(gdt[ap].pointer));\n}\n\nvoid arch_set_kernel_stack(uintptr_t stack) {\n\tgdt[this_core->cpu_id].tss.rsp[0] = stack;\n\tthis_core->syscall_stack = stack;\n}\n\nvoid arch_set_tls_base(uintptr_t tlsbase) {\n\tasm volatile (\"wrmsr\" : : \"c\"(0xc0000100), \"d\"((uint32_t)(tlsbase >> 32)), \"a\"((uint32_t)(tlsbase & 0xFFFFFFFF)));\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/idt.c",
    "content": "/**\n * @file kernel/arch/x86_64/idt.c\n * @brief x86-64 Interrupt Descriptor Table management\n *\n * This is the C side of all interrupt handling. See\n * also @ref irq.S which has the assembly entrypoints.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/vfs.h>\n#include <kernel/version.h>\n#include <kernel/process.h>\n#include <kernel/signal.h>\n#include <kernel/misc.h>\n#include <kernel/time.h>\n#include <kernel/ptrace.h>\n#include <kernel/hashmap.h>\n#include <kernel/module.h>\n#include <kernel/ksym.h>\n#include <kernel/mmu.h>\n#include <kernel/syscall.h>\n\n#include <sys/time.h>\n#include <sys/utsname.h>\n#include <sys/ptrace.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/pml.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/irq.h>\n\nstatic struct idt_pointer idtp;\nstatic idt_entry_t idt[256];\n\n/**\n * @brief Initialize a gate, since there's some address swizzling involved...\n */\nvoid idt_set_gate(uint8_t num, interrupt_handler_t handler, uint16_t selector, uint8_t flags, int userspace) {\n\tuintptr_t base = (uintptr_t)handler;\n\tidt[num].base_low  = (base & 0xFFFF);\n\tidt[num].base_mid  = (base >> 16) & 0xFFFF;\n\tidt[num].base_high = (base >> 32) & 0xFFFFFFFF;\n\tidt[num].selector = selector;\n\tidt[num].zero = 0;\n\tidt[num].pad = 0;\n\tidt[num].flags = flags | (userspace ? 0x60 : 0);\n}\n\n/**\n * @brief Initializes the IDT and sets up gates for all interrupts.\n */\nvoid idt_install(void) {\n\tidtp.limit = sizeof(idt);\n\tidtp.base  = (uintptr_t)&idt;\n\n\t/** ISRs */\n\tidt_set_gate(0,  _isr0,  0x08, 0x8E, 0);\n\tidt_set_gate(1,  _isr1,  0x08, 0x8E, 0);\n\tidt_set_gate(2,  _isr2,  0x08, 0x8E, 0);\n\tidt_set_gate(3,  _isr3,  0x08, 0x8E, 0);\n\tidt_set_gate(4,  _isr4,  0x08, 0x8E, 0);\n\tidt_set_gate(5,  _isr5,  0x08, 0x8E, 0);\n\tidt_set_gate(6,  _isr6,  0x08, 0x8E, 0);\n\tidt_set_gate(7,  _isr7,  0x08, 0x8E, 0);\n\tidt_set_gate(8,  _isr8,  0x08, 0x8E, 0);\n\tidt_set_gate(9,  _isr9,  0x08, 0x8E, 0);\n\tidt_set_gate(10, _isr10, 0x08, 0x8E, 0);\n\tidt_set_gate(11, _isr11, 0x08, 0x8E, 0);\n\tidt_set_gate(12, _isr12, 0x08, 0x8E, 0);\n\tidt_set_gate(13, _isr13, 0x08, 0x8E, 0);\n\tidt_set_gate(14, _isr14, 0x08, 0x8E, 0);\n\tidt_set_gate(15, _isr15, 0x08, 0x8E, 0);\n\tidt_set_gate(16, _isr16, 0x08, 0x8E, 0);\n\tidt_set_gate(17, _isr17, 0x08, 0x8E, 0);\n\tidt_set_gate(18, _isr18, 0x08, 0x8E, 0);\n\tidt_set_gate(19, _isr19, 0x08, 0x8E, 0);\n\tidt_set_gate(20, _isr20, 0x08, 0x8E, 0);\n\tidt_set_gate(21, _isr21, 0x08, 0x8E, 0);\n\tidt_set_gate(22, _isr22, 0x08, 0x8E, 0);\n\tidt_set_gate(23, _isr23, 0x08, 0x8E, 0);\n\tidt_set_gate(24, _isr24, 0x08, 0x8E, 0);\n\tidt_set_gate(25, _isr25, 0x08, 0x8E, 0);\n\tidt_set_gate(26, _isr26, 0x08, 0x8E, 0);\n\tidt_set_gate(27, _isr27, 0x08, 0x8E, 0);\n\tidt_set_gate(28, _isr28, 0x08, 0x8E, 0);\n\tidt_set_gate(29, _isr29, 0x08, 0x8E, 0);\n\tidt_set_gate(30, _isr30, 0x08, 0x8E, 0);\n\tidt_set_gate(31, _isr31, 0x08, 0x8E, 0);\n\tidt_set_gate(32, _irq0,  0x08, 0x8E, 0);\n\tidt_set_gate(33, _irq1,  0x08, 0x8E, 0);\n\tidt_set_gate(34, _irq2,  0x08, 0x8E, 0);\n\tidt_set_gate(35, _irq3,  0x08, 0x8E, 0);\n\tidt_set_gate(36, _irq4,  0x08, 0x8E, 0);\n\tidt_set_gate(37, _irq5,  0x08, 0x8E, 0);\n\tidt_set_gate(38, _irq6,  0x08, 0x8E, 0);\n\tidt_set_gate(39, _irq7,  0x08, 0x8E, 0);\n\tidt_set_gate(40, _irq8,  0x08, 0x8E, 0);\n\tidt_set_gate(41, _irq9,  0x08, 0x8E, 0);\n\tidt_set_gate(42, _irq10, 0x08, 0x8E, 0);\n\tidt_set_gate(43, _irq11, 0x08, 0x8E, 0);\n\tidt_set_gate(44, _irq12, 0x08, 0x8E, 0);\n\tidt_set_gate(45, _irq13, 0x08, 0x8E, 0);\n\tidt_set_gate(46, _irq14, 0x08, 0x8E, 0);\n\tidt_set_gate(47, _irq15, 0x08, 0x8E, 0);\n\n\tidt_set_gate(123, _isr123, 0x08, 0x8E, 0); /* Clock interrupt for other processors */\n\tidt_set_gate(124, _isr124, 0x08, 0x8E, 0); /* Bad TLB shootdown. */\n\tidt_set_gate(125, _isr125, 0x08, 0x8E, 0); /* Halts everyone. */\n\tidt_set_gate(126, _isr126, 0x08, 0x8E, 0); /* Does nothing, used to exit wait-for-interrupt sleep. */\n\tidt_set_gate(127, _isr127, 0x08, 0x8E, 1); /* Legacy system call entry point, called by userspace. */\n\n\tasm volatile (\n\t\t\"lidt %0\"\n\t\t: : \"m\"(idtp)\n\t);\n}\n\n/**\n * @brief Quicker call to lidt for APs, when the IDT is already set up.\n *\n * We use the same idt in all cores, so there's not much to do here.\n */\nvoid idt_ap_install(void) {\n\tidtp.limit = sizeof(idt);\n\tidtp.base  = (uintptr_t)&idt;\n\tasm volatile (\n\t\t\"lidt %0\"\n\t\t: : \"m\"(idtp)\n\t);\n}\n\n/** External IRQ management */\n#define IRQ_CHAIN_SIZE  16\n#define IRQ_CHAIN_DEPTH 4\nstatic irq_handler_chain_t irq_routines[IRQ_CHAIN_SIZE * IRQ_CHAIN_DEPTH] = { NULL };\nstatic const char * _irq_handler_descriptions[IRQ_CHAIN_SIZE * IRQ_CHAIN_DEPTH] = { NULL };\n\n/**\n * @brief Examine the IRQ handler chain to see what handles an IRQ.\n *\n * This is a debug function used by the procfs /proc/irq callback.\n * Can be called with different @p chain values to get all of the\n * handlers when there is more than one.\n *\n * @param irq The interrupt number (0~15)\n * @param chain Handler chain depth (0~4)\n * @return The name of the handler.\n */\nconst char * get_irq_handler(int irq, int chain) {\n\tif (irq >= IRQ_CHAIN_SIZE) return NULL;\n\tif (chain >= IRQ_CHAIN_DEPTH) return NULL;\n\treturn _irq_handler_descriptions[IRQ_CHAIN_SIZE * chain + irq];\n}\n\n/**\n * @brief Install an IRQ handler.\n *\n * TODO Shouldn't this return a status code? What if we have too many\n *      IRQs installed? What if @p irq is invalid (>16)?\n *\n * TODO Should we provide callers with a unique reference to their IRQ vector\n *      so it can be removed later?\n *\n * @param irq The IRQ number to handle (0~15)\n * @param handler Function to install as a callback for this IRQ\n * @param desc Textual description for debugging.\n */\nvoid irq_install_handler(size_t irq, irq_handler_chain_t handler, const char * desc) {\n\tfor (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) {\n\t\tif (irq_routines[i * IRQ_CHAIN_SIZE + irq])\n\t\t\tcontinue;\n\t\tirq_routines[i * IRQ_CHAIN_SIZE + irq] = handler;\n\t\t_irq_handler_descriptions[i * IRQ_CHAIN_SIZE + irq ] = desc;\n\t\tbreak;\n\t}\n}\n\n/* We used to have a function here that incorrectly uninstalled IRQ handlers... */\n\n/**\n * @brief Examine the module table to find which module owns an address.\n *\n * Looks through the loaded module list to find what module\n * owns @p addr, setting @p name and returning the corresponding module\n * entry. Since we know how big modules are in memory, we can also know\n * if an address doesn't belong to any module, in which case we return NULL.\n *\n * @param addr Address to look for\n * @param name (out) Name of the matching module\n * @return Pointer to LoadedModule for the matched module, or NULL.\n */\nstatic struct LoadedModule * find_module(uintptr_t addr, char ** name) {\n\thashmap_t * modules = modules_get_list();\n\n\tfor (size_t i = 0; i < modules->size; ++i) {\n\t\thashmap_entry_t * x = modules->entries[i];\n\t\twhile (x) {\n\t\t\tstruct LoadedModule * info = x->value;\n\t\t\tif (info->baseAddress <= addr && addr <= info->baseAddress + info->loadedSize) {\n\t\t\t\t*name = (char*)x->key;\n\t\t\t\treturn info;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/**\n * @brief Use brute force to determine if an address is mapped.\n *\n * Examines the current page table to see if @p base and up to @p size\n * is a valid region of memory. Useful for determining if a stack entry\n * is a valid base pointer to a calling frame.\n *\n * @param base Address to validate\n * @param size How many bytes after @p base are going to be examined.\n * @return 1 if the range is mapped, 0 otherwise.\n */\nstatic int validate_pointer(uintptr_t base, size_t size) {\n\tuintptr_t end  = size ? (base + (size - 1)) : base;\n\tuintptr_t page_base = base >> 12;\n\tuintptr_t page_end  =  end >> 12;\n\tfor (uintptr_t page = page_base; page <= page_end; ++page) {\n\t\tif ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0;\n\t\tunion PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12);\n\t\tif (!page_entry) return 0;\n\t\tif (!page_entry->bits.present) return 0;\n\t}\n\treturn 1;\n}\n\nextern char end[];\n\n/**\n * @brief Find the closest preceding symbol to an address.\n *\n * Scans the kernel symbol table to find the closest preceding\n * symbol to the address @p ip and stores its name in @p name,\n * returning the actual address of the symbol.\n *\n * As this uses the kernel symbol linkage table, it is only aware\n * of exported functions and objects, and can not provide any\n * information on static functions.\n *\n * @param ip Address to scan for\n * @param name (out) Name of matching symbol\n * @return Address of matching symbol\n */\nstatic uintptr_t matching_symbol(uintptr_t ip, char ** name) {\n\thashmap_t * symbols = ksym_get_map();\n\tuintptr_t best_match = 0;\n\tfor (size_t i = 0; i < symbols->size; ++i) {\n\t\thashmap_entry_t * x = symbols->entries[i];\n\t\twhile (x) {\n\t\t\tvoid* sym_addr = x->value;\n\t\t\tchar* sym_name = x->key;\n\t\t\tif ((uintptr_t)sym_addr < ip && (uintptr_t)sym_addr > best_match) {\n\t\t\t\tbest_match = (uintptr_t)sym_addr;\n\t\t\t\t*name = sym_name;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t}\n\t}\n\treturn best_match;\n}\n\n/**\n * @brief Display a traceback from the given ip and stack base.\n *\n * Walks the stack referenced by @p bp and attempts to find\n * kernel symbol names or module names. Stops when it reaches\n * a return address that looks invalid.\n *\n * You probably want to @see arch_fatal_prepare before calling\n * this to make sure you get a readable output.\n *\n * Note that symbol names are the closest symbol before the\n * given address, and will only ever be exported symbols,\n * so static functions will give the wrong name.\n *\n * We don't track symbols from modules at all at the moment,\n * so for addresses in module space the best we can do is\n * provide the name of the model and the offset into the loaded\n * file, so that's what we do.\n *\n * @param ip  IP address to assume is the top of the backtrace.\n * @param bp  Stack frame pointer.\n */\nstatic void dump_traceback(uintptr_t ip, uintptr_t bp) {\n\tint depth = 0;\n\tint max_depth = 20;\n\n\twhile (bp && ip && depth < max_depth) {\n\t\tdprintf(\" 0x%016zx \", ip);\n\t\tif (ip >= 0xffffffff80000000UL) {\n\t\t\tchar * name = NULL;\n\t\t\tstruct LoadedModule * mod = find_module(ip, &name);\n\t\t\tif (mod) {\n\t\t\t\tdprintf(\"\\a in module '%s', base address %#zx (offset %#zx)\\n\",\n\t\t\t\t\tname, mod->baseAddress, ip - mod->baseAddress);\n\t\t\t} else {\n\t\t\t\tdprintf(\"\\a (unknown)\\n\");\n\t\t\t}\n\t\t} else if (ip >= (uintptr_t)&end && ip <= 0x800000000000) {\n\t\t\tdprintf(\"\\a in userspace\\n\");\n\t\t} else if (ip <= (uintptr_t)&end) {\n\t\t\t/* Find symbol match */\n\t\t\tchar * name;\n\t\t\tuintptr_t addr = matching_symbol(ip, &name);\n\t\t\tif (!addr) {\n\t\t\t\tdprintf(\"\\a (no match)\\n\");\n\t\t\t} else {\n\t\t\t\tdprintf(\"\\a %s+0x%zx\\n\", name, ip-addr);\n\t\t\t}\n\t\t} else {\n\t\t\tdprintf(\"\\a (unknown)\\n\");\n\t\t}\n\t\tif (!validate_pointer(bp, sizeof(uintptr_t)) || !validate_pointer(bp + sizeof(uintptr_t), sizeof(uintptr_t))) {\n\t\t\tbreak;\n\t\t}\n\t\tip = *(uintptr_t*)(bp + sizeof(uintptr_t));\n\t\tbp = *(uintptr_t*)(bp);\n\t\tdepth++;\n\t}\n}\n\n/**\n * @brief Display a traceback from the rip and rbp of a register state.\n *\n * Primarily used to dump tracebacks that led to unexpected interrupts.\n *\n * @param r Interrupt register context\n */\nstatic void safe_dump_traceback(struct regs * r) {\n\tdump_traceback(r->rip, r->rbp);\n}\n\n/**\n * @brief Display a traceback from the current call context.\n */\nvoid arch_dump_traceback(void) {\n\tdump_traceback((uintptr_t)arch_dump_traceback+1, (uintptr_t)__builtin_frame_address(0));\n}\n\n/**\n * @brief Map in more pages for a userspace stack.\n *\n * Allows for soft expansion of the stack downards on a page fault.\n *\n * @param fromAddr The low address to map, should be page aligned.\n */\nstatic int map_more_stack(uintptr_t fromAddr) {\n\tvolatile process_t * volatile proc = this_core->current_process;\n\n\t/* Is this thread the process leader? */\n\tif (proc->group != 0) {\n\t\tproc = process_from_pid(proc->group);\n\t}\n\n\tif (!proc) return 0;\n\n\t/* Make sure nothing else is going to mess with this process's page tables */\n\tspin_lock(proc->image.lock);\n\n\t/* Map more stack! */\n\tfor (uintptr_t i = fromAddr; i < proc->image.userstack; i += 0x1000) {\n\t\tunion PML * page = mmu_get_page(i, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t}\n\n\t/* Update the saved stack address */\n\tproc->image.userstack = fromAddr;\n\n\tspin_unlock(proc->image.lock);\n\treturn 1;\n}\n\n/**\n * @brief Handle fatal exceptions.\n *\n * Prepares for a fatal event, prints information on the running\n * process and the cause of the panic, dumps the register state,\n * prints a backtrace, and then hard loops.\n *\n * @param desc Textual description of the panic cause.\n * @param r    Interrupt register context\n * @param faulting_address When available, the address that was accessed leading to this fault.\n */\nstatic void panic(const char * desc, struct regs * r, uintptr_t faulting_address) {\n\t/* Stop all other cores */\n\tarch_fatal_prepare();\n\n\t/* Print the description, current process, cause */\n\tdprintf(\"\\033[31mPanic!\\033[0m %s pid=%d (%s) at %#zx\\n\", desc,\n\t\tthis_core->current_process ? (int)this_core->current_process->id : 0,\n\t\tthis_core->current_process ? this_core->current_process->name : \"kernel\",\n\t\tfaulting_address\n\t);\n\n\t/* Dump register state */\n\tdprintf(\n\t\t\"Registers at interrupt:\\n\"\n\t\t\"  $rip=0x%016lx\\n\"\n\t\t\"  $rsi=0x%016lx,$rdi=0x%016lx,$rbp=0x%016lx,$rsp=0x%016lx\\n\"\n\t\t\"  $rax=0x%016lx,$rbx=0x%016lx,$rcx=0x%016lx,$rdx=0x%016lx\\n\"\n\t\t\"  $r8= 0x%016lx,$r9= 0x%016lx,$r10=0x%016lx,$r11=0x%016lx\\n\"\n\t\t\"  $r12=0x%016lx,$r13=0x%016lx,$r14=0x%016lx,$r15=0x%016lx\\n\"\n\t\t\"  cs=0x%016lx  ss=0x%016lx rflags=0x%016lx int=0x%02lx err=0x%02lx\\n\",\n\t\tr->rip,\n\t\tr->rsi, r->rdi, r->rbp, r->rsp,\n\t\tr->rax, r->rbx, r->rcx, r->rdx,\n\t\tr->r8, r->r9, r->r10, r->r11,\n\t\tr->r12, r->r13, r->r14, r->r15,\n\t\tr->cs, r->ss, r->rflags, r->int_no, r->err_code\n\t);\n\n\t/* Dump GS segment register information */\n\tuint32_t gs_base_low, gs_base_high;\n\tasm volatile ( \"rdmsr\" : \"=a\" (gs_base_low), \"=d\" (gs_base_high): \"c\" (0xc0000101) );\n\tuint32_t kgs_base_low, kgs_base_high;\n\tasm volatile ( \"rdmsr\" : \"=a\" (kgs_base_low), \"=d\" (kgs_base_high): \"c\" (0xc0000102) );\n\tdprintf(\"  gs=0x%08x%08x kgs=0x%08x%08x\\n\",\n\t\tgs_base_high, gs_base_low, kgs_base_high, kgs_base_low);\n\n\t/* Walk the call stack from before the interrupt */\n\tsafe_dump_traceback(r);\n\n\t/* Stop this core */\n\tarch_fatal();\n}\n\n/**\n * @brief Debug interrupt\n *\n * Called when a CPU is single-stepping. We need to reset\n * the single-step flag in RFLAGS and if we were actually\n * debugging the current process we need to trigger a ptrace\n * SINGLESTEP event. This should also return immediately\n * from the syscall handler.\n *\n * @param r Interrupt register context\n * @return Register context, which should be unmodified.\n */\nstatic void _debug_int(struct regs * r) {\n\t/* Unset the debug flag */\n\tr->rflags &= ~(1 << 8);\n\n\t/* If the current process was debugging, trigger a SINGLESTEP event. */\n\tif (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) {\n\t\tptrace_signal(SIGTRAP, PTRACE_EVENT_SINGLESTEP);\n\t}\n}\n\n/**\n * @brief Double fault should always panic.\n */\nstatic void _double_fault(struct regs * r) {\n\tpanic(\"Double fault\", r, 0);\n}\n\n/**\n * @brief GPF handler.\n *\n * Mostly this is separated from other exceptions because\n * GPF should cause SIGSEGV rather than SIGILL? I think?\n *\n * @param r Interrupt register context\n */\nstatic void _general_protection_fault(struct regs * r) {\n\t/* Were we in the kernel? */\n\tif (!this_core->current_process || r->cs == 0x08) {\n\t\t/* Then that's a panic. */\n\t\tpanic(\"GPF in kernel\", r, 0);\n\t}\n\n\t/* Else, segfault the current process. */\n\tsend_signal(this_core->current_process->id, SIGSEGV, 1);\n}\n\n/**\n * @brief Page fault handler.\n *\n * Handles magic return addresses, stack expansions, maybe\n * later will handle COW or mmap'd filed... otherwise,\n * mostly segfaults.\n *\n * @param r Interrupt register context\n */\nstatic void _page_fault(struct regs * r) {\n\t/* Obtain the \"cause\" address */\n\tuintptr_t faulting_address;\n\tasm volatile(\"mov %%cr2, %0\" : \"=r\"(faulting_address));\n\n\t/* magic ret-from-sig address */\n\tif (faulting_address == 0x516) {\n\t\treturn_from_signal_handler(r);\n\t\treturn;\n\t}\n\n\tif ((r->err_code & 3) == 3) {\n\t\t/* This is probably a COW page? */\n\t\textern int mmu_copy_on_write(uintptr_t address);\n\t\tif (!mmu_copy_on_write(faulting_address)) return;\n\t}\n\n\t/* Was this a kernel page fault? Those are always a panic. */\n\tif (!this_core->current_process || r->cs == 0x08) {\n\t\tpanic(\"Page fault in kernel\", r, faulting_address);\n\t}\n\n\t/* Page was present but not writable */\n\n\t/* Quietly map more stack if it was a viable stack address. */\n\tif (faulting_address < 0x800000000000 && faulting_address > 0x700000000000) {\n\t\tif (map_more_stack(faulting_address & 0xFFFFffffFFFFf000)) return;\n\t}\n\n\t/* Otherwise, segfault the current process. */\n\tsend_signal(this_core->current_process->id, SIGSEGV, 1);\n}\n\n/**\n * @brief AP-local timer signal.\n *\n * Update clocks and switch task gracefully.\n *\n * @param r Interrupt register context\n * @return Register state after resume from task task switch.\n */\nstatic void _local_timer(struct regs * r) {\n\textern void arch_update_clock(void);\n\tarch_update_clock();\n\tif (r->cs != 0x08) switch_task(1);\n}\n\n/**\n * @brief Handle an exception interrupt.\n *\n * @param r           Interrupt register context\n * @param description Textual description of the exception, for panic messages.\n */\nstatic void _exception(struct regs * r, const char * description, int signum) {\n\t/* If we were in kernel space, this is a panic */\n\tif (!this_core->current_process || r->cs == 0x08) {\n\t\tpanic(description, r, r->int_no);\n\t}\n\t/* Otherwise, these interrupts should trigger a signal */\n\tsend_signal(this_core->current_process->id, signum, 1);\n}\n\n/**\n * @brief Handle an installable interrupt. This handles PIC IRQs\n *        that need to be acknowledged.\n *\n * @param r    Interrupt register context\n * @param irq  Translated IRQ number\n */\nstatic void _handle_irq(struct regs * r, int irq) {\n\tfor (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) {\n\t\tirq_handler_chain_t handler = irq_routines[i * IRQ_CHAIN_SIZE + irq];\n\t\tif (!handler) break;\n\t\tif (handler(r)) return;\n\t}\n\n\t/* Unhandled */\n\tirq_ack(irq);\n}\n\n#define EXC(i,n,s) case i: _exception(r, n, s); break;\n#define IRQ(i) case i: _handle_irq(r,i-32); break;\n\nvoid isr_handler_inner(struct regs * r) {\n\tswitch (r->int_no) {\n\t\tEXC(0,\"divide-by-zero\",SIGFPE);\n\t\tcase 1: _debug_int(r); return;\n\t\t/* NMI doesn't reach here, we use it as a panic signal. */\n\t\tEXC(3,\"breakpoint\",SIGTRAP); /* TODO: This should map to a ptrace event */\n\t\tEXC(4,\"overflow\",SIGFPE);\n\t\tEXC(5,\"bound range exceeded\",SIGBUS);\n\t\tEXC(6,\"invalid opcode\",SIGILL);\n\t\tEXC(7,\"device not available\",SIGBUS);\n\t\tcase 8: _double_fault(r); break;\n\t\t/* 9 is a legacy exception that shouldn't happen */\n\t\tEXC(10,\"invalid TSS\",SIGBUS);\n\t\tEXC(11,\"segment not present\",SIGBUS);\n\t\tEXC(12,\"stack-segment fault\",SIGBUS);\n\t\tcase 13: _general_protection_fault(r); break;\n\t\tcase 14: _page_fault(r); break;\n\t\t/* 15 is reserved */\n\t\tEXC(16,\"floating point exception\",SIGFPE);\n\t\tEXC(17,\"alignment check\",SIGBUS);\n\t\tEXC(18,\"machine check\",SIGBUS);\n\t\tEXC(19,\"SIMD floating-point exception\",SIGFPE);\n\t\tEXC(20,\"virtualization exception\",SIGBUS);\n\t\tEXC(21,\"control protection exception\",SIGBUS);\n\t\t/* 22 through 27 are reserved */\n\t\tEXC(28,\"hypervisor injection exception\",SIGBUS);\n\t\tEXC(29,\"VMM communication exception\",SIGBUS);\n\t\tEXC(30,\"security exception\",SIGBUS);\n\t\t/* 31 is reserved */\n\n\t\t/* 16 IRQs that go to the general IRQ chain */\n\t\tIRQ(32);\n\t\tIRQ(33);\n\t\tIRQ(34);\n\t\tIRQ(35);\n\t\tIRQ(36);\n\t\tIRQ(37);\n\t\tIRQ(38);\n\t\tcase 39: break; /* Except the spurious IRQ, just ignore that */\n\t\tIRQ(40);\n\t\tIRQ(41);\n\t\tIRQ(42);\n\t\tIRQ(43);\n\t\tIRQ(44);\n\t\tIRQ(45);\n\t\tIRQ(46);\n\t\tIRQ(47);\n\n\t\t/* Local interrupts that make it here. */\n\t\tcase 123: _local_timer(r); return;\n\t\tcase 127: syscall_handler(r); return;\n\n\t\t/* Other interrupts that don't make it here:\n\t\t *   124: TLB shootdown, we just reload CR3 in the handler.\n\t\t *   125: Fatal signal, jumps straight to a cli/hlt loop, though I think this just yields an NMI instead?\n\t\t *   126: Quiet wakeup, do we even use this anymore?\n\t\t */\n\n\t\tdefault: panic(\"Unexpected interrupt\",r,0);\n\t}\n\n\tif (this_core->current_process == this_core->kernel_idle_task && process_queue && process_queue->head) {\n\t\t/* If this is kidle and we got here, instead of finishing the interrupt\n\t\t * we can just switch task and there will probably be something else\n\t\t * to run that was awoken by the interrupt. */\n\t\tswitch_next();\n\t}\n}\n\nvoid isr_handler(struct regs * r) {\n\tint from_userspace = r->cs != 0x08;\n\n\tif (from_userspace && this_core->current_process) {\n\t\tthis_core->current_process->time_switch = arch_perf_timer();\n\t}\n\n\tisr_handler_inner(r);\n\n\tif (from_userspace && this_core->current_process) {\n\t\tprocess_check_signals(r);\n\t\tupdate_process_times_on_exit();\n\t}\n}\n\nvoid syscall_centry(struct regs * r) {\n\tthis_core->current_process->time_switch = arch_perf_timer();\n\n\tsyscall_handler(r);\n\n\tprocess_check_signals(r);\n\tupdate_process_times_on_exit();\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/irq.S",
    "content": "/**\n * @file kernel/arch/x86_64/irq.S\n * @brief x86-64 interrupt entry points\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n.section .text\n.align 8\n\n.macro IRQ index byte\n    .global _irq\\index\n    .type _irq\\index, @function\n    _irq\\index:\n        pushq $0x00\n        pushq $\\byte\n        jmp isr_common\n.endm\n\n.macro ISR_NOERR index\n    .global _isr\\index\n    .type _isr\\index, @function\n    _isr\\index:\n        pushq $0x00\n        pushq $\\index\n        jmp isr_common\n.endm\n\n.macro ISR_ERR index\n    .global _isr\\index\n    .type _isr\\index, @function\n    _isr\\index:\n        pushq $\\index\n        jmp isr_common\n.endm\n\n/* Interrupt Requests */\nISR_NOERR 0\nISR_NOERR 1\n//ISR_NOERR 2\nISR_NOERR 3\nISR_NOERR 4\nISR_NOERR 5\nISR_NOERR 6\nISR_NOERR 7\nISR_ERR   8\nISR_NOERR 9\nISR_ERR   10\nISR_ERR   11\nISR_ERR   12\nISR_ERR   13\nISR_ERR   14\nISR_NOERR 15\nISR_NOERR 16\nISR_ERR   17\nISR_NOERR 18\nISR_NOERR 19\nISR_NOERR 20\nISR_ERR   21\nISR_NOERR 22\nISR_NOERR 23\nISR_NOERR 24\nISR_NOERR 25\nISR_NOERR 26\nISR_NOERR 27\nISR_NOERR 28\nISR_ERR   29\nISR_ERR   30\nISR_NOERR 31\nIRQ 0, 32\nIRQ 1, 33\nIRQ 2, 34\nIRQ 3, 35\nIRQ 4, 36\nIRQ 5, 37\nIRQ 6, 38\nIRQ 7, 39\nIRQ 8, 40\nIRQ 9, 41\nIRQ 10, 42\nIRQ 11, 43\nIRQ 12, 44\nIRQ 13, 45\nIRQ 14, 46\nIRQ 15, 47\n\n/* syscall entry point */\nISR_NOERR 127\n\n.global _isr123\n.type _isr123, @function\n_isr123:\n    /* Acknowledge IPI */\n    pushq %r12\n    mov (lapic_final)(%rip), %r12\n    add $0xb0, %r12\n    movl $0, (%r12)\n    popq %r12\n    /* Then we can proceed! */\n    pushq $0x00\n    pushq $123\n    jmp isr_common\n\n\n.global _isr124\n.type _isr124, @function\n_isr124:\n    pushq %r12\n    mov %cr3, %r12\n    mov %r12, %cr3\n    mov (lapic_final)(%rip), %r12\n    add $0xb0, %r12\n    movl $0, (%r12)\n    popq %r12\n    iretq\n\n/* No op, used to signal sleeping processor to wake and check the queue. */\n.extern lapic_final\n.global _isr126\n.type _isr126, @function\n_isr126:\n    pushq %r12\n    mov (lapic_final)(%rip), %r12\n    add $0xb0, %r12\n    movl $0, (%r12)\n    popq %r12\n    iretq\n\n/* Fatal signal, stop everything. */\n.global _isr125\n.type _isr125, @function\n_isr125:\n    cli\n1:\n    hlt\n    jmp 1b\n\n/* Fatal signal, stop everything. */\n.global _isr2\n.type _isr2, @function\n_isr2:\n    cli\n1:\n    hlt\n    jmp 1b\n\n.macro _swapgs\n    cmpq $8, 24(%rsp)\n    je 1f\n    swapgs\n1:\n.endm\n\n.extern isr_handler\n.type isr_handler, @function\n\n.global isr_common\nisr_common:\n    /* Save all registers */\n    _swapgs\n    push %rax\n    push %rbx\n    push %rcx\n    push %rdx\n    push %rsi\n    push %rdi\n    push %rbp\n    push %r8\n    push %r9\n    push %r10\n    push %r11\n    push %r12\n    push %r13\n    push %r14\n    push %r15\n\n    cld\n\n    /* Call interrupt handler */\n    mov %rsp, %rdi\n    call isr_handler\n\n    /* Restore all registers */\n    pop %r15\n    pop %r14\n    pop %r13\n    pop %r12\n    pop %r11\n    pop %r10\n    pop %r9\n    pop %r8\n    pop %rbp\n    pop %rdi\n    pop %rsi\n    pop %rdx\n    pop %rcx\n    pop %rbx\n    pop %rax\n\n    _swapgs\n\n    /* Cleanup error code and interrupt # */\n    add $16, %rsp\n\n    /* Return from interrupt  */\n    iretq\n\n\n.global arch_save_context\n.type arch_save_context, @function\narch_save_context:\n    leaq 8(%rsp), %rax\n    movq %rax, 0(%rdi)\n    movq %rbp, 8(%rdi)\n    movq (%rsp), %rax\n    movq %rax, 16(%rdi)\n    movq $0xc0000100, %rcx\n    rdmsr\n    movl %eax, 24(%rdi)\n    movl %edx, 28(%rdi)\n    movq %rbx, 32(%rdi)\n    movq %r12, 40(%rdi)\n    movq %r13, 48(%rdi)\n    movq %r14, 56(%rdi)\n    movq %r15, 64(%rdi)\n    xor %rax, %rax\n    retq\n\n.global arch_restore_context\n.type arch_restore_context, @function\narch_restore_context:\n    mov  %gs:0x10,%rax\n    cmp  %gs:0x0,%rax\n    je   1f\n    lock andl $0xFFFFfff7,0x14(%rax)\n1:\n    movq 0(%rdi), %rsp\n    movq 8(%rdi), %rbp\n    movl 24(%rdi), %eax\n    movl 28(%rdi), %edx\n    movq $0xc0000100, %rcx\n    wrmsr\n    movq 32(%rdi), %rbx\n    movq 40(%rdi), %r12\n    movq 48(%rdi), %r13\n    movq 56(%rdi), %r14\n    movq 64(%rdi), %r15\n    movq $1, %rax\n    jmpq *16(%rdi)\n\n.global arch_enter_tasklet\n.type arch_enter_tasklet, @function\narch_enter_tasklet:\n    popq %rdi\n    popq %rsi\n    jmpq *%rsi\n\n\n.extern syscall_centry\n.global syscall_entry\n.type syscall_entry, @function\nsyscall_entry:\n    swapgs             /* SYSCALL only happens from userspace, so we must always swap gs */\n    mov %rsp, %gs:0x78 /* Store user RSP temporarily */\n    mov %gs:0x70, %rsp /* Restore kernel stack for this thread */\n\n    /* Normal `struct regs` layout, same as what we'd get on an interrupt */\n    pushq $0x23        /* SS */\n    pushq %gs:0x78     /* RSP */\n    push %r11          /* RFLAGS - SYSCALL stores in r11 */\n    pushq $0x2b        /* CS */\n    push %rcx          /* RIP - SYSCALL stores in rcx */\n\n    pushq $0           /* Dummy error code */\n    pushq $0           /* Dummy interrupt number */\n\n    push %rax\n    push %rbx\n    pushq $0           /* rcx is not valid, set to zero */\n    push %rdx\n    push %rsi\n    push %rdi\n    push %rbp\n    push %r8\n    push %r9\n    push %r10\n    pushq $0           /* r11 is not valid, set to zero */\n    push %r12\n    push %r13\n    push %r14\n    push %r15\n\n    mov %rsp, %rdi\n    call syscall_centry\n\n    pop %r15\n    pop %r14\n    pop %r13\n    pop %r12\n    add $8, %rsp\n    pop %r10\n    pop %r9\n    pop %r8\n    pop %rbp\n    pop %rdi\n    pop %rsi\n    pop %rdx\n    add $8, %rsp\n    pop %rbx\n    pop %rax\n\n    add $16, %rsp\n\n    pop %rcx\n    add $8, %rsp\n    pop %r11\n    pop %rsp\n\n    swapgs\n    sysretq\n"
  },
  {
    "path": "kernel/arch/x86_64/link.ld",
    "content": "OUTPUT_FORMAT(elf64-x86-64)\nENTRY(start)\n\nSECTIONS\n{\n\t. = 1M;\n\tphys = .;\n\n\t.text BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.multiboot)\n\t\t*(.bootstrap)\n\t\tcode = .;\n\t\t*(.text)\n\t\t*(.shit)\n\t}\n\n\t.rodata BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.rodata)\n\t}\n\t.rela.dyn BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t_rela_start = .;\n\t\t*(.rela)\n\t\t*(.rela.text)\n\t\t*(.rela.got)\n\t\t*(.rela.plt)\n\t\t*(.rela.bss)\n\t\t*(.rela.ifunc)\n\t\t*(.rela.text.*)\n\t\t*(.rela.data)\n\t\t*(.rela.data.*)\n\t\t*(.rela.rodata)\n\t\t*(.rela.rodata*)\n\t\t*(.rela.dyn)\n\t\t_rela_end = .;\n\t}\n\n\t.data BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tdata = .;\n\t\t*(.data)\n\t\t*(.symbols)\n\t\tPROVIDE(kernel_symbols_start = .);\n\t\tPROVIDE(kernel_symbols_end = .);\n\t}\n\n\t.bss BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tPROVIDE(bss_start = .);\n\t\tbss = .;\n\t\t*(COMMON)\n\t\t*(.bss)\n\t\t*(.stack)\n\t}\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.eh_frame)\n\t\t*(.note.gnu.build-id)\n\t}\n\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/main.c",
    "content": "/**\n * @file  kernel/arch/x86_64/main.c\n * @brief Intel/AMD x86-64 (IA64/amd64) architecture-specific startup.\n *\n * Parses multiboot data, sets up GDT/IDT/TSS, initializes PML4 paging,\n * and sets up PC device drivers (PS/2, port I/O, serial).\n */\n#include <kernel/types.h>\n#include <kernel/multiboot.h>\n#include <kernel/symboltable.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/hashmap.h>\n#include <kernel/process.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n#include <kernel/video.h>\n#include <kernel/generic.h>\n#include <kernel/gzip.h>\n#include <kernel/ramdisk.h>\n#include <kernel/args.h>\n#include <kernel/ksym.h>\n#include <kernel/misc.h>\n#include <kernel/version.h>\n#include <kernel/elf.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/cmos.h>\n#include <kernel/arch/x86_64/pml.h>\n\n#include <errno.h>\n\nextern void arch_clock_initialize(void);\n\nextern char end[];\nextern unsigned long tsc_mhz;\n\nextern void gdt_install(void);\nextern void idt_install(void);\nextern void pic_initialize(void);\nextern void pit_initialize(void);\nextern void smp_initialize(void);\nextern void portio_initialize(void);\nextern void ps2hid_install(void);\nextern void serial_initialize(void);\nextern void fbterm_initialize(void);\nextern void pci_remap(void);\nextern void mmu_init(size_t memsize, uintptr_t firstFreePage);\n\nstruct multiboot * mboot_struct = NULL;\nint mboot_is_2 = 0;\n\nstatic int _serial_debug = 1;\n#define EARLY_LOG_DEVICE 0x3F8\nstatic size_t _early_log_write(size_t size, uint8_t * buffer) {\n\tif (!_serial_debug) return size;\n\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\toutportb(EARLY_LOG_DEVICE, buffer[i]);\n\t}\n\treturn size;\n}\n\nstatic void early_log_initialize(void) {\n\toutportb(EARLY_LOG_DEVICE + 3, 0x03); /* Disable divisor mode, set parity */\n\tprintf_output = &_early_log_write;\n}\n\nstatic uintptr_t highest_valid_address = 0;\nstatic uintptr_t highest_kernel_address = (uintptr_t)&end;\n\nstruct MB2_TagHeader {\n\tuint32_t type;\n\tuint32_t size;\n};\n\nvoid * mboot2_find_next(char * current, uint32_t type) {\n\tchar * header = current;\n\twhile ((uintptr_t)header & 7) header++;\n\tstruct MB2_TagHeader * tag = (void*)header;\n\twhile (1) {\n\t\tif (tag->type == 0) return NULL;\n\t\tif (tag->type == type) return tag;\n\t\t/* Next tag */\n\t\theader += tag->size;\n\t\twhile ((uintptr_t)header & 7) header++;\n\t\ttag = (void*)header;\n\t}\n}\n\nvoid * mboot2_find_tag(void * fromStruct, uint32_t type) {\n\tchar * header = (void*)fromStruct;\n\theader += 8;\n\treturn mboot2_find_next(header, type);\n}\n\nstruct MB2_MemoryMap {\n\tstruct MB2_TagHeader head;\n\tuint32_t entry_size;\n\tuint32_t entry_version;\n\tchar entries[];\n};\n\nstruct MB2_MemoryMap_Entry {\n\tuint64_t base_addr;\n\tuint64_t length;\n\tuint32_t type;\n\tuint32_t reserved;\n};\n\nstruct MB2_Framebuffer {\n\tstruct MB2_TagHeader head;\n\tuint64_t addr;\n\tuint32_t pitch;\n\tuint32_t width;\n\tuint32_t height;\n\tuint8_t bpp;\n\tuint8_t fb_type;\n};\n\nstruct MB2_Module {\n\tstruct MB2_TagHeader head;\n\tuint32_t mod_start;\n\tuint32_t mod_end;\n\tuint8_t  cmdline[];\n};\n\nstatic void multiboot2_initialize(void * mboot) {\n\tmboot_is_2 = 1;\n\tdprintf(\"multiboot: Started with a Multiboot 2 loader\\n\");\n\n\tstruct MB2_MemoryMap * mmap = mboot2_find_tag(mboot, 6);\n\tif (!mmap) {\n\t\tprintf(\"fatal: unable to boot without memory map from loader\\n\");\n\t\tarch_fatal();\n\t}\n\n\tchar * entry = mmap->entries;\n\twhile ((uintptr_t)entry < (uintptr_t)mmap + mmap->head.size) {\n\t\tstruct MB2_MemoryMap_Entry * this = (void*)entry;\n\t\tif (this->type == 1 && this->length && this->base_addr + this->length - 1> highest_valid_address) {\n\t\t\thighest_valid_address = this->base_addr + this->length - 1;\n\t\t}\n\t\tentry += mmap->entry_size;\n\t}\n\n\tstruct MB2_Module * mod = mboot2_find_tag(mboot, 3);\n\twhile (mod) {\n\t\tuintptr_t addr = (uintptr_t)mod->mod_end;\n\t\tif (addr > highest_kernel_address) highest_kernel_address = addr;\n\t\tmod = mboot2_find_next((char*)mod + mod->head.size, 3);\n\t}\n\n\t/* Round the max address up a page */\n\thighest_kernel_address = (highest_kernel_address + 0xFFF) & 0xFFFFffffFFFFf000;\n}\n\nstatic void multiboot_initialize(struct multiboot * mboot) {\n\n\tdprintf(\"multiboot: Started with a Multiboot 1 loader\\n\");\n\n\tif (!(mboot->flags & MULTIBOOT_FLAG_MMAP)) {\n\t\tprintf(\"fatal: unable to boot without memory map from loader\\n\");\n\t\tarch_fatal();\n\t}\n\n\tmboot_memmap_t * mmap = (void *)(uintptr_t)mboot->mmap_addr;\n\n\tif ((uintptr_t)mmap + mboot->mmap_length > highest_kernel_address) {\n\t\thighest_kernel_address = (uintptr_t)mmap + mboot->mmap_length;\n\t}\n\n\twhile ((uintptr_t)mmap < mboot->mmap_addr + mboot->mmap_length) {\n\t\tif (mmap->type == 1 && mmap->length && mmap->base_addr + mmap->length - 1> highest_valid_address) {\n\t\t\thighest_valid_address = mmap->base_addr + mmap->length - 1;\n\t\t}\n\t\tmmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t));\n\t}\n\n\tif (mboot->flags & MULTIBOOT_FLAG_MODS) {\n\t\tmboot_mod_t * mods = (mboot_mod_t *)(uintptr_t)mboot->mods_addr;\n\t\tfor (unsigned int i = 0; i < mboot->mods_count; ++i) {\n\t\t\tuintptr_t addr = (uintptr_t)mods[i].mod_end;\n\t\t\tif (addr > highest_kernel_address) {\n\t\t\t\thighest_kernel_address = addr;\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Round the max address up a page */\n\thighest_kernel_address = (highest_kernel_address + 0xFFF) & 0xFFFFffffFFFFf000;\n}\n\nvoid mboot_unmark_valid_memory(void) {\n\tsize_t frames_marked = 0;\n\tif (mboot_is_2) {\n\t\tstruct MB2_MemoryMap * mmap = mboot2_find_tag(mboot_struct, 6);\n\t\tchar * entry = mmap->entries;\n\t\twhile ((uintptr_t)entry < (uintptr_t)mmap + mmap->head.size) {\n\t\t\tstruct MB2_MemoryMap_Entry * this = (void*)entry;\n\t\t\tif (this->type == 1) {\n\t\t\t\tfor (uintptr_t base = this->base_addr; base < this->base_addr + (this->length & 0xFFFFffffFFFFf000); base += 0x1000) {\n\t\t\t\t\tmmu_frame_clear(base);\n\t\t\t\t\tframes_marked++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tentry += mmap->entry_size;\n\t\t}\n\t} else {\n\t\tmboot_memmap_t * mmap = mmu_map_from_physical((uintptr_t)mboot_struct->mmap_addr);\n\t\twhile ((uintptr_t)mmap < (uintptr_t)mmu_map_from_physical(mboot_struct->mmap_addr + mboot_struct->mmap_length)) {\n\t\t\tif (mmap->type == 1) {\n\t\t\t\tfor (uintptr_t base = mmap->base_addr; base < mmap->base_addr + (mmap->length & 0xFFFFffffFFFFf000); base += 0x1000) {\n\t\t\t\t\tmmu_frame_clear(base);\n\t\t\t\t\tframes_marked++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t));\n\t\t}\n\t}\n}\n\nstatic void symbols_install(uint64_t base) {\n\tksym_install();\n\tkernel_symbol_t * k = (kernel_symbol_t *)&kernel_symbols_start;\n\twhile ((uintptr_t)k < (uintptr_t)&kernel_symbols_end) {\n\t\tksym_bind(k->name, (void*)(k->addr + base));\n\t\tk = (kernel_symbol_t *)((uintptr_t)k + sizeof *k + strlen(k->name) + 1);\n\t}\n}\n\n/**\n * @brief Initializes the page attribute table.\n * FIXME: This seems to be assuming the lower entries are\n *        already sane - we should probably initialize all\n *        of the entries ourselves.\n */\nvoid pat_initialize(void) {\n\tasm volatile (\n\t\t\"mov $0x277, %%ecx\\n\" /* IA32_MSR_PAT */\n\t\t\"rdmsr\\n\"\n\t\t\"or $0x1000000, %%edx\\n\" /* set bit 56 */\n\t\t\"and $0xf9ffffff, %%edx\\n\" /* unset bits 57, 58 */\n\t\t\"wrmsr\\n\"\n\t\t: : : \"ecx\", \"edx\", \"eax\"\n\t);\n}\n\n/**\n * @brief Turns on the floating-point unit.\n *\n * Enables a few bits so we can get SSE.\n *\n * We don't do any fancy lazy FPU reload as x86-64 assumes a wide\n * variety of FPU-provided registers are available so most userspace\n * code will be messing with the FPU anyway and we'd probably just\n * waste time with all the interrupts turning it off and on...\n */\nvoid fpu_initialize(void) {\n\tasm volatile (\n\t\t\"clts\\n\"\n\t\t\"mov %%cr0, %%rax\\n\"\n\t\t\"and $0xFFFD, %%ax\\n\"\n\t\t\"or $0x10, %%ax\\n\"\n\t\t\"mov %%rax, %%cr0\\n\"\n\t\t\"fninit\\n\"\n\t\t\"mov %%cr0, %%rax\\n\"\n\t\t\"and $0xfffb, %%ax\\n\"\n\t\t\"or  $0x0002, %%ax\\n\"\n\t\t\"mov %%rax, %%cr0\\n\"\n\t\t\"mov %%cr4, %%rax\\n\"\n\t\t\"or $0x600, %%rax\\n\"\n\t\t\"mov %%rax, %%cr4\\n\"\n\t\t\"push $0x1F80\\n\"\n\t\t\"ldmxcsr (%%rsp)\\n\"\n\t\t\"addq $8, %%rsp\\n\"\n\t: : : \"rax\");\n}\n\nstatic void mount_ramdisk(uintptr_t addr, size_t len) {\n\tuint8_t * data = mmu_map_from_physical(addr);\n\tif (data[0] == 0x1F && data[1] == 0x8B) {\n\t\t/* Yes - decompress it first */\n\t\tdprintf(\"multiboot: Decompressing initial ramdisk...\\n\");\n\t\tuint32_t decompressedSize = *(uint32_t*)mmu_map_from_physical(addr + len - sizeof(uint32_t));\n\t\tsize_t pageCount = (((size_t)decompressedSize + 0xFFF) & ~(0xFFF)) >> 12;\n\t\tuintptr_t physicalAddress = mmu_allocate_n_frames(pageCount) << 12;\n\n\t\tif (physicalAddress == (uintptr_t)-1) {\n\t\t\tdprintf(\"gzip: failed to allocate pages\\n\");\n\t\t\treturn;\n\t\t}\n\t\tgzip_inputPtr = (void*)data;\n\t\tgzip_outputPtr = mmu_map_from_physical(physicalAddress);\n\t\t/* Do the deed */\n\t\tif (gzip_decompress()) {\n\t\t\tdprintf(\"gzip: failed to decompress payload\\n\");\n\t\t\treturn;\n\t\t}\n\t\tramdisk_mount(physicalAddress, decompressedSize);\n\t\tdprintf(\"multiboot: Decompressed %lu kB to %u kB.\\n\",\n\t\t\t(len) / 1024,\n\t\t\t(decompressedSize) / 1024);\n\t\t/* Free the pages from the original mod */\n\t\tfor (size_t j = addr; j < addr + len; j += 0x1000) {\n\t\t\tmmu_frame_clear(j);\n\t\t}\n\t} else {\n\t\t/* No, or it doesn't look like one - mount it directly */\n\t\tdprintf(\"multiboot: Mounting uncompressed ramdisk.\\n\");\n\t\tramdisk_mount(addr, len);\n\t}\n}\n\n/**\n * @brief Decompress compressed ramdisks and hand them to the ramdisk driver.\n *\n * Reads through the list of modules passed by a multiboot-compatible loader\n * and determines if they are gzip-compressed, decompresses if they are, and\n * finally hands them to the VFS driver. The VFS ramdisk driver takes control\n * of linear sets of physical pages, and handles mapping them somewhere to\n * provide reads in userspace, as well as freeing them if requested.\n */\nvoid mount_multiboot_ramdisks(struct multiboot * mboot) {\n\t/* ramdisk_mount takes physical pages, it will map them itself. */\n\tif (mboot_is_2) {\n\t\tstruct MB2_Module * mod = mboot2_find_tag(mboot_struct, 3);\n\t\twhile (mod) {\n\t\t\tuintptr_t address = mod->mod_start;\n\t\t\tsize_t    length  = mod->mod_end - mod->mod_start;\n\t\t\tmount_ramdisk(address, length);\n\t\t\tmod = mboot2_find_next((char*)mod + mod->head.size, 3);\n\t\t}\n\t} else {\n\t\tmboot_mod_t * mods = mmu_map_from_physical(mboot->mods_addr);\n\t\tfor (unsigned int i = 0; i < mboot->mods_count; ++i) {\n\t\t\tuint64_t address = mods[i].mod_start;\n\t\t\tuint64_t length  = mods[i].mod_end - mods[i].mod_start;\n\t\t\tmount_ramdisk(address, length);\n\t\t}\n\t}\n}\n\n/**\n * x86-64: The kernel commandline is retrieved from the multiboot struct.\n */\nconst char * arch_get_cmdline(void) {\n\tif (mboot_is_2) {\n\t\tstruct loader { uint32_t type; uint32_t size; char name[]; } * loader = mboot2_find_tag(mboot_struct, 1);\n\t\tif (loader) {\n\t\t\treturn loader->name;\n\t\t}\n\t\treturn \"\";\n\t} else {\n\t\treturn mmu_map_from_physical(mboot_struct->cmdline);\n\t}\n}\n\n/**\n * x86-64: The bootloader name is retrieved from the multiboot struct.\n */\nconst char * arch_get_loader(void) {\n\tif (mboot_is_2) {\n\t\tstruct loader { uint32_t type; uint32_t size; char name[]; } * loader = mboot2_find_tag(mboot_struct, 2);\n\t\tif (loader) {\n\t\t\treturn loader->name;\n\t\t}\n\t} else if (mboot_struct->flags & MULTIBOOT_FLAG_LOADER) {\n\t\treturn mmu_map_from_physical(mboot_struct->boot_loader_name);\n\t}\n\treturn \"(unknown)\";\n}\n\n/**\n * x86-64: The GS register, which is set by a pair of MSRs, tracks per-CPU kernel state.\n */\nvoid arch_set_core_base(uintptr_t base) {\n\tasm volatile (\"wrmsr\" : : \"c\"(0xc0000101), \"d\"((uint32_t)(base >> 32)), \"a\"((uint32_t)(base & 0xFFFFFFFF)));\n\tasm volatile (\"wrmsr\" : : \"c\"(0xc0000102), \"d\"((uint32_t)(base >> 32)), \"a\"((uint32_t)(base & 0xFFFFFFFF)));\n\tasm volatile (\"swapgs\");\n}\n\nvoid arch_framebuffer_initialize(void) {\n\textern uint8_t * lfb_vid_memory;\n\textern uint16_t lfb_resolution_x;\n\textern uint16_t lfb_resolution_y;\n\textern uint32_t lfb_resolution_s;\n\textern uint16_t lfb_resolution_b;\n\n\tif (!mboot_is_2) {\n\t\tlfb_vid_memory = mmu_map_from_physical(mboot_struct->framebuffer_addr);\n\t\tlfb_resolution_x = mboot_struct->framebuffer_width;\n\t\tlfb_resolution_y = mboot_struct->framebuffer_height;\n\t\tlfb_resolution_s = mboot_struct->framebuffer_pitch;\n\t\tlfb_resolution_b = mboot_struct->framebuffer_bpp;\n\t} else {\n\t\tstruct MB2_Framebuffer * fb = mboot2_find_tag(mboot_struct, 8);\n\t\tif (fb) {\n\t\t\tlfb_vid_memory = mmu_map_from_physical(fb->addr);\n\t\t\tlfb_resolution_x = fb->width;\n\t\t\tlfb_resolution_y = fb->height;\n\t\t\tlfb_resolution_s = fb->pitch;\n\t\t\tlfb_resolution_b = fb->bpp;\n\t\t}\n\t}\n}\n\n/**\n * @brief x86-64 multiboot C entrypoint.\n *\n * Called by the x86-64 longmode bootstrap.\n */\nint kmain(struct multiboot * mboot, uint32_t mboot_mag, void* esp, uint64_t base) {\n\textern Elf64_Rela _rela_start[], _rela_end[];\n\tfor (Elf64_Rela * rela = _rela_start; rela < _rela_end; ++rela) {\n\t\tswitch (ELF64_R_TYPE(rela->r_info)) {\n\t\t\tcase R_X86_64_RELATIVE:\n\t\t\t\t*(uint64_t*)(rela->r_offset + base) = base + rela->r_addend;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* The debug log is over /dev/ttyS0, but skips the PTY interface; it's available\n\t * as soon as we can call printf(), which is as soon as we get to long mode. */\n\tearly_log_initialize();\n\n\tdprintf(\"%s %d.%d.%d-%s %s %s\\n\",\n\t\t__kernel_name,\n\t\t__kernel_version_major,\n\t\t__kernel_version_minor,\n\t\t__kernel_version_lower,\n\t\t__kernel_version_suffix,\n\t\t__kernel_version_codename,\n\t\t__kernel_arch);\n\n\t/* Initialize GS base */\n\tarch_set_core_base((uintptr_t)&processor_local_data[0]);\n\n\t/* Time the TSC and get the initial boot time from the RTC. */\n\tarch_clock_initialize();\n\n\t/* Parse multiboot data so we can get memory map, modules, command line, etc. */\n\tif (mboot_mag == 0x36d76289) {\n\t\tmultiboot2_initialize(mboot);\n\t} else {\n\t\tmultiboot_initialize(mboot);\n\t}\n\n\t/* multiboot memory is now mapped high, if you want it. */\n\tmboot_struct = mmu_map_from_physical((uintptr_t)mboot);\n\n\t/* memCount and maxAddress come from multiboot data */\n\tmmu_init(highest_valid_address, highest_kernel_address);\n\n\t/* With the MMU initialized, set up things required for the scheduler. */\n\tpat_initialize();\n\tsymbols_install(base);\n\tgdt_install();\n\tidt_install();\n\tfpu_initialize();\n\tpic_initialize();\n\n\t/* Early generic stuff */\n\tgeneric_startup();\n\n\t/* Should we override the TSC timing? */\n\tif (args_present(\"tsc_mhz\")) {\n\t\ttsc_mhz = atoi(args_value(\"tsc_mhz\"));\n\t}\n\n\tif (!args_present(\"debug\")) {\n\t\t_serial_debug = 0;\n\t}\n\n\t/* Scheduler is running and we have parsed the kcmdline, initialize video. */\n\tframebuffer_initialize();\n\tfbterm_initialize();\n\n\t/* Start up other cores and enable an appropriate preempt source. */\n\tsmp_initialize();\n\n\t/* Decompress and mount all initial ramdisks. */\n\tmount_multiboot_ramdisks(mboot_struct);\n\n\t/* Install generic PC device drivers. */\n\tps2hid_install();\n\tserial_initialize();\n\tportio_initialize();\n\n\t/* Yield to the generic main, which starts /bin/init */\n\treturn generic_main();\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/mmu.c",
    "content": "/**\n * @file  kernel/arch/x86_64/mmu.c\n * @brief Memory management facilities for x86-64\n *\n * Frame allocation and mapping routines for x86-64.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdint.h>\n#include <kernel/assert.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/spinlock.h>\n#include <kernel/misc.h>\n#include <kernel/mmu.h>\n#include <kernel/arch/x86_64/pml.h>\n\nextern void arch_tlb_shootdown(uintptr_t);\n\n/**\n * bitmap page allocator for 4KiB pages\n */\nstatic volatile uint32_t *frames;\nstatic size_t nframes;\nstatic size_t total_memory = 0;\nstatic size_t unavailable_memory = 0;\nstatic uint8_t * mem_refcounts = NULL;\n\n#define PAGE_SHIFT     12\n#define PAGE_SIZE      0x1000UL\n#define PAGE_SIZE_MASK 0xFFFFffffFFFFf000UL\n#define PAGE_LOW_MASK  0x0000000000000FFFUL\n\n#define LARGE_PAGE_SIZE 0x200000UL\n\n#define   USER_PML_ACCESS 0x07\n#define KERNEL_PML_ACCESS 0x03\n#define    LARGE_PAGE_BIT 0x80\n\n#define PDP_MASK 0x3fffffffUL\n#define  PD_MASK 0x1fffffUL\n#define  PT_MASK PAGE_LOW_MASK\n#define ENTRY_MASK 0x1FF\n\n#define PHYS_MASK 0x7fffffffffUL\n#define CANONICAL_MASK 0xFFFFffffFFFFUL\n\n#define INDEX_FROM_BIT(b)  ((b) >> 5)\n#define OFFSET_FROM_BIT(b) ((b) & 0x1F)\n\n/**\n * @brief Mark a physical page frame as in use.\n *\n * Sets the bitmap allocator bit for a frame.\n *\n * @param frame_addr Address of the frame (not index!)\n */\nvoid mmu_frame_set(uintptr_t frame_addr) {\n\t/* If the frame is within bounds... */\n\tif (frame_addr < nframes * PAGE_SIZE) {\n\t\tuint64_t frame  = frame_addr >> 12;\n\t\tuint64_t index  = INDEX_FROM_BIT(frame);\n\t\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\t\tframes[index]  |= ((uint32_t)1 << offset);\n\t\tasm (\"\" ::: \"memory\");\n\t}\n}\n\nstatic uintptr_t lowest_available = 0;\n\n/**\n * @brief Mark a physical page frame as available.\n *\n * Clears the bitmap allocator bit for a frame.\n *\n * @param frame_addr Address of the frame (not index!)\n */\nvoid mmu_frame_clear(uintptr_t frame_addr) {\n\t/* If the frame is within bounds... */\n\tif (frame_addr < nframes * PAGE_SIZE) {\n\t\tuint64_t frame  = frame_addr >> PAGE_SHIFT;\n\t\tuint64_t index  = INDEX_FROM_BIT(frame);\n\t\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\t\tframes[index]  &= ~((uint32_t)1 << offset);\n\t\tasm (\"\" ::: \"memory\");\n\t\tif (frame < lowest_available) lowest_available = frame;\n\t}\n}\n\n/**\n * @brief Determine if a physical page is available for use.\n *\n * @param frame_addr Address of the frame (not index!)\n * @returns 0 if available, 1 otherwise.\n */\nint mmu_frame_test(uintptr_t frame_addr) {\n\tif (!(frame_addr < nframes * PAGE_SIZE)) return 1;\n\tuint64_t frame  = frame_addr >> PAGE_SHIFT;\n\tuint64_t index  = INDEX_FROM_BIT(frame);\n\tuint32_t offset = OFFSET_FROM_BIT(frame);\n\tasm (\"\" ::: \"memory\");\n\treturn !!(frames[index] & ((uint32_t)1 << offset));\n}\n\nstatic spin_lock_t frame_alloc_lock = { 0 };\nstatic spin_lock_t kheap_lock = { 0 };\nstatic spin_lock_t mmio_space_lock = { 0 };\nstatic spin_lock_t module_space_lock = { 0 };\n\nvoid mmu_frame_release(uintptr_t frame_addr) {\n\tspin_lock(frame_alloc_lock);\n\tmmu_frame_clear(frame_addr);\n\tspin_unlock(frame_alloc_lock);\n}\n\n/**\n * @brief Find the first range of @p n contiguous frames.\n *\n * If a large enough region could not be found, results are fatal.\n */\nuintptr_t mmu_first_n_frames(int n) {\n\tfor (uint64_t i = 0; i < nframes * PAGE_SIZE; i += PAGE_SIZE) {\n\t\tint bad = 0;\n\t\tfor (int j = 0; j < n; ++j) {\n\t\t\tif (mmu_frame_test(i + PAGE_SIZE * j)) {\n\t\t\t\tbad = j + 1;\n\t\t\t}\n\t\t}\n\t\tif (!bad) {\n\t\t\treturn i / PAGE_SIZE;\n\t\t}\n\t}\n\n\tarch_fatal_prepare();\n\tdprintf(\"Failed to allocate %d contiguous frames.\\n\", n);\n\tarch_dump_traceback();\n\tarch_fatal();\n\treturn (uintptr_t)-1;\n}\n\n/**\n * @brief Find the first available frame from the bitmap.\n */\nuintptr_t mmu_first_frame(void) {\n\tuintptr_t i, j;\n\tfor (i = INDEX_FROM_BIT(lowest_available); i < INDEX_FROM_BIT(nframes); ++i) {\n\t\tif (frames[i] != (uint32_t)-1) {\n\t\t\tfor (j = 0; j < (sizeof(uint32_t)*8); ++j) {\n\t\t\t\tuint32_t testFrame = (uint32_t)1 << j;\n\t\t\t\tif (!(frames[i] & testFrame)) {\n\t\t\t\t\tuintptr_t out = (i << 5) + j;\n\t\t\t\t\tlowest_available = out + 1;\n\t\t\t\t\treturn out;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tarch_fatal_prepare();\n\tdprintf(\"Out of memory.\\n\");\n\tarch_dump_traceback();\n\tarch_fatal();\n\treturn (uintptr_t)-1;\n}\n\n/**\n * @brief Set the flags for a page, and allocate a frame for it if needed.\n *\n * Sets the page bits based on the the value of @p flags.\n * If @p page->bits.page is unset, a new frame will be allocated.\n */\nvoid mmu_frame_allocate(union PML * page, unsigned int flags) {\n\tif (page->bits.page == 0) {\n\t\tspin_lock(frame_alloc_lock);\n\t\tuintptr_t index = mmu_first_frame();\n\t\tmmu_frame_set(index << PAGE_SHIFT);\n\t\tpage->bits.page     = index;\n\t\tspin_unlock(frame_alloc_lock);\n\t}\n\tpage->bits.size     = 0;\n\tpage->bits.present  = 1;\n\tpage->bits.writable = (flags & MMU_FLAG_WRITABLE) ? 1 : 0;\n\tpage->bits.user     = (flags & MMU_FLAG_KERNEL)   ? 0 : 1;\n\tpage->bits.nocache  = (flags & MMU_FLAG_NOCACHE)  ? 1 : 0;\n\tpage->bits.writethrough  = (flags & MMU_FLAG_WRITETHROUGH)  ? 1 : 0;\n\tpage->bits.size     = (flags & MMU_FLAG_SPEC) ? 1 : 0;\n\tpage->bits.nx       = (flags & MMU_FLAG_NOEXECUTE) ? 1 : 0;\n}\n\n/**\n * @brief Map the given page to the requested physical address.\n */\nvoid mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr) {\n\tmmu_frame_set(physAddr);\n\tpage->bits.page = physAddr >> PAGE_SHIFT;\n\tmmu_frame_allocate(page, flags);\n}\n\n/* Initial memory maps loaded by boostrap */\n#define _pagemap __attribute__((aligned(PAGE_SIZE))) = {0}\nunion PML init_page_region[3][512] _pagemap;\nunion PML high_base_pml[512] _pagemap;\nunion PML heap_base_pml[512] _pagemap;\nunion PML heap_base_pd[512] _pagemap;\nunion PML heap_base_pt[512*3] _pagemap;\nunion PML low_base_pmls[34][512] _pagemap;\nunion PML twom_high_pds[64][512] _pagemap;\n\n/**\n * @brief Maps a frame address to a virtual address.\n *\n * Returns the virtual address within the general-purpose\n * identity mapping region for the given physical frame address.\n * This address is not suitable for some operations, such as MMIO.\n */\nvoid * mmu_map_from_physical(uintptr_t frameaddress) {\n\treturn (void*)(frameaddress | HIGH_MAP_REGION);\n}\n\nunion PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr) {\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\t/* Get the PML4 entry for this address */\n\tif (!root[pml4_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pdp[pdp_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tif (pdp[pdp_entry].bits.size) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pd[pd_entry].bits.present) {\n\t\treturn NULL;\n\t}\n\n\tif (pd[pd_entry].bits.size) {\n\t\treturn NULL;\n\t}\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\treturn (union PML *)&pt[pt_entry];\n}\n\n/**\n * @brief Find the physical address at a given virtual address.\n *\n * Calculates the physical address of the page backing the virtual\n * address @p virtAddr. If no page is mapped, a negative value\n * is returned indicating which level of the page directory is\n * unmapped from -1 (no PDP) to -4 (page not present in table).\n */\nuintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr) {\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\t/* Get the PML4 entry for this address */\n\tif (!root[pml4_entry].bits.present) return (uintptr_t)-1;\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pdp[pdp_entry].bits.present) return (uintptr_t)-2;\n\tif (pdp[pdp_entry].bits.size) return ((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT) | (virtAddr & PDP_MASK);\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pd[pd_entry].bits.present) return (uintptr_t)-3;\n\tif (pd[pd_entry].bits.size) return ((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT) | (virtAddr & PD_MASK);\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pt[pt_entry].bits.present) return (uintptr_t)-4;\n\treturn ((uintptr_t)pt[pt_entry].bits.page << PAGE_SHIFT) | (virtAddr & PT_MASK);\n}\n\n/**\n * @brief Obtain the page entry for a virtual address.\n *\n * Digs into the current page directory to obtain the page entry\n * for a requested address @p virtAddr. If new intermediary directories\n * need to be allocated and @p flags has @c MMU_GET_MAKE set, they\n * will be allocated with the user access bits set. Otherwise,\n * NULL will be returned. If the requested virtual address is within\n * a large page, NULL will be returned.\n *\n * @param virtAddr Canonical virtual address offset.\n * @param flags See @c MMU_GET_MAKE\n * @returns the requested page entry, or NULL if doing so required allocating\n *          an intermediary paging level and @p flags did not have @c MMU_GET_MAKE set.\n */\nunion PML * mmu_get_page(uintptr_t virtAddr, int flags) {\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\tunion PML * root = this_core->current_pml;\n\n\t/* Get the PML4 entry for this address */\n\tif (!root[pml4_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tspin_lock(frame_alloc_lock);\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\tspin_unlock(frame_alloc_lock);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\troot[pml4_entry].raw = (newPage) | USER_PML_ACCESS;\n\t}\n\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pdp[pdp_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tspin_lock(frame_alloc_lock);\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\tspin_unlock(frame_alloc_lock);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\tpdp[pdp_entry].raw = (newPage) | USER_PML_ACCESS;\n\t}\n\n\tif (pdp[pdp_entry].bits.size) {\n\t\tprintf(\"Warning: Tried to get page for a 1GiB page!\\n\");\n\t\treturn NULL;\n\t}\n\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\n\tif (!pd[pd_entry].bits.present) {\n\t\tif (!(flags & MMU_GET_MAKE)) goto _noentry;\n\t\tspin_lock(frame_alloc_lock);\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tmmu_frame_set(newPage);\n\t\tspin_unlock(frame_alloc_lock);\n\t\t/* zero it */\n\t\tmemset(mmu_map_from_physical(newPage), 0, PAGE_SIZE);\n\t\tpd[pd_entry].raw = (newPage) | USER_PML_ACCESS;\n\t}\n\n\tif (pd[pd_entry].bits.size) {\n\t\tprintf(\"Warning: Tried to get page for a 2MiB page!\\n\");\n\t\treturn NULL;\n\t}\n\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\treturn (union PML *)&pt[pt_entry];\n\n_noentry:\n\tprintf(\"no entry for requested page\\n\");\n\treturn NULL;\n}\n\n/**\n * @brief Increment the reference count for a physical page of memory.\n *\n * We allow up to 255 references to a page, so that we can track individual\n * page reference counts in a big @c uint8_t array. If there are already\n * that many references (that's a lot of forks!) we give up and do a regular\n * copy of the page and the new copy is writable.\n *\n * @param frame Physical page index\n * @returns 1 if there are already too many references to this page, 0 otherwise.\n */\nint refcount_inc(uintptr_t frame) {\n\tif (frame >= nframes) {\n\t\tarch_fatal_prepare();\n\t\tdprintf(\"%zu (inc, bad frame)\\n\", frame);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\tif (mem_refcounts[frame] == 255) return 1;\n\tmem_refcounts[frame]++;\n\treturn 0;\n}\n\n/**\n * @brief Decrement the reference count for a physical page of memory.\n *\n * Panics if @p frame is invalid or has a zero reference count.\n *\n * @param frame Physical page index\n * @returns the resulting reference count.\n */\nuint8_t refcount_dec(uintptr_t frame) {\n\tif (frame >= nframes) {\n\t\tarch_fatal_prepare();\n\t\tdprintf(\"%zu (dec, bad frame)\\n\", frame);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\tif (mem_refcounts[frame] == 0) {\n\t\tarch_fatal_prepare();\n\t\tdprintf(\"%zu (dec, frame has no references)\\n\", frame);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\tmem_refcounts[frame]--;\n\treturn mem_refcounts[frame];\n}\n\n/**\n * @brief Handle user pages in mmu_clone\n *\n * Copies and updates reference counts for pages across forks.\n * If a page was writable in the source directory, it will be marked\n * read-only and have reference counts initialized for COW.\n *\n * If a page was already read-only, its reference count will\n * be incremented for the new directory.\n *\n * @param pt_in Existing page table.\n * @param pt_out New directory's page table.\n * @param l Index into both page tables for this page.\n * @param address Virtual address being referenced.\n * @returns 0, generally\n */\nint copy_page_maybe(union PML * pt_in, union PML * pt_out, size_t l, uintptr_t address) {\n\t/* Can we cow the current page? */\n\tspin_lock(frame_alloc_lock);\n\n\t/* Is the page writable? */\n\tif (pt_in[l].bits.writable) {\n\t\t/* Then we need to initialize the refcounts */\n\t\tif (mem_refcounts[pt_in[l].bits.page] != 0) {\n\t\t\tarch_fatal_prepare();\n\t\t\tdprintf(\"%#zx (page=%u) refcount = %u\\n\",\n\t\t\t\taddress, pt_in[l].bits.page, mem_refcounts[pt_in[l].bits.page]);\n\t\t\tarch_dump_traceback();\n\t\t\tarch_fatal();\n\t\t\treturn 1;\n\t\t}\n\t\tmem_refcounts[pt_in[l].bits.page] = 2;\n\t\tpt_in[l].bits.writable = 0;\n\t\tpt_in[l].bits.cow_pending = 1;\n\t\tpt_out[l].raw = pt_in[l].raw;\n\t\tasm (\"\" ::: \"memory\");\n\t\tmmu_invalidate(address);\n\t\tspin_unlock(frame_alloc_lock);\n\t\treturn 0;\n\t}\n\n\t/* Can we make a new reference? */\n\tif (refcount_inc(pt_in[l].bits.page)) {\n\t\t/* There are too many references to fit in our refcount table, so just make a new page. */\n\t\tchar * page_in = mmu_map_from_physical((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT);\n\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\tchar * page_out = mmu_map_from_physical(newPage);\n\t\tmemcpy(page_out,page_in,PAGE_SIZE);\n\t\tpt_out[l].raw = 0;\n\t\tpt_out[l].bits.present = 1;\n\t\tpt_out[l].bits.user = 1;\n\t\tpt_out[l].bits.page = newPage >> PAGE_SHIFT;\n\t\tpt_out[l].bits.writable = 1;\n\t\tpt_out[l].bits.cow_pending = 0;\n\t\tasm (\"\" ::: \"memory\");\n\t} else {\n\t\tpt_out[l].raw = pt_in[l].raw;\n\t}\n\n\tspin_unlock(frame_alloc_lock);\n\treturn 0;\n}\n\n/**\n * @brief When freeing a directory, handle individual user pages.\n *\n * If @p pt_in references a writable user page, we know we can\n * free it immediately as it is the only reference to that page.\n *\n * Otherwise, we need to decrement the reference counts for read-only\n * pages, as they are shared COW entries. Only if this was the last\n * reference (refcount drops to 0) can we then proceed to free the\n * underlying page.\n *\n * @param pt_in Start of page table\n * @param l Offset into page table for this page\n * @param address Virtual address being freed (was used for debugging)\n * @returns 0, generally\n */\nint free_page_maybe(union PML * pt_in, size_t l, uintptr_t address) {\n\tif (pt_in[l].bits.writable) {\n\t\tassert(mem_refcounts[pt_in[l].bits.page] == 0);\n\t\tmmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT);\n\t\treturn 0;\n\t}\n\n\t/* No more references */\n\tif (refcount_dec(pt_in[l].bits.page) == 0) {\n\t\tmmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT);\n\t}\n\n\treturn 0;\n}\n\n/**\n * @brief Create a new address space with the same contents of an existing one.\n *\n * Allocates all of the necessary intermediary directory levels for a new address space\n * and also copies data from the existing address space.\n *\n * TODO: This doesn't do any CoW and it's kinda complicated.\n *\n * @param from The directory to clone, or NULL to clone the kernel map.\n * @returns a pointer to the new page directory, suitable for mapping to a physical address.\n */\nunion PML * mmu_clone(union PML * from) {\n\t/* Clone the current PMLs... */\n\tif (!from) from = this_core->current_pml;\n\n\t/* First get a page for ourselves. */\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\tmmu_frame_set(newPage);\n\tspin_unlock(frame_alloc_lock);\n\tunion PML * pml4_out = mmu_map_from_physical(newPage);\n\n\t/* Zero bottom half */\n\tmemset(&pml4_out[0], 0, 256 * sizeof(union PML));\n\n\t/* Copy top half */\n\tmemcpy(&pml4_out[256], &from[256], 256 * sizeof(union PML));\n\n\t/* Copy PDPs */\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tspin_lock(frame_alloc_lock);\n\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\tmmu_frame_set(newPage);\n\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\tunion PML * pdp_out = mmu_map_from_physical(newPage);\n\t\t\tmemset(pdp_out, 0, 512 * sizeof(union PML));\n\t\t\tpml4_out[i].raw = (newPage) | USER_PML_ACCESS;\n\n\t\t\t/* Copy the PDs */\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tspin_lock(frame_alloc_lock);\n\t\t\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\t\t\tmmu_frame_set(newPage);\n\t\t\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\t\t\tunion PML * pd_out = mmu_map_from_physical(newPage);\n\t\t\t\t\tmemset(pd_out, 0, 512 * sizeof(union PML));\n\t\t\t\t\tpdp_out[j].raw = (newPage) | USER_PML_ACCESS;\n\n\t\t\t\t\t/* Now copy the PTs */\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tspin_lock(frame_alloc_lock);\n\t\t\t\t\t\t\tuintptr_t newPage = mmu_first_frame() << PAGE_SHIFT;\n\t\t\t\t\t\t\tmmu_frame_set(newPage);\n\t\t\t\t\t\t\tspin_unlock(frame_alloc_lock);\n\t\t\t\t\t\t\tunion PML * pt_out = mmu_map_from_physical(newPage);\n\t\t\t\t\t\t\tmemset(pt_out, 0, 512 * sizeof(union PML));\n\t\t\t\t\t\t\tpd_out[k].raw = (newPage) | USER_PML_ACCESS;\n\n\t\t\t\t\t\t\t/* Now, finally, copy pages */\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.user) {\n\t\t\t\t\t\t\t\t\t\tcopy_page_maybe(pt_in, pt_out, l, address);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t/* If it's not a user page, just copy directly */\n\t\t\t\t\t\t\t\t\t\tpt_out[l].raw = pt_in[l].raw;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} /* Else, mmap'd files? */\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn pml4_out;\n}\n\n/**\n * @brief Allocate one physical page.\n *\n * @returns a frame index, not an address\n */\nuintptr_t mmu_allocate_a_frame(void) {\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t index = mmu_first_frame();\n\tmmu_frame_set(index << PAGE_SHIFT);\n\tspin_unlock(frame_alloc_lock);\n\treturn index;\n}\n\n/**\n * @brief Allocate a number of contiguous physical pages.\n *\n * @returns a frame index, not an address\n */\nuintptr_t mmu_allocate_n_frames(int n) {\n\tspin_lock(frame_alloc_lock);\n\tuintptr_t index = mmu_first_n_frames(n);\n\tfor (int i = 0; i < n; ++i) {\n\t\tmmu_frame_set((index+i) << PAGE_SHIFT);\n\t}\n\tspin_unlock(frame_alloc_lock);\n\treturn index;\n}\n\n/**\n * @brief Scans a directory to calculate how many user pages are in use.\n *\n * Calculates how many pages a userspace application has mapped, between\n * its general memory space and stack. Excludes shared mappings, such\n * as SHM or mapped devices.\n *\n * TODO: This can probably be reduced to check a smaller range, but as we\n *       currently stick the user stack at the top of the low half of the\n *       address space we just scan everything and exclude shared memory...\n *\n * @param from Top-level page directory to scan.\n */\nsize_t mmu_count_user(union PML * from) {\n\tsize_t out = 0;\n\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tout++;\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tout++;\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\t/* Calculate final address to skip SHM */\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.user) {\n\t\t\t\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * @brief Scans a directory to calculate how many shared memory pages are in use.\n *\n * At the moment, we only ever map shared pages to a specific region, so we just figure\n * out how many present pages are in that region and that's the answer.\n *\n * @param from Top-level page directory to scan.\n */\nsize_t mmu_count_shm(union PML * from) {\n\tsize_t out = 0;\n\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\t/* Calculate final address to skip SHM */\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\tif (address < USER_DEVICE_MAP || address > USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.user) {\n\t\t\t\t\t\t\t\t\t\tout++;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * @brief Return the total amount of usable memory.\n *\n * @returns the total amount of usable memory in KiB.\n */\nsize_t mmu_total_memory(void) {\n\treturn total_memory;\n}\n\n/**\n * @brief Return the amount of used memory.\n *\n * Calculates the number of pages currently marked as allocated.\n * Multiplies it by 4 because pages are 4KiB.\n *\n * @returns the amount of memory in use in KiB.\n */\nsize_t mmu_used_memory(void) {\n\tsize_t ret = 0;\n\tsize_t i, j;\n\tfor (i = 0; i < INDEX_FROM_BIT(nframes); ++i) {\n\t\tfor (j = 0; j < 32; ++j) {\n\t\t\tuint32_t testFrame = (uint32_t)0x1 << j;\n\t\t\tif (frames[i] & testFrame) {\n\t\t\t\tret++;\n\t\t\t}\n\t\t}\n\t}\n\treturn ret * 4 - unavailable_memory;\n}\n\n/**\n * @brief Relinquish pages owned by a top-level directory.\n *\n * Frees the underlying pages for a page directory within the lower (user) region.\n * Does not free kernel pages, as those are generally shared in the lower region.\n *\n * @param from Virtual pointer to top-level directory.\n */\nvoid mmu_free(union PML * from) {\n\tif (!from) {\n\t\tprintf(\"can't clear NULL directory\\n\");\n\t\treturn;\n\t}\n\n\tspin_lock(frame_alloc_lock);\n\tfor (size_t i = 0; i < 256; ++i) {\n\t\tif (from[i].bits.present) {\n\t\t\tunion PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t\tfor (size_t j = 0; j < 512; ++j) {\n\t\t\t\tif (pdp_in[j].bits.present) {\n\t\t\t\t\tunion PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t\tfor (size_t k = 0; k < 512; ++k) {\n\t\t\t\t\t\tif (pd_in[k].bits.present) {\n\t\t\t\t\t\t\tunion PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t\tfor (size_t l = 0; l < 512; ++l) {\n\t\t\t\t\t\t\t\tuintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT));\n\t\t\t\t\t\t\t\t/* Do not free shared mappings; SHM subsystem does that for SHM, devices don't need it. */\n\t\t\t\t\t\t\t\tif (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue;\n\t\t\t\t\t\t\t\tif (pt_in[l].bits.present) {\n\t\t\t\t\t\t\t\t\t/* Free only user pages */\n\t\t\t\t\t\t\t\t\tif (pt_in[l].bits.user) {\n\t\t\t\t\t\t\t\t\t\tfree_page_maybe(pt_in,l,address);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tmmu_frame_clear((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tmmu_frame_clear((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT);\n\t\t\t\t}\n\t\t\t}\n\t\t\tmmu_frame_clear((uintptr_t)from[i].bits.page << PAGE_SHIFT);\n\t\t}\n\t}\n\n\tmmu_frame_clear((((uintptr_t)from) & PHYS_MASK));\n\tspin_unlock(frame_alloc_lock);\n}\n\nunion PML * mmu_get_kernel_directory(void) {\n\treturn mmu_map_from_physical((uintptr_t)&init_page_region[0]);\n}\n\n/**\n * @brief Switch the active page directory for this core.\n *\n * Generally called during task creation and switching to change\n * the active page directory of a core. Updates @c this_core->current_pml.\n *\n * x86-64: Loads a given PML into CR3.\n *\n * @param new_pml Either the physical address or the shadow mapping virtual address\n *                of the new PML4 directory to switch into, general obtained from\n *                a process struct; if NULL is passed, the initial kernel directory\n *                will be used and no userspace mappings will be present.\n */\nvoid mmu_set_directory(union PML * new_pml) {\n\tif (!new_pml) new_pml = mmu_map_from_physical((uintptr_t)&init_page_region[0]);\n\tthis_core->current_pml = new_pml;\n\n\tasm volatile (\n\t\t\"movq %0, %%cr3\"\n\t\t: : \"r\"((uintptr_t)new_pml & PHYS_MASK));\n}\n\n/**\n * @brief Mark a virtual address's mappings as invalid in the TLB.\n *\n * Generally should be called when a mapping is relinquished, as this is what\n * the TLB caches, but is also called in a bunch of places where we're just mapping\n * new pages...\n *\n * @param addr Virtual address in the current address space to invalidate.\n */\nvoid mmu_invalidate(uintptr_t addr) {\n\tasm volatile (\n\t\t\"invlpg (%0)\"\n\t\t: : \"r\"(addr));\n\tarch_tlb_shootdown(addr);\n}\n\nint mmu_get_page_deep(uintptr_t virtAddr, union PML ** pml4_out, union PML ** pdp_out, union PML ** pd_out, union PML ** pt_out) {\n\t/* This is all the same as x86, thankfully? */\n\tuintptr_t realBits = virtAddr & CANONICAL_MASK;\n\tuintptr_t pageAddr = realBits >> PAGE_SHIFT;\n\tunsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK;\n\tunsigned int pdp_entry  = (pageAddr >> 18) & ENTRY_MASK;\n\tunsigned int pd_entry   = (pageAddr >> 9)  & ENTRY_MASK;\n\tunsigned int pt_entry   = (pageAddr) & ENTRY_MASK;\n\n\t/* Zero all the outputs */\n\t*pdp_out  = NULL;\n\t*pd_out   = NULL;\n\t*pt_out   = NULL;\n\n\tspin_lock(frame_alloc_lock);\n\tunion PML * root = this_core->current_pml;\n\t*pml4_out = (union PML *)&root[pml4_entry];\n\tif (!root[pml4_entry].bits.present) goto _noentry;\n\tunion PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT);\n\t*pdp_out = (union PML *)&pdp[pdp_entry];\n\tif (!pdp[pdp_entry].bits.present) goto _noentry;\n\tunion PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT);\n\t*pd_out = (union PML *)&pd[pd_entry];\n\tif (!pd[pd_entry].bits.present) goto _noentry;\n\tunion PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT);\n\t*pt_out = (union PML *)&pt[pt_entry];\n\n\tspin_unlock(frame_alloc_lock);\n\treturn 0;\n\n_noentry:\n\tspin_unlock(frame_alloc_lock);\n\treturn 1;\n}\n\nstatic int maybe_release_directory(union PML * parent, union PML * child) {\n\t/* child points to one entry, to get the base, we can page align it */\n\tunion PML * table = (union PML *)((uintptr_t)child & PAGE_SIZE_MASK);\n\n\t/* Is everything in the table free? */\n\tfor (int i = 0; i < 512; ++i) {\n\t\tif (table[i].bits.present) return 0;\n\t}\n\n\tuintptr_t old_page = (parent->bits.page << PAGE_SHIFT);\n\n\t/* Then we can mark 'parent' as freed, clear the whole thing. */\n\tparent->raw = 0;\n\tmmu_frame_clear(old_page);\n\n\treturn 1;\n}\n\nvoid mmu_unmap_user(uintptr_t addr, size_t size) {\n\tfor (uintptr_t a = addr; a < addr + size; a += PAGE_SIZE) {\n\t\tunion PML * pml4, * pdp, * pd, * pt;\n\n\t\tif (a >= USER_DEVICE_MAP && a <= USER_SHM_HIGH) continue;\n\t\tif (mmu_get_page_deep(a, &pml4, &pdp, &pd, &pt)) continue;\n\n\t\tspin_lock(frame_alloc_lock);\n\n\t\tif (pt && pt->bits.present && pt->bits.user) {\n\t\t\tif (pt->bits.writable) {\n\t\t\t\tassert(mem_refcounts[pt->bits.page] == 0);\n\t\t\t\tmmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT);\n\t\t\t} else if (refcount_dec(pt->bits.page) == 0) {\n\t\t\t\tmmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT);\n\t\t\t}\n\t\t\tpt->bits.present = 0;\n\t\t\tpt->bits.writable = 0;\n\n\t\t\tif (maybe_release_directory(pd, pt)) {\n\t\t\t\tif (maybe_release_directory(pdp, pd)) {\n\t\t\t\t\tmaybe_release_directory(pml4, pdp);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmmu_invalidate(a);\n\t\t}\n\n\t\tspin_unlock(frame_alloc_lock);\n\t}\n}\n\n\nstatic char * heapStart = NULL;\nextern char end[];\n\n/**\n * @brief Prepare virtual page mappings for use by the kernel.\n *\n * Called during early boot to switch from the loader/bootstrap mappings\n * to ones suitable for general use. Sets up the bitmap allocator, high\n * identity mapping, kernel heap, and various mid-level structures to\n * ensure that future kernelspace mappings apply to all kernel threads.\n *\n * @param memsize The maximum accessible physical address.\n * @param firstFreePage The address of the first frame the kernel may use for new allocations.\n */\nvoid mmu_init(size_t memsize, uintptr_t firstFreePage) {\n\tthis_core->current_pml = (union PML *)&init_page_region[0];\n\n\t/**\n\t * Enable WP bit, which will cause kernel writes to\n\t * non-writable pages to trigger page faults. We use\n\t * this to perform COW mappings for user processes if\n\t * they passed an unmapped region to a system call, though\n\t * this should be handled by @see mmu_validate_user_pointer\n\t * before we get to that point...\n\t */\n\tasm volatile (\n\t\t\"movq %%cr0, %%rax\\n\"\n\t\t\"orq  $0x10000, %%rax\\n\"\n\t\t\"movq %%rax, %%cr0\\n\"\n\t\t: : : \"rax\");\n\n\t/* Map the high base PDP */\n\tinit_page_region[0][511].raw = (uintptr_t)&high_base_pml | KERNEL_PML_ACCESS;\n\tinit_page_region[0][510].raw = (uintptr_t)&heap_base_pml | KERNEL_PML_ACCESS;\n\n\t/* Identity map from -128GB in the boot PML using 2MiB pages */\n\tfor (size_t i = 0; i < 64; ++i) {\n\t\thigh_base_pml[i].raw = (uintptr_t)&twom_high_pds[i] | KERNEL_PML_ACCESS;\n\t\tfor (uintptr_t j = 0; j < 512; ++j) {\n\t\t\ttwom_high_pds[i][j].raw = ((i << 30) + (j << 21)) | LARGE_PAGE_BIT | KERNEL_PML_ACCESS;\n\t\t}\n\t}\n\n\t/* Map low base PDP */\n\tlow_base_pmls[0][0].raw = (uintptr_t)&low_base_pmls[1] | USER_PML_ACCESS;\n\n\t/* How much memory do we need to map low for our *kernel* to fit? */\n\tuintptr_t endPtr = ((uintptr_t)&end + PAGE_LOW_MASK) & PAGE_SIZE_MASK;\n\n\t/* How many pages does that need? */\n\tsize_t lowPages = endPtr >> PAGE_SHIFT;\n\n\t/* And how many 512-page blocks does that fit in? */\n\tsize_t pdCount = (lowPages + ENTRY_MASK) >> 9;\n\n\tfor (size_t j = 0; j < pdCount; ++j) {\n\t\tlow_base_pmls[1][j].raw = (uintptr_t)&low_base_pmls[2+j] | KERNEL_PML_ACCESS;\n\t\tfor (int i = 0; i < 512; ++i) {\n\t\t\tlow_base_pmls[2+j][i].raw = (uintptr_t)(LARGE_PAGE_SIZE * j + PAGE_SIZE * i) | KERNEL_PML_ACCESS;\n\t\t}\n\t}\n\n\t/* Unmap null */\n\tlow_base_pmls[2][0].raw = 0;\n\n\t/* Now map our new low base */\n\tinit_page_region[0][0].raw = (uintptr_t)&low_base_pmls[0] | USER_PML_ACCESS;\n\n\t/* Set up the page allocator bitmap... */\n\tnframes = (memsize >> 12);\n\tsize_t bytesOfFrames = INDEX_FROM_BIT(nframes * 8);\n\tbytesOfFrames = (bytesOfFrames + PAGE_LOW_MASK) & PAGE_SIZE_MASK;\n\tfirstFreePage = (firstFreePage + PAGE_LOW_MASK) & PAGE_SIZE_MASK;\n\tsize_t pagesOfFrames = bytesOfFrames >> 12;\n\n\t/* Set up heap map for that... */\n\theap_base_pml[0].raw = (uintptr_t)&heap_base_pd | KERNEL_PML_ACCESS;\n\theap_base_pd[0].raw  = (uintptr_t)&heap_base_pt[0] | KERNEL_PML_ACCESS;\n\theap_base_pd[1].raw  = (uintptr_t)&heap_base_pt[512] | KERNEL_PML_ACCESS;\n\theap_base_pd[2].raw  = (uintptr_t)&heap_base_pt[1024] | KERNEL_PML_ACCESS;\n\n\tif (pagesOfFrames > 512*3) {\n\t\tprintf(\"Warning: Too much available memory for current setup. Need %zu pages to represent allocation bitmap.\\n\", pagesOfFrames);\n\t}\n\n\tfor (size_t i = 0; i < pagesOfFrames; i++) {\n\t\theap_base_pt[i].raw = (firstFreePage + (i << 12)) | KERNEL_PML_ACCESS;\n\t}\n\n\tasm volatile (\"\" : : : \"memory\");\n\tthis_core->current_pml = mmu_map_from_physical((uintptr_t)this_core->current_pml);\n\tasm volatile (\"\" : : : \"memory\");\n\n\t/* We are now in the new stuff. */\n\tframes = (void*)((uintptr_t)KERNEL_HEAP_START);\n\tmemset((void*)frames, 0xFF, bytesOfFrames);\n\n\textern void mboot_unmark_valid_memory(void);\n\tmboot_unmark_valid_memory();\n\n\t/* Don't trust anything but our own bitmap... */\n\tsize_t unavail = 0, avail = 0;\n\tfor (size_t i = 0; i < INDEX_FROM_BIT(nframes); ++i) {\n\t\tfor (size_t j = 0; j < 32; ++j) {\n\t\t\tuint32_t testFrame = (uint32_t)0x1 << j;\n\t\t\tif (frames[i] & testFrame) {\n\t\t\t\tunavail++;\n\t\t\t} else {\n\t\t\t\tavail++;\n\t\t\t}\n\t\t}\n\t}\n\n\ttotal_memory = avail * 4;\n\tunavailable_memory = unavail * 4;\n\n\t/* Now mark everything up to (firstFreePage + bytesOfFrames) as in use */\n\tfor (uintptr_t i = 0; i < firstFreePage + bytesOfFrames; i += PAGE_SIZE) {\n\t\tmmu_frame_set(i);\n\t}\n\n\theapStart = (char*)KERNEL_HEAP_START + bytesOfFrames;\n\n\t/* Then, uh, make a bunch of space for page counts? */\n\tsize_t size_of_refcounts = (nframes & PAGE_LOW_MASK) ? (nframes + PAGE_SIZE - (nframes & PAGE_LOW_MASK)) : nframes;\n\tmem_refcounts = sbrk(size_of_refcounts);\n\tmemset(mem_refcounts, 0, size_of_refcounts);\n}\n\n/**\n * @brief Allocate space in the kernel virtual heap.\n *\n * Called by the kernel heap allocator to obtain space for new heap allocations.\n *\n * @warning Not to be confused with sys_sbrk\n *\n * @param bytes Bytes to allocate. Must be a multiple of PAGE_SIZE.\n * @returns The previous address of the break point, after which @p bytes may now be used.\n */\nvoid * sbrk(size_t bytes) {\n\tif (!heapStart) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"sbrk: Called before heap was ready.\\n\");\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tif (!bytes) {\n\t\t/* Skip lock acquisition if we just wanted to know where the break was. */\n\t\treturn heapStart;\n\t}\n\n\tif (bytes & PAGE_LOW_MASK) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"sbrk: Size must be multiple of 4096, was %#zx\\n\", bytes);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tif (bytes > 0x1F00000) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"sbrk: Size must be within a reasonable bound, was %#zx\\n\", bytes);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tspin_lock(kheap_lock);\n\tvoid * out = heapStart;\n\n\tfor (uintptr_t p = (uintptr_t)out; p < (uintptr_t)out + bytes; p += PAGE_SIZE) {\n\t\tunion PML * page = mmu_get_page(p, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE | MMU_FLAG_KERNEL);\n\t}\n\n\t//memset(out, 0xAA, bytes);\n\n\theapStart += bytes;\n\tspin_unlock(kheap_lock);\n\treturn out;\n}\n\nstatic uintptr_t mmio_base_address = MMIO_BASE_START;\n\n/**\n * @brief Obtain a writethrough region mapped to the given physical address.\n *\n * For use by device drivers to obtain mappings suitable for MMIO accesses. Note that the\n * virtual address space for these mappings can not be reclaimed, so drivers should keep\n * them around or use the other MMU facilities to repurpose them.\n *\n * @param physical_address Physical memory offset of the destination MMIO space.\n * @param size Size of the requested space, which must be a multiple of PAGE_SIZE.\n * @returns a virtual address suitable for MMIO accesses.\n */\nvoid * mmu_map_mmio_region(uintptr_t physical_address, size_t size) {\n\tif (size & PAGE_LOW_MASK) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"mmu_map_mmio_region: MMIO region size must be multiple of 4096 bytes, was %#zx.\\n\", size);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tspin_lock(mmio_space_lock);\n\tvoid * out = (void*)mmio_base_address;\n\tfor (size_t i = 0; i < size; i += PAGE_SIZE) {\n\t\tunion PML * p = mmu_get_page(mmio_base_address + i, MMU_GET_MAKE);\n\t\tmmu_frame_map_address(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE | MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH, physical_address + i);\n\t}\n\tmmio_base_address += size;\n\tspin_unlock(mmio_space_lock);\n\n\treturn out;\n}\n\nstatic uintptr_t module_base_address = MODULE_BASE_START;\n\n/**\n * @brief Obtain space to load a module in the -2GiB region.\n *\n * This should really start immediately after the kernel, but we don't\n * yet load the kernel in the -2GiB region... it might also be worthwhile\n * to implement some ASLR here, especially given that we're loading\n * relocatable ELF object files and can stick them anywhere.\n *\n * @param size How much space to allocate, will be rounded up to page size.\n * @returns Start of the allocated address space.\n */\nvoid * mmu_map_module(size_t size) {\n\tif (size & PAGE_LOW_MASK) {\n\t\tsize += (PAGE_LOW_MASK + 1) - (size & PAGE_LOW_MASK);\n\t}\n\n\tspin_lock(module_space_lock);\n\tvoid * out = (void*)module_base_address;\n\tfor (size_t i = 0; i < size; i += PAGE_SIZE) {\n\t\tunion PML * p = mmu_get_page(module_base_address + i, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE);\n\t}\n\tmodule_base_address += size;\n\tspin_unlock(module_space_lock);\n\n\treturn out;\n}\n\n/**\n * @brief Free pages allocated for kernel modules.\n *\n * This rather blindly unmaps pages.\n *\n * @param start_address Start of mapping to unmap.\n * @param size Size of mapping to unmap.\n */\nvoid mmu_unmap_module(uintptr_t start_address, size_t size) {\n\tif ((size & PAGE_LOW_MASK) || (start_address & PAGE_LOW_MASK)) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"mmu_unmap_module start and size must be multiple of page size %#zx:%#zx.\\n\", start_address, size);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\tspin_lock(module_space_lock);\n\tuintptr_t end_address = start_address + size;\n\n\t/* Unmap all pages we just allocated */\n\tfor (uintptr_t i = start_address; i < end_address; i += 0x1000) {\n\t\tunion PML * p = mmu_get_page(i, 0);\n\t\tmmu_frame_clear(p->bits.page << 12);\n\t}\n\n\t/* Reset module base address if it was at the end, to avoid wasting address space */\n\tif (end_address == module_base_address) {\n\t\tmodule_base_address = start_address;\n\t}\n\tspin_unlock(module_space_lock);\n}\n\n/**\n * @brief Swap a COW page for a writable copy.\n *\n * Examines @p address to determine if it is a pending\n * COW page that has been marked read-only. If it is,\n * it will be exchanged for a writable page. If it is\n * the last read-only reference to a page, it will be\n * marked writable without introducing a new backing page.\n *\n * @param address Virtual address that triggered the fault.\n * @returns 0 if this was a valid and completed COW operation, 1 otherwise.\n */\nint mmu_copy_on_write(uintptr_t address) {\n\tunion PML * page = mmu_get_page(address,0);\n\n\t/* Was this address pending a cow? */\n\tif (!page->bits.cow_pending) {\n\t\t/* No, go back and trigger and a SIGSEGV */\n\t\treturn 1;\n\t}\n\n\tspin_lock(frame_alloc_lock);\n\n\t/* Is this the last reference to this page? */\n\tuint8_t refs = refcount_dec(page->bits.page);\n\tif (refs == 0) {\n\t\t/* Then we can just mark it writable. */\n\t\tpage->bits.writable = 1;\n\t\tpage->bits.cow_pending = 0;\n\t\tasm (\"\" ::: \"memory\");\n\t\tmmu_invalidate(address);\n\t\tspin_unlock(frame_alloc_lock);\n\t\treturn 0;\n\t}\n\n\t/* Allocate a new writable page */\n\tuintptr_t faulting_frame = page->bits.page;\n\tuintptr_t fresh_frame = mmu_first_frame();\n\tmmu_frame_set(fresh_frame << PAGE_SHIFT);\n\n\t/* Copy the read-only page into the new writable page */\n\tchar * page_in  = mmu_map_from_physical(faulting_frame << PAGE_SHIFT);\n\tchar * page_out = mmu_map_from_physical(fresh_frame << PAGE_SHIFT);\n\tmemcpy(page_out, page_in, 4096);\n\n\t/* And swap out the page table entry. */\n\tpage->bits.page = fresh_frame;\n\tpage->bits.writable = 1;\n\tpage->bits.cow_pending = 0;\n\tspin_unlock(frame_alloc_lock);\n\n\tasm (\"\" ::: \"memory\");\n\n\tmmu_invalidate(address);\n\treturn 0;\n}\n\n/**\n * @brief Check if the current user process can access address space.\n *\n * Thoroughly examines page table entries to determine if a user process\n * can access the memory at @p addr through @p size bytes.\n *\n * @p flags can be set to @c MMU_PTR_NULL if @c NULL address should trigger\n * a failure, @c MMU_PTR_WRITE if the process must have write access.\n *\n * @param addr Address to start checking from.\n * @param size Size after @p addr to check.\n * @param flags Control what constitutes a failure.\n * @returns 0 on failure, 1 if process has access.\n */\nint mmu_validate_user_pointer(const void * addr, size_t size, int flags) {\n\tif (addr == NULL && !(flags & MMU_PTR_NULL)) return 0;\n\tif (size >     0x800000000000) return 0;\n\n\tuintptr_t base = (uintptr_t)addr;\n\tuintptr_t end  = size ? (base + (size - 1)) : base;\n\n\t/* Get start page, end page */\n\tuintptr_t page_base = base >> 12;\n\tuintptr_t page_end  =  end >> 12;\n\n\tfor (uintptr_t page = page_base; page <= page_end; ++page) {\n\t\tif ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0;\n\t\tunion PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12);\n\t\tif (!page_entry) return 0;\n\t\tif (!page_entry->bits.present) return 0;\n\t\tif (!page_entry->bits.user) return 0;\n\t\tif (!page_entry->bits.writable && (flags & MMU_PTR_WRITE)) {\n\t\t\tif (mmu_copy_on_write((uintptr_t)(page << 12))) return 0;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\n\n"
  },
  {
    "path": "kernel/arch/x86_64/pic.c",
    "content": "/**\n * @file  kernel/arch/x86_64/pic.c\n * @brief Legacy PIC support.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n#include <kernel/arch/x86_64/regs.h>\n\n/* Programmable interrupt controller */\n#define PIC1           0x20\n#define PIC1_COMMAND   PIC1\n#define PIC1_OFFSET    0x20\n#define PIC1_DATA      (PIC1+1)\n\n#define PIC2           0xA0\n#define PIC2_COMMAND   PIC2\n#define PIC2_OFFSET    0x28\n#define PIC2_DATA      (PIC2+1)\n\n#define PIC_EOI        0x20\n\n#define ICW1_ICW4      0x01\n#define ICW1_INIT      0x10\n\n#define PIC_WAIT() \\\n\tdo { \\\n\t\t/* May be fragile */ \\\n\t\tasm volatile(\"jmp 1f\\n\\t\" \\\n\t\t             \"1:\\n\\t\" \\\n\t\t             \"    jmp 2f\\n\\t\" \\\n\t\t             \"2:\"); \\\n\t} while (0)\n\nstatic void irq_remap(void) {\n\t/* Cascade initialization */\n\toutportb(PIC1_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT();\n\toutportb(PIC2_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT();\n\n\t/* Remap */\n\toutportb(PIC1_DATA, PIC1_OFFSET); PIC_WAIT();\n\toutportb(PIC2_DATA, PIC2_OFFSET); PIC_WAIT();\n\n\t/* Cascade identity with slave PIC at IRQ2 */\n\toutportb(PIC1_DATA, 0x04); PIC_WAIT();\n\toutportb(PIC2_DATA, 0x02); PIC_WAIT();\n\n\t/* Request 8086 mode on each PIC */\n\toutportb(PIC1_DATA, 0x01); PIC_WAIT();\n\toutportb(PIC2_DATA, 0x01); PIC_WAIT();\n}\n\nvoid irq_ack(size_t irq_no) {\n\tif (irq_no >= 8) {\n\t\toutportb(PIC2_COMMAND, PIC_EOI);\n\t}\n\toutportb(PIC1_COMMAND, PIC_EOI);\n}\n\nvoid pic_initialize(void) {\n\tirq_remap();\n}\n\n"
  },
  {
    "path": "kernel/arch/x86_64/pit.c",
    "content": "/**\n * @file kernel/arch/x86_64/pit.c\n * @author K. Lange\n * @brief Legacy x86 Programmable Interrupt Timer\n *\n * The PIT is used as a fallback preempt source if the LAPIC can\n * not be configured. The preempt signal is 100Hz.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2024 K. Lange\n */\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n#include <kernel/arch/x86_64/regs.h>\n\n/* Programmable interval timer */\n#define PIT_A 0x40\n#define PIT_B 0x41\n#define PIT_C 0x42\n#define PIT_CONTROL 0x43\n\n#define PIT_MASK 0xFF\n#define PIT_SCALE 1193180\n#define PIT_SET 0x34\n\n#define TIMER_IRQ 0\n\n#define RESYNC_TIME 1\n\n/**\n * @brief Set the phase of the PIT in Hz.\n *\n * @param hz Ticks per second.\n */\nstatic void pit_set_timer_phase(long hz) {\n\tlong divisor = PIT_SCALE / hz;\n\toutportb(PIT_CONTROL, PIT_SET);\n\toutportb(PIT_A, divisor & PIT_MASK);\n\toutportb(PIT_A, (divisor >> 8) & PIT_MASK);\n}\n\n/**\n * @brief Interrupt handler for the PIT.\n */\nint pit_interrupt(struct regs *r) {\n\textern void arch_update_clock(void);\n\tarch_update_clock();\n\n\tirq_ack(0);\n\n\tif (r->cs == 0x08) return 1;\n\n\tswitch_task(1);\n\treturn 1;\n}\n\n/**\n * @brief Install an interrupt handler for, and turn on, the PIT.\n */\nvoid pit_initialize(void) {\n\tirq_install_handler(TIMER_IRQ, pit_interrupt, \"pit timer\");\n\n\t/* ELCR? */\n\tuint8_t val = inportb(0x4D1);\n\toutportb(0x4D1, val | (1 << (10-8)) | (1 << (11-8)));\n\n\t/* Enable PIT */\n\tpit_set_timer_phase(100);\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/ports.c",
    "content": "/**\n * @file  kernel/arch/x86_64/ports.c\n * @brief Port I/O methods for x86-64.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/types.h>\n\nunsigned short inports(unsigned short _port) {\n\tunsigned short rv;\n\tasm volatile (\"inw %1, %0\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nvoid outports(unsigned short _port, unsigned short _data) {\n\tasm volatile (\"outw %1, %0\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nunsigned int inportl(unsigned short _port) {\n\tunsigned int rv;\n\tasm volatile (\"inl %%dx, %%eax\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nvoid outportl(unsigned short _port, unsigned int _data) {\n\tasm volatile (\"outl %%eax, %%dx\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nunsigned char inportb(unsigned short _port) {\n\tunsigned char rv;\n\tasm volatile (\"inb %1, %0\" : \"=a\" (rv) : \"dN\" (_port));\n\treturn rv;\n}\n\nvoid outportb(unsigned short _port, unsigned char _data) {\n\tasm volatile (\"outb %1, %0\" : : \"dN\" (_port), \"a\" (_data));\n}\n\nvoid outportsm(unsigned short port, unsigned char * data, unsigned long size) {\n\tasm volatile (\"rep outsw\" : \"+S\" (data), \"+c\" (size) : \"d\" (port));\n}\n\nvoid inportsm(unsigned short port, unsigned char * data, unsigned long size) {\n\tasm volatile (\"rep insw\" : \"+D\" (data), \"+c\" (size) : \"d\" (port) : \"memory\");\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/ps2hid.c",
    "content": "/**\n * @file  kernel/arch/x86_64/ps2mouse.c\n * @brief PC PS/2 input device driver\n *\n * This is the slightly less terrible merged PS/2 mouse+keyboard driver.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/printf.h>\n#include <kernel/pipe.h>\n#include <kernel/mouse.h>\n#include <kernel/misc.h>\n#include <kernel/args.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#define PACKETS_IN_PIPE    1024\n#define DISCARD_POINT        32\n#define KEYBOARD_IRQ          1\n#define MOUSE_IRQ            12\n\n#define PS2_DATA           0x60\n#define PS2_STATUS         0x64\n#define PS2_COMMAND        0x64\n#define MOUSE_WRITE        0xD4\n#define MOUSE_V_BIT        0x08\n\n#define PS2_PORT1_IRQ      0x01\n#define PS2_PORT2_IRQ      0x02\n#define PS2_PORT1_TLATE    0x40\n\n#define PS2_READ_CONFIG    0x20\n#define PS2_WRITE_CONFIG   0x60\n\n#define PS2_DISABLE_PORT2  0xA7\n#define PS2_ENABLE_PORT2   0xA8\n#define PS2_DISABLE_PORT1  0xAD\n#define PS2_ENABLE_PORT1   0xAE\n\n#define MOUSE_SET_REMOTE   0xF0\n#define MOUSE_DEVICE_ID    0xF2\n#define MOUSE_SAMPLE_RATE  0xF3\n#define MOUSE_DATA_ON      0xF4\n#define MOUSE_DATA_OFF     0xF5\n#define MOUSE_SET_DEFAULTS 0xF6\n\n#define MOUSE_DEFAULT         0\n#define MOUSE_SCROLLWHEEL     1\n#define MOUSE_BUTTONS         2\n\n#define KBD_SET_SCANCODE   0xF0\n\nstatic uint8_t mouse_cycle = 0;\nstatic uint8_t mouse_byte[4];\nstatic int8_t mouse_mode = MOUSE_DEFAULT;\nstatic fs_node_t * mouse_pipe;\nstatic fs_node_t * keyboard_pipe;\n\nvoid (*ps2_mouse_alternate)(uint8_t) = NULL;\n\n/**\n * @brief Wait until the PS/2 controller's input buffer is clear.\n *\n * Use this before WRITING to the controller.\n */\nstatic int ps2_wait_input(void) {\n\tuint64_t timeout = 100000UL;\n\twhile (--timeout) {\n\t\tif (!(inportb(PS2_STATUS) & (1 << 1))) return 0;\n\t}\n\treturn 1;\n}\n\n/**\n * @brief Wait until the PS/2 controller's output buffer is filled.\n *\n * Use this before READING from the controller.\n */\nstatic int ps2_wait_output(void) {\n\tuint64_t timeout = 100000UL;\n\twhile (--timeout) {\n\t\tif (inportb(PS2_STATUS) & (1 << 0)) return 0;\n\t}\n\treturn 1;\n}\n\n/**\n * @brief Send a command with no response or argument.\n */\nstatic void ps2_command(uint8_t cmdbyte) {\n\tps2_wait_input();\n\toutportb(PS2_COMMAND, cmdbyte);\n}\n\n/**\n * @brief Send a command and get the reply.\n */\nstatic uint8_t ps2_command_response(uint8_t cmdbyte) {\n\tps2_wait_input();\n\toutportb(PS2_COMMAND, cmdbyte);\n\tps2_wait_output();\n\treturn inportb(PS2_DATA);\n}\n\n/**\n * @brief Send a command with an argument and no reply.\n */\nstatic void ps2_command_arg(uint8_t cmdbyte, uint8_t arg) {\n\tps2_wait_input();\n\toutportb(PS2_COMMAND, cmdbyte);\n\tps2_wait_input();\n\toutportb(PS2_DATA, arg);\n}\n\n/**\n * @brief Write to the aux port.\n */\nstatic uint8_t mouse_write(uint8_t write) {\n\tps2_command_arg(MOUSE_WRITE, write);\n\tps2_wait_output();\n\treturn inportb(PS2_DATA);\n}\n\n/**\n * @brief Read generic response byte\n */\nstatic uint8_t ps2_read_byte(void) {\n\tps2_wait_output();\n\treturn inportb(PS2_DATA);\n}\n\n/**\n * @brief Write to the primary port.\n */\nstatic uint8_t kbd_write(uint8_t write) {\n\tps2_wait_input();\n\toutportb(PS2_DATA, write);\n\tps2_wait_output();\n\treturn inportb(PS2_DATA);\n}\n\n/**\n * @brief Process a completed mouse packet.\n *\n * Assembles a mouse_device_packet_t from the data we got from\n * the PS/2 device and forwards it to the pipe to be read by\n * userspace; if the pipe is full we discard old bytes first.\n */\nstatic void finish_packet(void) {\n\tmouse_cycle = 0;\n\t/* We now have a full mouse packet ready to use */\n\tmouse_device_packet_t packet;\n\tpacket.magic = MOUSE_MAGIC;\n\tint x = mouse_byte[1];\n\tint y = mouse_byte[2];\n\tif (x && mouse_byte[0] & (1 << 4)) {\n\t\t/* Sign bit */\n\t\tx = x - 0x100;\n\t}\n\tif (y && mouse_byte[0] & (1 << 5)) {\n\t\t/* Sign bit */\n\t\ty = y - 0x100;\n\t}\n\tif (mouse_byte[0] & (1 << 6) || mouse_byte[0] & (1 << 7)) {\n\t\t/* Overflow */\n\t\tx = 0;\n\t\ty = 0;\n\t}\n\tpacket.x_difference = x;\n\tpacket.y_difference = y;\n\tpacket.buttons = 0;\n\tif (mouse_byte[0] & 0x01) {\n\t\tpacket.buttons |= LEFT_CLICK;\n\t}\n\tif (mouse_byte[0] & 0x02) {\n\t\tpacket.buttons |= RIGHT_CLICK;\n\t}\n\tif (mouse_byte[0] & 0x04) {\n\t\tpacket.buttons |= MIDDLE_CLICK;\n\t}\n\n\tif (mouse_mode == MOUSE_SCROLLWHEEL && mouse_byte[3]) {\n\t\tif ((int8_t)mouse_byte[3] > 0) {\n\t\t\tpacket.buttons |= MOUSE_SCROLL_DOWN;\n\t\t} else if ((int8_t)mouse_byte[3] < 0) {\n\t\t\tpacket.buttons |= MOUSE_SCROLL_UP;\n\t\t}\n\t}\n\n\tmouse_device_packet_t bitbucket;\n\twhile (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {\n\t\tread_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);\n\t}\n\twrite_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);\n}\n\n/**\n * @brief Read one byte from the mouse.\n */\nstatic void ps2_mouse_handle(uint8_t data_byte) {\n\tif (ps2_mouse_alternate) {\n\t\tps2_mouse_alternate(data_byte);\n\t} else {\n\t\tint8_t mouse_in = data_byte;\n\t\tswitch (mouse_cycle) {\n\t\t\tcase 0:\n\t\t\t\tmouse_byte[0] = mouse_in;\n\t\t\t\tif (!(mouse_in & MOUSE_V_BIT)) break;\n\t\t\t\t++mouse_cycle;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tmouse_byte[1] = mouse_in;\n\t\t\t\t++mouse_cycle;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tmouse_byte[2] = mouse_in;\n\t\t\t\tif (mouse_mode == MOUSE_SCROLLWHEEL || mouse_mode == MOUSE_BUTTONS) {\n\t\t\t\t\t++mouse_cycle;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tfinish_packet();\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tmouse_byte[3] = mouse_in;\n\t\t\t\tfinish_packet();\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) {\n\tif (request == 1) {\n\t\tmouse_cycle = 0;\n\t\treturn 0;\n\t}\n\treturn -1;\n}\n\n/**\n * @brief Read one byte from the keyboard.\n *\n * We give userspace the keyboard scancodes directly, and libtoaru_kbd\n * handles translation to a more usable format. This is probably not\n * the best way to do this...\n */\nstatic void ps2_kbd_handle(uint8_t data_byte) {\n\twrite_fs(keyboard_pipe, 0, 1, (uint8_t []){data_byte});\n}\n\n/**\n * @brief Shared handler that does some magic that probably only works in QEMU.\n *\n * The general idea behind this shared handler is that QEMU is \"broken\"\n * and introduces a race that shouldn't be possible on real hardware?\n * We can get an interrupt but the byte we get out of the port is\n * for the other device. This makes playing Quake very hard because\n * our keyboard our mouse devices get garbage when we're doing both\n * at once! Thankfully, QEMU supports the status bit for checking\n * if there is mouse data, and if we prevent any data from coming\n * in from either port (by disabling both) while checking both\n * the status and the data port, we can use that as a lock and get\n * an \"atomic\" read that tells us which thing the data come from.\n */\nstatic int shared_handler(struct regs * r) {\n\t/* Disable both ports */\n\tps2_command(PS2_DISABLE_PORT1);\n\tps2_command(PS2_DISABLE_PORT2);\n\t/* Read the status and data */\n\tuint8_t status = inportb(PS2_STATUS);\n\tuint8_t data_byte = inportb(PS2_DATA);\n\t/* Re-enable both */\n\tps2_command(PS2_ENABLE_PORT1);\n\tps2_command(PS2_ENABLE_PORT2);\n\n\tirq_ack(r->int_no-32);\n\n\tif (!(status & 0x01)) return 1;\n\n\tif (!(status & 0x20)) {\n\t\tps2_kbd_handle(data_byte);\n\t} else if (status & 0x21) {\n\t\tps2_mouse_handle(data_byte);\n\t}\n\treturn 1;\n}\n\n/**\n * @brief IRQ1 handler.\n */\nstatic int keyboard_handler(struct regs *r) {\n\tuint8_t data_byte = inportb(PS2_DATA);\n\tirq_ack(KEYBOARD_IRQ);\n\tps2_kbd_handle(data_byte);\n\treturn 1;\n}\n\n/**\n * @brief IRQ12 handler.\n */\nstatic int mouse_handler(struct regs *r) {\n\tuint8_t data_byte = inportb(PS2_DATA);\n\tirq_ack(MOUSE_IRQ);\n\tps2_mouse_handle(data_byte);\n\treturn 1;\n}\n\n\n/**\n * @brief Initialze i8042/AIP PS/2 controller.\n */\nvoid ps2hid_install(void) {\n\tuint8_t status, result;\n\n\tmouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE);\n\tmouse_pipe->flags = FS_CHARDEVICE;\n\tmouse_pipe->ioctl = ioctl_mouse;\n\tvfs_mount(\"/dev/mouse\", mouse_pipe, \"ps2-mouse\", \"\");\n\n\tkeyboard_pipe = make_pipe(128);\n\tkeyboard_pipe->flags = FS_CHARDEVICE;\n\tvfs_mount(\"/dev/kbd\", keyboard_pipe, \"ps2-kbd\", \"\");\n\n\t/* Disable both ports. */\n\tps2_command(PS2_DISABLE_PORT1);\n\tps2_command(PS2_DISABLE_PORT2);\n\n\t/* Clear the input buffer. */\n\tsize_t timeout = 1024; /* Can't imagine a buffer with more than that being full... */\n\twhile ((inportb(PS2_STATUS) & 1) && timeout > 0) {\n\t\ttimeout--;\n\t\tinportb(PS2_DATA);\n\t}\n\n\tif (timeout == 0) {\n\t\tprintf(\"ps2hid: probably don't actually have PS/2.\\n\");\n\t\treturn;\n\t}\n\n\t/* Enable interrupt lines, enable translation. */\n\tstatus = ps2_command_response(PS2_READ_CONFIG);\n\tstatus |= (PS2_PORT1_IRQ | PS2_PORT2_IRQ | PS2_PORT1_TLATE);\n\tps2_command_arg(PS2_WRITE_CONFIG, status);\n\n\t/* Re-enable ports */\n\tps2_command(PS2_ENABLE_PORT1);\n\tps2_command(PS2_ENABLE_PORT2);\n\n\t/* Set scancode mode to 2... which then gives us 1 with translation... */\n\tkbd_write(KBD_SET_SCANCODE);\n\tkbd_write(2);\n\n\t/* Now we'll configure the mouse... */\n\tmouse_write(MOUSE_SET_DEFAULTS);\n\tmouse_write(MOUSE_DATA_ON);\n\n\t/* Try to enable scroll wheel (but not buttons) */\n\tif (!args_present(\"nomousescroll\")) {\n\t\tmouse_write(MOUSE_DEVICE_ID);\n\t\tps2_read_byte(); /* Ignore response */\n\t\tmouse_write(MOUSE_SAMPLE_RATE);\n\t\tmouse_write(200);\n\t\tmouse_write(MOUSE_SAMPLE_RATE);\n\t\tmouse_write(100);\n\t\tmouse_write(MOUSE_SAMPLE_RATE);\n\t\tmouse_write(80);\n\t\tmouse_write(MOUSE_DEVICE_ID);\n\t\tresult = ps2_read_byte();\n\t\tif (result == 3) {\n\t\t\tmouse_mode = MOUSE_SCROLLWHEEL;\n\t\t}\n\t}\n\n\tif (args_present(\"sharedps2\")) {\n\t\tirq_install_handler(KEYBOARD_IRQ, shared_handler, \"ps2hid\");\n\t\tirq_install_handler(MOUSE_IRQ,    shared_handler, \"ps2hid\");\n\t} else {\n\t\tirq_install_handler(KEYBOARD_IRQ, keyboard_handler, \"ps2hid\");\n\t\tirq_install_handler(MOUSE_IRQ,    mouse_handler,    \"ps2hid\");\n\t}\n}\n\n"
  },
  {
    "path": "kernel/arch/x86_64/serial.c",
    "content": "/**\n * @file  kernel/arch/x86_64/serial.c\n * @brief PC serial port driver.\n *\n * Attaches serial ports to TTY interfaces. Serial input processing\n * happens in a kernel tasklet so that blocking is handled smoothly.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <kernel/string.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/pipe.h>\n#include <kernel/process.h>\n#include <kernel/printf.h>\n#include <kernel/args.h>\n#include <kernel/pty.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#define SERIAL_PORT_A 0x3F8\n#define SERIAL_PORT_B 0x2F8\n#define SERIAL_PORT_C 0x3E8\n#define SERIAL_PORT_D 0x2E8\n\n#define SERIAL_IRQ_AC 4\n#define SERIAL_IRQ_BD 3\n\nstruct serial_port_map {\n\tint port;\n\tpty_t * pty;\n\tint index;\n\n\ttcflag_t cflags;\n};\n\nstatic struct serial_port_map serial_ports[4] = {\n\t{ SERIAL_PORT_A, NULL, 0, 0 },\n\t{ SERIAL_PORT_B, NULL, 1, 0 },\n\t{ SERIAL_PORT_C, NULL, 2, 0 },\n\t{ SERIAL_PORT_D, NULL, 3, 0 },\n};\n\nstatic struct serial_port_map * map_entry_for_port(int port) {\n\tswitch (port) {\n\t\tcase SERIAL_PORT_A: return &serial_ports[0];\n\t\tcase SERIAL_PORT_B: return &serial_ports[1];\n\t\tcase SERIAL_PORT_C: return &serial_ports[2];\n\t\tcase SERIAL_PORT_D: return &serial_ports[3];\n\t}\n\t__builtin_unreachable();\n}\n\nstatic pty_t ** pty_for_port(int port) {\n\treturn &map_entry_for_port(port)->pty;\n}\n\nstatic int serial_rcvd(int device) {\n\treturn inportb(device + 5) & 1;\n}\n\nstatic char serial_recv(int device) {\n\twhile (serial_rcvd(device) == 0) switch_task(1);\n\treturn inportb(device);\n}\n\nstatic int serial_transmit_empty(int device) {\n\treturn inportb(device + 5) & 0x20;\n}\n\nstatic void serial_send(int device, char out) {\n\twhile (serial_transmit_empty(device) == 0) switch_task(1);\n\toutportb(device, out);\n}\n\nstatic process_t * serial_ac_handler = NULL;\nstatic process_t * serial_bd_handler = NULL;\n\nstatic void process_serial(void * argp) {\n\tint portBase = (uintptr_t)argp;\n\tint _did_something;\n\twhile (1) {\n\t\tunsigned long s, ss;\n\t\trelative_time(1, 0, &s, &ss);\n\t\tsleep_until((process_t *)this_core->current_process, s, ss);\n\t\tswitch_task(0);\n\t\t_did_something = 1;\n\t\twhile (_did_something) {\n\t\t\t_did_something = 0;\n\t\t\tint port_a_status = inportb(portBase + 5);\n\t\t\tint port_c_status = inportb(portBase - 0x10 + 5);\n\n\t\t\tif (port_a_status != 0xFF && (port_a_status & 1)) {\n\t\t\t\tchar ch = inportb(portBase);\n\t\t\t\tpty_t * pty = *pty_for_port(portBase);\n\t\t\t\ttty_input_process(pty, ch);\n\t\t\t\t_did_something = 1;\n\t\t\t}\n\n\t\t\tif (port_c_status != 0xFF && (port_c_status & 1)) {\n\t\t\t\tchar ch = inportb(portBase - 0x10);\n\t\t\t\tpty_t * pty = *pty_for_port(portBase - 0x10);\n\t\t\t\ttty_input_process(pty, ch);\n\t\t\t\t_did_something = 1;\n\t\t\t}\n\t\t}\n\t}\n}\n\nint serial_handler_ac(struct regs *r) {\n\tirq_ack(SERIAL_IRQ_AC);\n\tmake_process_ready(serial_ac_handler);\n\treturn 1;\n}\n\nint serial_handler_bd(struct regs *r) {\n\tirq_ack(SERIAL_IRQ_BD);\n\tmake_process_ready(serial_bd_handler);\n\treturn 1;\n}\n\n#define BASE 115200\n#define D(n) { B ## n, BASE / n }\nstatic struct divisor {\n\tspeed_t baud;\n\tuint16_t div;\n} divisors[] = {\n\t{ B0,  0 },\n\tD(50), D(75), D(110),\n\t{ B134, BASE * 10 / 1345 },\n\tD(150), D(200), D(300), D(600), D(1200),\n\tD(1800), D(2400), D(4800), D(9600), D(19200),\n\tD(38400), D(57600), D(115200),\n};\n#undef D\n\nstatic void serial_enable(int port, tcflag_t cflags) {\n\toutportb(port + 1, 0x00); /* Disable interrupts */\n\toutportb(port + 3, 0x80); /* Enable divisor mode */\n\n\tuint16_t divisor = 0;\n\tfor (size_t i = 0; i < sizeof(divisors) / sizeof(*divisors); ++i) {\n\t\tif ((cflags & CBAUD) == divisors[i].baud) {\n\t\t\tdivisor = divisors[i].div;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\toutportb(port + 0, divisor & 0xFF); /* Div Low */\n\toutportb(port + 1, divisor >> 8);   /* Div High */\n\n\tuint8_t line_ctl = 0;\n\tif (cflags & PARENB) {\n\t\tline_ctl |= (1 << 3); /* Enable parity */\n\t\tif (!(cflags & PARODD)) line_ctl |= (1 << 4); /* Even parity */\n\t}\n\n\t/* Size */\n\tswitch (cflags & CSIZE) {\n\t\tcase CS5: line_ctl |= 0; break;\n\t\tcase CS6: line_ctl |= 1; break;\n\t\tcase CS7: line_ctl |= 2; break;\n\t\tcase CS8: line_ctl |= 3; break;\n\t}\n\n\toutportb(port + 3, line_ctl); /* set line mode */\n\toutportb(port + 2, 0xC7); /* Enable FIFO and clear */\n\toutportb(port + 4, 0x0B); /* Enable interrupts */\n\toutportb(port + 1, 0x01); /* Enable interrupts */\n}\n\nstatic int have_installed_ac = 0;\nstatic int have_installed_bd = 0;\n\nstatic void serial_write_out(pty_t * pty, uint8_t c) {\n\tstruct serial_port_map * me = pty->_private;\n\tif (pty->tios.c_cflag != me->cflags) {\n\t\tme->cflags = pty->tios.c_cflag;\n\t\tserial_enable(me->port, pty->tios.c_cflag);\n\t}\n\tserial_send(me->port, c);\n}\n\n#define DEV_PATH \"/dev/ttyS\"\n\nstatic void serial_fill_name(pty_t * pty, char * name) {\n\tsnprintf(name, 100, DEV_PATH \"%d\", ((struct serial_port_map *)pty->_private)->index);\n}\n\nstatic fs_node_t * serial_device_create(int port) {\n\tpty_t * pty = pty_new(NULL, 0);\n\n\tmap_entry_for_port(port)->pty = pty;\n\tpty->_private = map_entry_for_port(port);\n\n\tpty->write_out = serial_write_out;\n\tpty->fill_name = serial_fill_name;\n\n\tserial_enable(port, pty->tios.c_cflag);\n\n\tif (port == SERIAL_PORT_A || port == SERIAL_PORT_C) {\n\t\tif (!have_installed_ac) {\n\t\t\tirq_install_handler(SERIAL_IRQ_AC, serial_handler_ac, \"serial ac\");\n\t\t\thave_installed_ac = 1;\n\t\t}\n\t} else {\n\t\tif (!have_installed_bd) {\n\t\t\tirq_install_handler(SERIAL_IRQ_BD, serial_handler_bd, \"serial bd\");\n\t\t\thave_installed_bd = 1;\n\t\t}\n\t}\n\n\tpty->slave->gid = 2; /* dialout group */\n\tpty->slave->mask = 0660;\n\n\treturn pty->slave;\n}\n\nvoid serial_initialize(void) {\n\tserial_ac_handler = spawn_worker_thread(process_serial, \"[serial ac]\", (void*)(uintptr_t)SERIAL_PORT_A);\n\tserial_bd_handler = spawn_worker_thread(process_serial, \"[serial bd]\", (void*)(uintptr_t)SERIAL_PORT_B);\n\n\tfs_node_t * ttyS0 = serial_device_create(SERIAL_PORT_A); vfs_mount(DEV_PATH \"0\", ttyS0, \"pc-serial\", \"a\");\n\tfs_node_t * ttyS1 = serial_device_create(SERIAL_PORT_B); vfs_mount(DEV_PATH \"1\", ttyS1, \"pc-serial\", \"b\");\n\tfs_node_t * ttyS2 = serial_device_create(SERIAL_PORT_C); vfs_mount(DEV_PATH \"2\", ttyS2, \"pc-serial\", \"c\");\n\tfs_node_t * ttyS3 = serial_device_create(SERIAL_PORT_D); vfs_mount(DEV_PATH \"3\", ttyS3, \"pc-serial\", \"d\");\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/smp.c",
    "content": "/**\n * @file  kernel/arch/x86_64/smp.c\n * @brief Multi-processor Support for x86-64.\n *\n * Locates and bootstraps APs using ACPI MADT tables.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/process.h>\n#include <kernel/printf.h>\n#include <kernel/misc.h>\n#include <kernel/args.h>\n#include <kernel/time.h>\n#include <kernel/multiboot.h>\n#include <kernel/mmu.h>\n#include <kernel/arch/x86_64/acpi.h>\n\n__attribute__((used))\n__attribute__((naked))\nstatic void __ap_bootstrap(void) {\n\tasm volatile (\n\t\t\".section .shit\\n\"\n\t\t\".code16\\n\"\n\t\t\".org 0x0\\n\"\n\t\t\".global _ap_bootstrap_start\\n\"\n\t\t\"_ap_bootstrap_start:\\n\"\n\n\t\t/* Enable PAE, paging */\n\t\t\"mov $0xA0, %%eax\\n\"\n\t\t\"mov %%eax, %%cr4\\n\"\n\n\t\t/* Kernel base PML4 */\n\t\t\"mov $0x77777777, %%edx\\n\"\n\t\t\"mov %%edx, %%cr3\\n\"\n\n\t\t/* Set LME */\n\t\t\"mov $0xc0000080, %%ecx\\n\"\n\t\t\"rdmsr\\n\"\n\t\t\"or $0x100, %%eax\\n\"\n\t\t\"wrmsr\\n\"\n\n\t\t/* Enable long mode */\n\t\t\"mov $0x80000011, %%ebx\\n\"\n\t\t\"mov  %%ebx, %%cr0\\n\"\n\n\t\t/* Set up basic GDT */\n\t\t\"addr32 lgdtl %%cs:_ap_bootstrap_gdtp-_ap_bootstrap_start\\n\"\n\n\t\t/* Jump... */\n\t\t\"data32 jmp $0x08,$0x5A5A5A5A\\n\"\n\n\t\t\".global _ap_bootstrap_gdtp\\n\"\n\t\t\".align 16\\n\"\n\t\t\"_ap_bootstrap_gdtp:\\n\"\n\t\t\".word 0\\n\"\n\t\t\".quad 0\\n\"\n\n\t\t\".global _ap_bootstrap_end\\n\"\n\t\t\"_ap_bootstrap_end:\\n\"\n\t\t\".section .text\\n\"\n\t\t: : : \"memory\"\n\t);\n}\n\n__attribute__((used))\n__attribute__((naked))\nstatic void __ap_bootstrap_landing(void) {\n\tasm volatile (\n\t\t\".code64\\n\"\n\t\t\".align 16\\n\"\n\t\t\".global _ap_premain\\n\"\n\t\t\"_ap_premain:\\n\"\n\t\t\"mov $0x10, %%ax\\n\"\n\t\t\"mov %%ax, %%ds\\n\"\n\t\t\"mov %%ax, %%ss\\n\"\n\t\t\"mov $0x33, %%ax\\n\" /* TSS offset in gdt */\n\t\t\"ltr %%ax\\n\"\n\t\t\".extern _ap_stack_base\\n\"\n\t\t\"mov _ap_stack_base(%%rip),%%rsp\\n\"\n\t\t\".extern ap_main\\n\"\n\t\t\"callq ap_main\\n\"\n\t\t: : : \"memory\"\n\t);\n}\n\nextern char _ap_bootstrap_start[];\nextern char _ap_bootstrap_end[];\nextern char _ap_bootstrap_gdtp[];\nextern char _ap_premain[];\nextern size_t arch_cpu_mhz(void);\nextern void gdt_copy_to_trampoline(int ap, char * trampoline);\nextern void arch_set_core_base(uintptr_t base);\nextern void fpu_initialize(void);\nextern void idt_ap_install(void);\nextern void pat_initialize(void);\nextern process_t * spawn_kidle(int);\nextern union PML init_page_region[];\n\n/**\n * @brief Read the timestamp counter.\n *\n * This is duplicated in a couple of places as it's a quick\n * inline wrapper for 'rdtsc'.\n */\nstatic inline uint64_t read_tsc(void) {\n\tuint32_t lo, hi;\n\tasm volatile ( \"rdtsc\" : \"=a\"(lo), \"=d\"(hi) );\n\treturn ((uint64_t)hi << 32) | (uint64_t)lo;\n}\n\n/**\n * @brief Pause by looping on TSC.\n *\n * Used for AP startup.\n */\nstatic void short_delay(unsigned long amount) {\n\tuint64_t clock = read_tsc();\n\twhile (read_tsc() < clock + amount * arch_cpu_mhz());\n}\n\nstatic volatile int _ap_current = 0;       /**< The AP we're currently starting up; shared between @c ap_main and @c smp_initialize */\nstatic volatile int _ap_startup_flag = 0;  /**< Simple lock, shared between @c ap_main and @c smp_initialize */\nuintptr_t _ap_stack_base = 0;              /**< Stack address for this AP to use on startup; used by @c __ap_boostrap */\nuintptr_t lapic_final = 0;                 /**< MMIO region to use for APIC access. */\n\n#define cpuid(in,a,b,c,d) do { asm volatile (\"cpuid\" : \"=a\"(a),\"=b\"(b),\"=c\"(c),\"=d\"(d) : \"a\"(in)); } while(0)\n\n/**\n * @brief Obtains processor name strings from cpuid\n *\n * We store the processor names for each core (they might be different...)\n * so we can display them nicely in /proc/cpuinfo\n */\nvoid load_processor_info(void) {\n\tunsigned long a, b, unused;\n\tcpuid(0,unused,b,unused,unused);\n\n\tthis_core->cpu_manufacturer = \"Unknown\";\n\n\tif (b == 0x756e6547) {\n\t\tcpuid(1, a, b, unused, unused);\n\t\tthis_core->cpu_manufacturer = \"Intel\";\n\t\tthis_core->cpu_model        = (a >> 4) & 0x0F;\n\t\tthis_core->cpu_family       = (a >> 8) & 0x0F;\n\t} else if (b == 0x68747541) {\n\t\tcpuid(1, a, unused, unused, unused);\n\t\tthis_core->cpu_manufacturer = \"AMD\";\n\t\tthis_core->cpu_model        = (a >> 4) & 0x0F;\n\t\tthis_core->cpu_family       = (a >> 8) & 0x0F;\n\t}\n\n\tsnprintf(processor_local_data[this_core->cpu_id].cpu_model_name, 20, \"(unknown)\");\n\n\t/* See if we can get a long manufacturer strings */\n\tcpuid(0x80000000, a, unused, unused, unused);\n\tif (a >= 0x80000004) {\n\t\tuint32_t brand[12];\n\t\tcpuid(0x80000002, brand[0], brand[1], brand[2], brand[3]);\n\t\tcpuid(0x80000003, brand[4], brand[5], brand[6], brand[7]);\n\t\tcpuid(0x80000004, brand[8], brand[9], brand[10], brand[11]);\n\t\tmemcpy(processor_local_data[this_core->cpu_id].cpu_model_name, brand, 48);\n\t}\n\n\textern void syscall_entry(void);\n\tuint32_t efer_hi, efer_lo;\n\tasm volatile (\"rdmsr\" : \"=d\"(efer_hi), \"=a\"(efer_lo) : \"c\"(0xc0000080));    /* Read current EFER */\n\tasm volatile (\"wrmsr\" : : \"c\"(0xc0000080), \"d\"(efer_hi), \"a\"(efer_lo | 1)); /* Enable SYSCALL/SYSRET in EFER */\n\tasm volatile (\"wrmsr\" : : \"c\"(0xC0000081), \"d\"(0x1b0008), \"a\"(0));          /* Set segment bases in STAR */\n\tasm volatile (\"wrmsr\" : : \"c\"(0xC0000082),                                  /* Set SYSCALL entry point in LSTAR */\n\t              \"d\"((uintptr_t)&syscall_entry >> 32),\n\t              \"a\"((uintptr_t)&syscall_entry & 0xFFFFffff));\n\tasm volatile (\"wrmsr\" : : \"c\"(0xC0000084), \"d\"(0), \"a\"(0x700));             /* SFMASK: Direction flag, interrupt flag, trap flag are all cleared */\n}\n\nstatic void lapic_timer_initialize(void) {\n\t/* Enable our spurious vector register */\n\t*((volatile uint32_t*)(lapic_final + 0x0F0)) = 0x127;\n\t*((volatile uint32_t*)(lapic_final + 0x320)) = 0x7b;\n\t*((volatile uint32_t*)(lapic_final + 0x3e0)) = 1;\n\n\t/* Time our APIC timer against the TSC */\n\tuint64_t before = arch_perf_timer();\n\t*((volatile uint32_t*)(lapic_final + 0x380)) = 1000000;\n\twhile (*((volatile uint32_t*)(lapic_final + 0x390)));\n\tuint64_t after = arch_perf_timer();\n\n\tuint64_t ms = (after-before)/arch_cpu_mhz();\n\tuint64_t target = 10000000000UL / ms;\n\n\t/* Enable our APIC timer to send periodic wakeup signals */\n\t*((volatile uint32_t*)(lapic_final + 0x3e0)) = 1;\n\t*((volatile uint32_t*)(lapic_final + 0x320)) = 0x7b | 0x20000;\n\t*((volatile uint32_t*)(lapic_final + 0x380)) = target;\n}\n\n/**\n * @brief C entrypoint for APs, called by the bootstrap.\n *\n * After an AP has entered long mode, it jumps here, where\n * we do the rest of the core setup.\n */\nvoid ap_main(void) {\n\n\t/* Set the GS base to point to our 'this_core' struct. */\n\tarch_set_core_base((uintptr_t)&processor_local_data[_ap_current]);\n\n\t/* Safety check...\n\t * Make sure we're actually the core we think we are...\n\t */\n\tuint32_t ebx, _unused;\n\tcpuid(0x1,_unused,ebx,_unused,_unused);\n\tif (this_core->lapic_id != (int)(ebx >> 24)) {\n\t\tprintf(\"smp: lapic id does not match\\n\");\n\t}\n\n\t/* lidt, initialize local FPU, set up page attributes */\n\tidt_ap_install();\n\tfpu_initialize();\n\tpat_initialize();\n\n\t/* Set our pml pointers */\n\tthis_core->current_pml = &init_page_region[0];\n\n\t/* Spawn our kidle, make it our current process. */\n\tthis_core->kernel_idle_task = spawn_kidle(0);\n\tthis_core->current_process = this_core->kernel_idle_task;\n\n\t/* Collect CPU name strings. */\n\tload_processor_info();\n\n\t/* Inform BSP it can continue. */\n\t_ap_startup_flag = 1;\n\n\tlapic_timer_initialize();\n\n\t/* Enter scheduler */\n\tswitch_next();\n}\n\n/**\n * @brief MMIO write for LAPIC\n * @param addr Register address to access\n * @param value DWORD to write\n */\nvoid lapic_write(size_t addr, uint32_t value) {\n\t*((volatile uint32_t*)(lapic_final + addr)) = value;\n\tasm volatile (\"\":::\"memory\");\n}\n\n/**\n * @brief MMIO read for LAPIC\n * @param addr Register address to access\n * @return DWORD\n */\nuint32_t lapic_read(size_t addr) {\n\treturn *((volatile uint32_t*)(lapic_final + addr));\n}\n\n/**\n * @brief Send an inter-processor interrupt.\n *\n * Sends an IPI and waits for the LAPIC to signal the IPI was sent.\n *\n * @param int The interrupt to send.\n * @param val Flags to control how the IPI should be delivered\n */\nvoid lapic_send_ipi(int i, uint32_t val) {\n\tlapic_write(0x310, i << 24);\n\tlapic_write(0x300, val);\n\tdo { asm volatile (\"pause\" : : : \"memory\"); } while (lapic_read(0x300) & (1 << 12));\n}\n\n/**\n * @brief Quick dumb hex parser.\n *\n * Just to support acpi= command line flag for overriding\n * the scan address for ACPI tables...\n *\n * @param c String of hexadecimal characters, optionally prefixed with '0x'\n * @return Unsigned integer interpretation of @p c\n */\nuintptr_t xtoi(const char * c) {\n\tuintptr_t out = 0;\n\tif (c[0] == '0' && c[1] == 'x') {\n\t\tc += 2;\n\t}\n\n\twhile (*c) {\n\t\tout *= 0x10;\n\t\tif (*c >= '0' && *c <= '9') {\n\t\t\tout += (*c - '0');\n\t\t} else if (*c >= 'a' && *c <= 'f') {\n\t\t\tout += (*c - 'a' + 0xa);\n\t\t} else if (*c >= 'A' && *c <= 'F') {\n\t\t\tout += (*c - 'A' + 0xa);\n\t\t}\n\t\tc++;\n\t}\n\n\treturn out;\n}\n\n/**\n * @brief Called on main startup to initialize other cores.\n *\n * We always do this ourselves. We support a few different\n * bootloader conventions, and most of them don't support\n * starting up APs for us.\n */\nvoid smp_initialize(void) {\n\t/* Locate ACPI tables */\n\tuintptr_t scan = 0xE0000;\n\tuintptr_t scan_top = 0x100000;\n\tint good = 0;\n\n\textern struct multiboot * mboot_struct;\n\textern int mboot_is_2;\n\tif (mboot_is_2) {\n\t\t/* A multiboot2 loader should give us a \"firmware table\" address\n\t\t * that should allow us to find the RSDP. */\n\t\textern void * mboot2_find_tag(void * fromStruct, uint32_t type);\n\n\t\t/* First try for an RSDPv1 */\n\t\tscan = (uintptr_t)mboot2_find_tag(mboot_struct, 14);\n\n\t\t/* If we didn't get one of those, try for an RSDPv2 */\n\t\tif (!scan) scan = (uintptr_t)mboot2_find_tag(mboot_struct, 15);\n\t\t/* If we didn't get one of _those_, we should really be bailing here... */\n\n\t\t/* Account for the tag header. */\n\t\tscan += 8;\n\t\tscan_top = scan + 0x100000;\n\t} else if (mboot_struct->config_table) {\n\t\t/*\n\t\t * @warning This is specific to ToaruOS's native loader.\n\t\t * We steal the config_table entry in our EFI loader to pass the RSDP,\n\t\t * just like a multiboot2 loader would...\n\t\t */\n\t\tscan = mboot_struct->config_table;\n\t\tscan_top = scan + 0x100000;\n\t} else if (args_present(\"acpi\")) {\n\t\t/* If all else fails, you can provide the address yourself on the command line */\n\t\tscan = xtoi(args_value(\"acpi\"));\n\t\tscan_top = scan + 0x100000;\n\t}\n\n\t/* Look for it the RSDP */\n\tfor (; scan < scan_top; scan += 16) {\n\t\tchar * _scan = mmu_map_from_physical(scan);\n\t\tif (_scan[0] == 'R' &&\n\t\t\t_scan[1] == 'S' &&\n\t\t\t_scan[2] == 'D' &&\n\t\t\t_scan[3] == ' ' &&\n\t\t\t_scan[4] == 'P' &&\n\t\t\t_scan[5] == 'T' &&\n\t\t\t_scan[6] == 'R') {\n\t\t\tgood = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* I don't know why we do this here... */\n\tload_processor_info();\n\n\t/* Did we still not find our table? */\n\tif (!good) {\n\t\tdprintf(\"smp: No RSD PTR found\\n\");\n\t\tgoto _pit_fallback;\n\t}\n\n\t/* Map the ACPI RSDP */\n\tstruct rsdp_descriptor * rsdp = (struct rsdp_descriptor *)mmu_map_from_physical(scan);\n\n\t/* Validate the checksum */\n\tuint8_t check = 0;\n\tuint8_t * tmp;\n\tfor (tmp = (uint8_t *)rsdp; (uintptr_t)tmp < (uintptr_t)rsdp + sizeof(struct rsdp_descriptor); tmp++) {\n\t\tcheck += *tmp;\n\t}\n\n\t/* Did the checksum fail? */\n\tif (check != 0 && !args_present(\"noacpichecksum\")) {\n\t\tdprintf(\"smp: Bad checksum on RSDP (add 'noacpichecksum' to ignore this)\\n\");\n\t\tgoto _pit_fallback; /* bad checksum */\n\t}\n\n\t/* Was SMP disabled by a commandline flag? */\n\tif (args_present(\"nosmp\")) goto _pit_fallback;\n\n\t/* Map the RSDT from the address given by the RSDP */\n\tstruct rsdt * rsdt = mmu_map_from_physical(rsdp->rsdt_address);\n\n\tint cores = 0;\n\tuintptr_t lapic_base = 0x0;\n\tfor (unsigned int i = 0; i < (rsdt->header.length - 36) / 4; ++i) {\n\t\tuint8_t * table = mmu_map_from_physical(rsdt->pointers[i]);\n\t\tif (table[0] == 'A' && table[1] == 'P' && table[2] == 'I' && table[3] == 'C') {\n\t\t\t/* APIC table! Let's find some CPUs! */\n\t\t\tstruct madt * madt = (void*)table;\n\t\t\tlapic_base = madt->lapic_addr;\n\t\t\tfor (uint8_t * entry = madt->entries; entry < table + madt->header.length; entry += entry[1]) {\n\t\t\t\tswitch (entry[0]) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tif (entry[4] & 0x01) {\n\t\t\t\t\t\t\tif (cores == 32) { /* TODO define this somewhere better */\n\t\t\t\t\t\t\t\tprintf(\"smp: too many cores\\n\");\n\t\t\t\t\t\t\t\tgoto _toomany;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprocessor_local_data[cores].cpu_id = cores;\n\t\t\t\t\t\t\tprocessor_local_data[cores].lapic_id = entry[3];\n\t\t\t\t\t\t\tcores++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t/* TODO: Other entries */\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n_toomany:\n\tif (!lapic_base) goto _pit_fallback;\n\n\t/* Allocate a virtual address with which we can poke the lapic */\n\tlapic_final = (uintptr_t)mmu_map_mmio_region(lapic_base, 0x1000);\n\tlapic_timer_initialize();\n\n\tif (cores <= 1) return;\n\n\t/* Get a page we can backup the previous contents of the bootstrap target page to, as it probably has mmap crap in multiboot2 */\n\tuintptr_t tmp_space = mmu_allocate_a_frame() << 12;\n\tmemcpy(mmu_map_from_physical(tmp_space), mmu_map_from_physical(0x1000), 0x1000);\n\n\t*(uint32_t*)(&_ap_bootstrap_start[0xb])  = (uintptr_t)&init_page_region;\n\t*(uint32_t*)(&_ap_bootstrap_start[0x37]) = (uintptr_t)&_ap_premain;\n\n\t/* Map the bootstrap code */\n\tmemcpy(mmu_map_from_physical(0x1000), &_ap_bootstrap_start, (uintptr_t)&_ap_bootstrap_end - (uintptr_t)&_ap_bootstrap_start);\n\n\tfor (int i = 1; i < cores; ++i) {\n\t\t_ap_startup_flag = 0;\n\n\t\t/* Set gdt pointer value */\n\t\tgdt_copy_to_trampoline(i, (char*)mmu_map_from_physical(0x1000) + ((uintptr_t)&_ap_bootstrap_gdtp - (uintptr_t)&_ap_bootstrap_start));\n\n\t\t/* Make an initial stack for this AP */\n\t\t_ap_stack_base = (uintptr_t)valloc(KERNEL_STACK_SIZE)+ KERNEL_STACK_SIZE;\n\n\t\t_ap_current = i;\n\n\t\t/* Send INIT */\n\t\tlapic_send_ipi(processor_local_data[i].lapic_id, 0x4500);\n\t\tshort_delay(5000UL);\n\n\t\t/* Send SIPI */\n\t\tlapic_send_ipi(processor_local_data[i].lapic_id, 0x4601);\n\n\t\t/* Wait for AP to signal it is ready before starting next AP */\n\t\tdo { asm volatile (\"pause\" : : : \"memory\"); } while (!_ap_startup_flag);\n\n\t\tprocessor_count++;\n\t}\n\n\t/* Copy data back */\n\tmemcpy(mmu_map_from_physical(0x1000), mmu_map_from_physical(tmp_space), 0x1000);\n\tmmu_frame_clear(tmp_space);\n\n\tdprintf(\"smp: enabled with %d cores\\n\", cores);\n\treturn;\n\n_pit_fallback:\n\tdprintf(\"pit: falling back to pit as preempt source\\n\");\n\textern void pit_initialize(void);\n\tpit_initialize();\n}\n\n/**\n * @brief Send a soft IPI to all other cores.\n *\n * This is called by the scheduler when a process enters the ready queue,\n * to give other CPUs a chance to pick it up before their timer interrupt\n * fires. This is a soft interrupt: It should be ignored by the receiving\n * cores if they are busy with other things - we only want it to wake up\n * the HLT in the kernel idle task.\n *\n * TODO We could make this more fine-grained and deliver only to processors\n *      we think are ready, or to specific processors to aid in affinity?\n */\nvoid arch_wakeup_others(void) {\n\tif (!lapic_final || processor_count < 2) return;\n\t/* Send broadcast IPI to others; this is a soft interrupt\n\t * that just nudges idle cores out of their HLT states.\n\t * It should be gentle enough that busy cores dont't care. */\n\tlapic_send_ipi(0, 0x7E | (3 << 18));\n}\n\n/**\n * @brief Trigger a TLB shootdown on other cores.\n *\n * XXX This is really dumb; we just send an IPI to everyone else\n *     and they reload CR3...\n *\n * @param vaddr Should have the address to flush, but not actually used.\n */\nvoid arch_tlb_shootdown(uintptr_t vaddr) {\n\tif (!lapic_final || processor_count < 2) return;\n\n\t/*\n\t * We should be checking if this address can be sensibly\n\t * mapped somewhere else before IPIing everyone...\n\t */\n\n\tlapic_send_ipi(0, 0x7C | (3 << 18));\n}\n"
  },
  {
    "path": "kernel/arch/x86_64/user.c",
    "content": "/**\n * @file  kernel/arch/x86_64/user.c\n * @brief Various assembly snippets for jumping to usermode and back.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdint.h>\n#include <errno.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/mmu.h>\n#include <kernel/syscall.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/ports.h>\n\n/**\n * @brief Enter userspace.\n *\n * Called by process startup.\n * Does not return.\n *\n * @param entrypoint Address to \"return\" to in userspace.\n * @param argc       Number of arguments to provide to the new process.\n * @param argv       Argument array to pass to the new process; make sure this is user-accessible!\n * @param envp       Environment strings array\n * @param stack      Userspace stack address.\n */\nvoid arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack) {\n\tstruct regs ret;\n\tret.cs = 0x28 | 0x03;\n\tret.ss = 0x20 | 0x03;\n\tret.rip = entrypoint;\n\tret.rflags = (1 << 21) | (1 << 9);\n\tret.rsp = stack;\n\n\tupdate_process_times_on_exit();\n\n\tasm volatile (\n\t\t\"pushq %0\\n\"\n\t\t\"pushq %1\\n\"\n\t\t\"pushq %2\\n\"\n\t\t\"pushq %3\\n\"\n\t\t\"pushq %4\\n\"\n\t\t\"swapgs\\n\"\n\t\t\"iretq\"\n\t: : \"m\"(ret.ss), \"m\"(ret.rsp), \"m\"(ret.rflags), \"m\"(ret.cs), \"m\"(ret.rip),\n\t    \"D\"(argc), \"S\"(argv), \"d\"(envp));\n}\n\nstatic void _kill_it(void) {\n\tdprintf(\"core %d (pid=%d %s): invalid stack for signal return\\n\",\n\t\tthis_core->cpu_id, this_core->current_process->id, this_core->current_process->name);\n\ttask_exit(((128 + SIGSEGV) << 8) | SIGSEGV);\n}\n\n#define PUSH(stack, type, item) do { \\\n\tstack -= sizeof(type); \\\n\tif (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),MMU_PTR_WRITE)) \\\n\t\t_kill_it(); \\\n\t*((volatile type *) stack) = item; \\\n} while (0)\n\n#define POP(stack, type, item) do { \\\n\tif (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),0)) \\\n\t\t_kill_it(); \\\n\titem = *((volatile type *) stack); \\\n\tstack += sizeof(type); \\\n} while (0)\n\nint arch_return_from_signal_handler(struct regs *r) {\n\n\tfor (int i = 0; i < 64; ++i) {\n\t\tPOP(r->rsp, uint64_t, this_core->current_process->thread.fp_regs[63-i]);\n\t}\n\n\tarch_restore_floating((process_t*)this_core->current_process);\n\n\tPOP(r->rsp, sigset_t, this_core->current_process->blocked_signals);\n\tlong originalSignal;\n\tPOP(r->rsp, long, originalSignal);\n\n\tPOP(r->rsp, long, this_core->current_process->interrupted_system_call);\n\n\tstruct regs out;\n\tPOP(r->rsp, struct regs, out);\n\n#define R(n) r-> n = out. n\n\n\tR(r15); R(r14); R(r13); R(r12);\n\tR(r11); R(r10); R(r9); R(r8);\n\tR(rbp); R(rdi); R(rsi); R(rdx); R(rcx); R(rbx); R(rax);\n\n\tR(rip);\n\tR(rsp);\n\n\tr->rflags = (out.rflags & 0xcd5) | (1 << 21) | (1 << 9) | ((r->rflags & (1 << 8)) ? (1 << 8) : 0);\n\treturn originalSignal;\n}\n\n\n/**\n * @brief Enter a userspace signal handler.\n *\n * Similar to @c arch_enter_user but also setups up magic return addresses.\n *\n * Since signal handlers do to take complicated argument arrays, this only\n * supplies a @p signum argument.\n *\n * Does not return.\n *\n * @param entrypoint Userspace address of the signal handler, set by the process.\n * @param signum     Signal number that caused this entry.\n */\nvoid arch_enter_signal_handler(uintptr_t entrypoint, int signum, struct regs *r) {\n\tstruct regs ret;\n\tret.cs = 0x28 | 0x03;\n\tret.ss = 0x20 | 0x03;\n\tret.rip = entrypoint;\n\tret.rflags = (1 << 21) | (1 << 9);\n\tret.rsp = (r->rsp - 128) & 0xFFFFFFFFFFFFFFF0; /* ensure considerable alignment */\n\n\tPUSH(ret.rsp, struct regs, *r);\n\n\tPUSH(ret.rsp, long, this_core->current_process->interrupted_system_call);\n\tthis_core->current_process->interrupted_system_call = 0;\n\n\tPUSH(ret.rsp, long, signum);\n\tPUSH(ret.rsp, sigset_t, this_core->current_process->blocked_signals);\n\n\tstruct signal_config * config = (struct signal_config*)&this_core->current_process->signals[signum];\n\tthis_core->current_process->blocked_signals |= config->mask | (config->flags & SA_NODEFER ? 0 : (1UL << signum));\n\n\tarch_save_floating((process_t*)this_core->current_process);\n\tfor (int i = 0; i < 64; ++i) {\n\t\tPUSH(ret.rsp, uint64_t, this_core->current_process->thread.fp_regs[i]);\n\t}\n\n\tPUSH(ret.rsp, uintptr_t, 0x516);\n\n\tupdate_process_times_on_exit();\n\n\tasm volatile(\n\t\t\"pushq %0\\n\"\n\t\t\"pushq %1\\n\"\n\t\t\"pushq %2\\n\"\n\t\t\"pushq %3\\n\"\n\t\t\"pushq %4\\n\"\n\t\t\"swapgs\\n\"\n\t\t\"iretq\"\n\t: : \"m\"(ret.ss), \"m\"(ret.rsp), \"m\"(ret.rflags), \"m\"(ret.cs), \"m\"(ret.rip),\n\t    \"D\"(signum));\n\t__builtin_unreachable();\n}\n\n/**\n * @brief Return from fork or clone.\n *\n * This is what we inject as the stored rip for a new thread,\n * so that it immediately returns from the system call.\n *\n * This is never called as a function, its address is stored\n * in the thread context of a new @c process_t.\n */\n__attribute__((naked))\nvoid arch_resume_user(void) {\n\tasm volatile (\n\t\t\"pop %r15\\n\"\n\t\t\"pop %r14\\n\"\n\t\t\"pop %r13\\n\"\n\t\t\"pop %r12\\n\"\n\t\t\"pop %r11\\n\"\n\t\t\"pop %r10\\n\"\n\t\t\"pop %r9\\n\"\n\t\t\"pop %r8\\n\"\n\t\t\"pop %rbp\\n\"\n\t\t\"pop %rdi\\n\"\n\t\t\"pop %rsi\\n\"\n\t\t\"pop %rdx\\n\"\n\t\t\"pop %rcx\\n\"\n\t\t\"pop %rbx\\n\"\n\t\t\"pop %rax\\n\"\n\t\t\"add $16, %rsp\\n\"\n\t\t\"swapgs\\n\"\n\t\t\"iretq\\n\"\n\t);\n\t__builtin_unreachable();\n}\n\n/**\n * @brief Save FPU registers for this thread.\n */\nvoid arch_restore_floating(process_t * proc) {\n\tasm volatile (\"fxrstor (%0)\" :: \"r\"(&proc->thread.fp_regs));\n}\n\n/**\n * @brief Restore FPU registers for this thread.\n */\nvoid arch_save_floating(process_t * proc) {\n\tasm volatile (\"fxsave (%0)\" :: \"r\"(&proc->thread.fp_regs));\n}\n\n/**\n * @brief Called in a loop by kernel idle tasks.\n *\n * Turns on and waits for interrupts.\n * There is room for improvement here with other power states,\n * but HLT is \"good enough\" for us.\n */\nvoid arch_pause(void) {\n\tasm volatile (\n\t\t\"sti\\n\"\n\t\t\"hlt\\n\"\n\t\t\"cli\\n\"\n\t);\n}\n\nextern void lapic_send_ipi(int i, uint32_t val);\n\n/**\n * @brief Prepare for a fatal event by stopping all other cores.\n *\n * Sends an IPI to all other CPUs to tell them to immediately stop.\n * This causes an NMI (isr2), which disables interrupts and loops\n * on a hlt instruction.\n *\n * Ensures that we can then print tracebacks and do other complicated\n * things without having to mess with locks, and without other\n * processors causing further damage in the case of a fatal error.\n */\nvoid arch_fatal_prepare(void) {\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tif (i == this_core->cpu_id) continue;\n\t\tlapic_send_ipi(processor_local_data[i].lapic_id, 0x447D);\n\t}\n}\n\n/**\n * @brief Halt all processors, including this one.\n * @see arch_fatal_prepare\n */\nvoid arch_fatal(void) {\n\tarch_fatal_prepare();\n\twhile (1) {\n\t\tasm volatile (\n\t\t\t\"cli\\n\"\n\t\t\t\"hlt\\n\"\n\t\t);\n\t}\n}\n\n/**\n * @brief Reboot the computer.\n *\n * This tries to do a \"keyboard reset\". We clear out the IDT\n * so that we can maybe triple fault, and then we try to use\n * the keyboard reset vector... if that doesn't work,\n * then returning from this and letting anything else happen\n * almost certainly will.\n */\nlong arch_reboot(void) {\n\t/* load a null page as an IDT */\n\tuintptr_t frame = mmu_allocate_a_frame();\n\tuintptr_t * idt = mmu_map_from_physical(frame << 12);\n\tmemset(idt, 0, 0x1000);\n\tasm volatile (\n\t\t\"lidt (%0)\"\n\t\t: : \"r\"(idt)\n\t);\n\tuint8_t out = 0x02;\n\twhile ((out & 0x02) != 0) {\n\t\tout = inportb(0x64);\n\t}\n\toutportb(0x64, 0xFE); /* Reset */\n\treturn 0;\n}\n\n/* Syscall parameter accessors */\nvoid arch_syscall_return(struct regs * r, long retval) { r->rax = retval; }\nlong arch_syscall_number(struct regs * r) { return (unsigned long)r->rax; }\nlong arch_syscall_arg0(struct regs * r) { return r->rdi; }\nlong arch_syscall_arg1(struct regs * r) { return r->rsi; }\nlong arch_syscall_arg2(struct regs * r) { return r->rdx; }\nlong arch_syscall_arg3(struct regs * r) { return r->r10; }\nlong arch_syscall_arg4(struct regs * r) { return r->r8; }\nlong arch_stack_pointer(struct regs * r) { return r->rsp; }\nlong arch_user_ip(struct regs * r) { return r->rip; }\n"
  },
  {
    "path": "kernel/audio/snd.c",
    "content": "/**\n * @file  kernel/audio/snd.c\n * @brief Gerow's Audio Subsystem for ToaruOS\n *\n * Simple generic mixer interface. Allows userspace to pipe audio data\n * to the kernel audio drivers and control volume knobs.\n *\n * Currently has the ability to mix several sound sources together. Could use\n * a /dev/mixer device to allow changing of audio settings. Also could use\n * the ability to change frequency and format for audio samples. Also doesn't\n * really support multiple devices despite the interface suggesting it might...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2021 K. Lange\n * Copyright (C) 2015 Mike Gerow\n */\n\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/ringbuffer.h>\n#include <kernel/list.h>\n#include <kernel/printf.h>\n#include <kernel/spinlock.h>\n\n#include <kernel/mod/snd.h>\n#include <errno.h>\n\n/* Utility macros */\n#define N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0]))\n#define MIN(a,b) ((a) < (b) ? (a) : (b))\n\n#define SND_BUF_SIZE 0x4000\n\nstatic ssize_t snd_dsp_write(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer);\nstatic int snd_dsp_ioctl(fs_node_t * node, unsigned long request, void * argp);\nstatic void snd_dsp_open(fs_node_t * node, unsigned int flags);\nstatic void snd_dsp_close(fs_node_t * node);\n\nstatic int snd_mixer_ioctl(fs_node_t * node, unsigned long request, void * argp);\nstatic void snd_mixer_open(fs_node_t * node, unsigned int flags);\nstatic void snd_mixer_close(fs_node_t * node);\n\nstatic spin_lock_t _devices_lock;\n\nstatic list_t _devices;\nstatic fs_node_t _dsp_fnode = {\n\t.name   = \"dsp\",\n\t.device = &_devices,\n\t.mask   = 0666,\n\t.flags  = FS_CHARDEVICE,\n\t.ioctl  = snd_dsp_ioctl,\n\t.write  = snd_dsp_write,\n\t.open   = snd_dsp_open,\n\t.close  = snd_dsp_close,\n};\nstatic fs_node_t _mixer_fnode = {\n\t.name  = \"mixer\",\n\t.mask  = 0666,\n\t.flags = FS_CHARDEVICE,\n\t.ioctl = snd_mixer_ioctl,\n\t.open  = snd_mixer_open,\n\t.close = snd_mixer_close,\n};\nstatic spin_lock_t _buffers_lock;\nstatic list_t _buffers;\nstatic uint32_t _next_device_id = SND_DEVICE_MAIN;\n\nstruct dsp_node {\n\tring_buffer_t * rb;\n\tsize_t samples;\n\tsize_t written;\n\tint realtime;\n};\n\nint snd_register(snd_device_t * device) {\n\tint rv = 0;\n\n\tspin_lock(_devices_lock);\n\tdevice->id = _next_device_id;\n\t_next_device_id++;\n\tif (list_find(&_devices, device)) {\n\t\trv = -1;\n\t\tgoto snd_register_cleanup;\n\t}\n\tlist_insert(&_devices, device);\n\nsnd_register_cleanup:\n\tspin_unlock(_devices_lock);\n\treturn rv;\n}\n\nint snd_unregister(snd_device_t * device) {\n\tint rv = 0;\n\n\tnode_t * node = list_find(&_devices, device);\n\tif (!node) {\n\t\tprintf(\"attempted to unregister unknown audio sink: %s\\n\", device->name);\n\t\tgoto snd_unregister_cleanup;\n\t}\n\tlist_delete(&_devices, node);\n\nsnd_unregister_cleanup:\n\tspin_unlock(_devices_lock);\n\treturn rv;\n}\n\nstatic ssize_t snd_dsp_write(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tif (!_devices.length) return -1; /* No sink available. */\n\n\tstruct dsp_node * dsp = node->device;\n\n\tsize_t s = ring_buffer_available(dsp->rb);\n\tsize_t out;\n\tif (size > s && dsp->realtime) {\n\t\tout = ring_buffer_write(dsp->rb, s & ~0x3, buffer);\n\t} else {\n\t\tout = ring_buffer_write(dsp->rb, size, buffer);\n\t}\n\tdsp->written += out / 4;\n\n\treturn out;\n}\n\nstatic int snd_dsp_ioctl(fs_node_t * node, unsigned long request, void * argp) {\n\t/* Potentially use this to set sample rates in the future */\n\tstruct dsp_node * dsp = node->device;\n\tif (request == 4) {\n\t\tdsp->realtime = 1;\n\t} else if (request == 5) {\n\t\treturn dsp->samples;\n\t}\n\treturn -1;\n}\n\nstatic void snd_dsp_open(fs_node_t * node, unsigned int flags) {\n\t/*\n\t * XXX(gerow): A process could take the memory of the entire system by opening\n\t * too many of these...\n\t */\n\t/* Allocate a buffer for the node and keep a reference for ourselves */\n\n\tstruct dsp_node * dsp = malloc(sizeof(struct dsp_node));\n\tdsp->rb = ring_buffer_create(SND_BUF_SIZE);\n\tdsp->samples = 0;\n\tdsp->written = 0;\n\tdsp->realtime = 0;\n\tnode->device = dsp;\n\tspin_lock(_buffers_lock);\n\tlist_insert(&_buffers, node->device);\n\tspin_unlock(_buffers_lock);\n}\n\nstatic void snd_dsp_close(fs_node_t * node) {\n\tstruct dsp_node * dsp = node->device;\n\tspin_lock(_buffers_lock);\n\tlist_delete(&_buffers, list_find(&_buffers, dsp));\n\tspin_unlock(_buffers_lock);\n\n\tring_buffer_destroy(dsp->rb);\n\tfree(dsp->rb);\n\tfree(dsp);\n}\n\nstatic snd_device_t * snd_device_by_id(uint32_t device_id) {\n\tspin_lock(_devices_lock);\n\tsnd_device_t * out = NULL;\n\tsnd_device_t * cur = NULL;\n\n\tforeach(node, &_devices) {\n\t\tcur = node->value;\n\t\tif (cur->id == device_id) {\n\t\t\tout = cur;\n\t\t}\n\t}\n\tspin_unlock(_devices_lock);\n\n\treturn out;\n}\n\nstatic int snd_mixer_ioctl(fs_node_t * node, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase SND_MIXER_GET_KNOBS: {\n\t\t\tsnd_knob_list_t * list = argp;\n\t\t\tsnd_device_t * device = snd_device_by_id(list->device);\n\t\t\tif (!device) {\n\t\t\t\treturn -EINVAL;\n\t\t\t}\n\t\t\tlist->num = device->num_knobs;\n\t\t\tfor (uint32_t i = 0; i < device->num_knobs; i++) {\n\t\t\t\tlist->ids[i] = device->knobs[i].id;\n\t\t\t}\n\t\t\treturn 0;\n\t\t}\n\t\tcase SND_MIXER_GET_KNOB_INFO: {\n\t\t\tsnd_knob_info_t * info = argp;\n\t\t\tsnd_device_t * device = snd_device_by_id(info->device);\n\t\t\tif (!device) {\n\t\t\t\treturn -EINVAL;\n\t\t\t}\n\t\t\tfor (uint32_t i = 0; i < device->num_knobs; i++) {\n\t\t\t\tif (device->knobs[i].id == info->id) {\n\t\t\t\t\tmemcpy(info->name, device->knobs[i].name, sizeof(info->name));\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn -EINVAL;\n\t\t}\n\t\tcase SND_MIXER_READ_KNOB: {\n\t\t\tsnd_knob_value_t * value = argp;\n\t\t\tsnd_device_t * device = snd_device_by_id(value->device);\n\t\t\tif (!device) {\n\t\t\t\treturn -EINVAL;\n\t\t\t}\n\t\t\treturn device->mixer_read(value->id, &value->val);\n\t\t}\n\t\tcase SND_MIXER_WRITE_KNOB: {\n\t\t\tsnd_knob_value_t * value = argp;\n\t\t\tsnd_device_t * device = snd_device_by_id(value->device);\n\t\t\tif (!device) {\n\t\t\t\treturn -EINVAL;\n\t\t\t}\n\t\t\treturn device->mixer_write(value->id, value->val);\n\t\t}\n\t\tdefault: {\n\t\t\treturn -EINVAL;\n\t\t}\n\t}\n}\n\nstatic void snd_mixer_open(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void snd_mixer_close(fs_node_t * node) {\n\treturn;\n}\n\nint snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer) {\n\tstatic int16_t tmp_buf[0x100];\n\n\tmemset(buffer, 0, size);\n\n\tspin_lock(_buffers_lock);\n\tforeach(buf_node, &_buffers) {\n\t\tstruct dsp_node * dsp = buf_node->value;\n\t\tring_buffer_t * buf = dsp->rb;\n\t\t/* ~0x3 is to ensure we don't read partial samples or just a single channel */\n\t\tsize_t bytes_left = MIN(ring_buffer_unread(buf) & ~0x3, size);\n\t\tint16_t * adding_ptr = (int16_t *) buffer;\n\t\twhile (bytes_left) {\n\t\t\tsize_t this_read_size = MIN(bytes_left, sizeof(tmp_buf));\n\t\t\tring_buffer_read(buf, this_read_size, (uint8_t *)tmp_buf);\n\t\t\tdsp->samples += this_read_size / 4; /* 16 bits, 2 channels */\n\t\t\t/*\n\t\t\t * Reduce the sample by a half so that multiple sources won't immediately\n\t\t\t * cause awful clipping. This is kind of a hack since it would probably be\n\t\t\t * better to just use some kind of compressor.\n\t\t\t */\n\t\t\tfor (size_t i = 0; i < N_ELEMENTS(tmp_buf); i++) {\n\t\t\t\ttmp_buf[i] /= 2;\n\t\t\t}\n\t\t\tfor (size_t i = 0; i < this_read_size / sizeof(*adding_ptr); i++) {\n\t\t\t\tadding_ptr[i] += tmp_buf[i];\n\t\t\t}\n\t\t\tadding_ptr += this_read_size / sizeof(*adding_ptr);\n\t\t\tbytes_left -= this_read_size;\n\t\t}\n\t}\n\tspin_unlock(_buffers_lock);\n\n\treturn size;\n}\n\nstatic snd_device_t * snd_main_device(void) {\n\tspin_lock(_devices_lock);\n\tforeach(node, &_devices) {\n\t\tspin_unlock(_devices_lock);\n\t\treturn node->value;\n\t}\n\n\tspin_unlock(_devices_lock);\n\treturn NULL;\n}\n\nvoid snd_install(void) {\n\tvfs_mount(\"/dev/dsp\", &_dsp_fnode, \"dsp\", \"\");\n\tvfs_mount(\"/dev/mixer\", &_mixer_fnode, \"mixer\", \"\");\n}\n\n"
  },
  {
    "path": "kernel/binfmt.c",
    "content": "/**\n * @file  kernel/binfmt.c\n * @brief Top-level executable parsing.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/mmu.h>\n#include <kernel/elf.h>\n#include <sys/time.h>\n\nextern int elf_exec(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp);\nint exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth);\n\n/**\n * @brief hash-exclamation parser\n *\n * Tries to safely read the first line of a script file to find an appropriate loader.\n */\nint exec_shebang(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp) {\n\tif (interp > 4) {\n\t\t/* If an interpreter calls an interpreter too many times, bail. */\n\t\treturn -ELOOP;\n\t}\n\n\t/* Read MAX_LINE... */\n\tchar tmp[100];\n\tread_fs(file, 0, 100, (unsigned char *)tmp); close_fs(file);\n\tchar * cmd = (char *)&tmp[2];\n\tif (*cmd == ' ') cmd++; /* Handle a leading space */\n\tchar * space_or_linefeed = strpbrk(cmd, \" \\n\");\n\tchar * arg = NULL;\n\n\t/* We read too much stuff before finding EOL or another signal\n\t * that the interpreter was found, so bail. */\n\tif (!space_or_linefeed) {\n\t\treturn -ENOEXEC;\n\t}\n\n\t/* If we found a space, accept one argument before the path... */\n\tif (*space_or_linefeed == ' ') {\n\t\t*space_or_linefeed = '\\0';\n\t\tspace_or_linefeed++;\n\t\targ = space_or_linefeed;\n\t\t/* ... and look for another EOL. */\n\t\tspace_or_linefeed = strpbrk(space_or_linefeed, \"\\n\");\n\t\tif (!space_or_linefeed) {\n\t\t\t/* If we didn't find one, bail. */\n\t\t\treturn -ENOEXEC;\n\t\t}\n\t}\n\n\t/* Make sure interpreter or argument is nil-terminated */\n\t*space_or_linefeed = '\\0';\n\n\tchar script[strlen(path)+1];\n\tmemcpy(script, path, strlen(path)+1);\n\n\tunsigned int nargc = argc + (arg ? 2 : 1);\n\tchar * args[nargc + 2];\n\targs[0] = cmd;\n\targs[1] = arg ? arg : script;\n\targs[2] = arg ? script : NULL;\n\targs[3] = NULL;\n\n\tint j = arg ? 3 : 2;\n\tfor (int i = 1; i < argc; ++i, ++j) {\n\t\targs[j] = argv[i];\n\t}\n\targs[j] = NULL;\n\n\t/* Try to execut the interpreter with the new arguments */\n\treturn exec(cmd, nargc, args, env, interp+1);\n}\n\n/* Consider exposing this and making it a list so it can be extended ... */\ntypedef int (*exec_func)(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp);\ntypedef struct {\n\texec_func func;\n\tunsigned char bytes[4];\n\tunsigned int  match;\n\tconst char * name;\n} exec_def_t;\n\nexec_def_t fmts[] = {\n\t{elf_exec, {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3}, 4, \"ELF\"},\n\t{exec_shebang, {'#', '!', 0, 0}, 2, \"#!\"},\n};\n\nstatic int matches(unsigned char * a, unsigned char * b, unsigned int len) {\n\tfor (unsigned int i = 0; i < len; ++i) {\n\t\tif (a[i] != b[i]) return 0;\n\t}\n\treturn 1;\n}\n\n/**\n * @brief Replace the current process with a new one.\n *\n * @param path Filename of the new executable.\n * @param argc Number of arguments passed in @p argv\n * @param argv Arguments to supply to the new executable's entry point.\n * @param env  Environment strings to pass to the new executable.\n * @param interp_depth Should be 0 for all external callers.\n * @returns Either never or -ENOEXEC on failure.\n */\nint exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth) {\n\tfs_node_t * file = kopen(path, 0);\n\tif (!file) return -ENOENT;\n\tif (!has_permission(file, 01)) return -EACCES;\n\n\tunsigned char head[4];\n\tread_fs(file, 0, 4, head);\n\n\tthis_core->current_process->name = strdup(path);\n\tgettimeofday((struct timeval*)&this_core->current_process->start, NULL);\n\n\tfor (unsigned int i = 0; i < sizeof(fmts) / sizeof(exec_def_t); ++i) {\n\t\tif (matches(fmts[i].bytes, head, fmts[i].match)) {\n\t\t\treturn fmts[i].func(path, file, argc, argv, env, interp_depth);\n\t\t}\n\t}\n\treturn -ENOEXEC;\n}\n\n/**\n * This is generally only called by system startup code to launch /bin/init.\n * Copies arguments from kernel constants into the heap, sets up a new MMU context\n * from the kernel boot context, and then calls @ref exec.\n */\nint system(const char * path, int argc, char *const argv[], char *const envin[]) {\n\tchar ** argv_ = malloc(sizeof(char*) * (argc + 1));\n\tfor (int j = 0; j < argc; ++j) {\n\t\targv_[j] = malloc((strlen(argv[j]) + 1));\n\t\tmemcpy((void*)argv_[j], argv[j], strlen(argv[j]) + 1);\n\t}\n\targv_[argc] = NULL;\n\tchar * env[] = {NULL};\n\tthis_core->current_process->thread.page_directory = malloc(sizeof(page_directory_t));\n\tthis_core->current_process->thread.page_directory->directory = mmu_clone(NULL); /* base PML? for exec? */\n\tthis_core->current_process->thread.page_directory->refcount = 1;\n\tspin_init(this_core->current_process->thread.page_directory->lock);\n\tmmu_set_directory(this_core->current_process->thread.page_directory->directory);\n\tthis_core->current_process->cmdline = (char**)argv_;\n\texec(path,argc,argv_,envin ? envin : env,0);\n\treturn -EINVAL;\n}\n"
  },
  {
    "path": "kernel/generic.c",
    "content": "/**\n * @file  kernel/generic.c\n * @brief Architecture-neutral startup sequences.\n *\n * The generic startup sequence is broken into two parts:\n * @c generic_startup should be called as soon as the platform\n * has configured memory and is ready for the VFS and scheduler\n * to be initialized. @c generic_main should be called after\n * the platform has set up its own device drivers, loaded any\n * early filesystems, and is ready to yield control to init.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/generic.h>\n#include <kernel/args.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/misc.h>\n\nextern int system(const char * path, int argc, const char ** argv, const char ** envin);\nextern void tarfs_register_init(void);\nextern void tmpfs_register_init(void);\nextern void tasking_start(void);\nextern void packetfs_initialize(void);\nextern void zero_initialize(void);\nextern void procfs_initialize(void);\nextern void shm_install(void);\nextern void random_initialize(void);\nextern void snd_install(void);\nextern void net_install(void);\nextern void console_initialize(void);\nextern void modules_install(void);\n\nvoid generic_startup(void) {\n\targs_parse(arch_get_cmdline());\n\tinitialize_process_tree();\n\tshm_install();\n\tvfs_install();\n\ttarfs_register_init();\n\ttmpfs_register_init();\n\tmap_vfs_directory(\"/dev\");\n\tconsole_initialize();\n\tpacketfs_initialize();\n\tzero_initialize();\n\tprocfs_initialize();\n\trandom_initialize();\n\tsnd_install();\n\tnet_install();\n\ttasking_start();\n\tmodules_install();\n}\n\nint generic_main(void) {\n\tif (args_present(\"root\")) {\n\t\tconst char * root_type = \"tar\";\n\t\tif (args_present(\"root_type\")) {\n\t\t\troot_type = args_value(\"root_type\");\n\t\t}\n\t\tvfs_mount_type(root_type,args_value(\"root\"),\"/\");\n\t}\n\n\tconst char * boot_arg = NULL;\n\n\tif (args_present(\"args\")) {\n\t\tboot_arg = strdup(args_value(\"args\"));\n\t}\n\n\tconst char * boot_app = \"/bin/init\";\n\tif (args_present(\"init\")) {\n\t\tboot_app = args_value(\"init\");\n\t}\n\n\tdprintf(\"generic: Running %s as init process.\\n\", boot_app);\n\n\tconst char * argv[] = {\n\t\tboot_app,\n\t\tboot_arg,\n\t\tNULL\n\t};\n\tint argc = 0;\n\twhile (argv[argc]) argc++;\n\tsystem(argv[0], argc, argv, NULL);\n\n\tdprintf(\"generic: Failed to execute %s.\\n\", boot_app);\n\tswitch_task(0);\n\treturn 0;\n}\n"
  },
  {
    "path": "kernel/misc/args.c",
    "content": "/**\n * @brief Kernel commandline argument parser.\n *\n * Arguments to the kernel are provided from the bootloader and\n * provide information such as what mode to pass to init, or what\n * hard disk partition should be mounted as root. We parse them\n * into a hash table for easy lookup by key.\n *\n * An argument may be value-less (having no '='), in which case\n * its value in the hash table will be NULL but it will be present.\n * Examples of value-less arguments are @c lfbwc or @c noi965.\n *\n * Arguments with values can have quoted or unquoted values. Unquoted\n * values are terminated by a space or the end of the command line and\n * are not processed for escapes. Examples of arguments with\n * unquoted values are @c root=/dev/ram0 or @c start=live-session.\n *\n * Quoted values must started immediately with a double quote (\").\n * Double quotes within the value may be escaped with a backslash (\\).\n * Backslash can also be escaped. Any other character after a\n * backslash results in both a literal backslash and the following\n * character.\n *\n * If a quoted value is not properly terminated with an unescaped\n * double quote character, the entire argument will be ignored.\n *\n * @copyright This file is part of ToaruOS and is released under the terms\n *            of the NCSA / University of Illinois License - see LICENSE.md\n *            Copyright (C) 2011-2023 K. Lange\n */\n#ifdef _KERNEL_\n# include <kernel/string.h>\n# include <kernel/args.h>\n# include <kernel/tokenize.h>\n# include <kernel/hashmap.h>\n#endif\n\nhashmap_t * kernel_args_map = NULL;\n\n/**\n * @brief Determine if an argument was passed to the kernel.\n *\n * Check if an argument was provided to the kernel. If the argument is\n * a simple switch, a response of 1 can be considered \"on\" for that\n * argument; otherwise, this just notes that the argument was present,\n * so the caller should check whether it is correctly set.\n */\nint args_present(const char * karg) {\n\tif (!kernel_args_map) return 0;\n\treturn hashmap_has(kernel_args_map, karg);\n}\n\n/**\n * @brief Return the value associated with an argument provided to the kernel.\n */\nchar * args_value(const char * karg) {\n\tif (!kernel_args_map) return 0;\n\treturn hashmap_get(kernel_args_map, karg);\n}\n\n/**\n * @brief Parse the given arguments to the kernel.\n *\n * @param arg A string containing all arguments, separated by spaces.\n */\nvoid args_parse(const char * cmdline) {\n\t/* Sanity check... */\n\tif (!cmdline) { return; }\n\tif (!kernel_args_map) { kernel_args_map = hashmap_create(10); }\n\tchar * argbuf = strdup(cmdline);\n\tchar * x = argbuf;\n\n\tfor (;;) {\n\t\twhile (*x && *x == ' ') x++; /* skip spaces */\n\t\tif (!*x) break;\n\n\t\tchar * value = NULL;\n\t\tchar * key = x;\n\t\twhile (*x && *x != '=' && *x != ' ') x++;\n\n\t\tif (*x == '=') {\n\t\t\t*x++ = '\\0';\n\t\t\tif (*x == '\"') {\n\t\t\t\t/* Start of quoted value */\n\t\t\t\tx++;\n\t\t\t\tvalue = x;\n\t\t\t\tchar * w = x;\n\n\t\t\t\twhile (*x && *x != '\"') {\n\t\t\t\t\tif (*x == '\\\\') {\n\t\t\t\t\t\tx++;\n\t\t\t\t\t\tswitch (*x) {\n\t\t\t\t\t\t\tcase '\"':  *w++ = '\"';  x++; break;\n\t\t\t\t\t\t\tcase '\\\\': *w++ = '\\\\'; x++; break;\n\t\t\t\t\t\t\tcase '\\0': goto _parse_error;\n\t\t\t\t\t\t\tdefault:   *w++ = '\\\\'; *w++ = *x++; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t*w++ = *x++;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (*x == '\"') {\n\t\t\t\t\t*w = '\\0';\n\t\t\t\t\tx++;\n\t\t\t\t} else if (*x == '\\0') {\n\t\t\t\t\tgoto _parse_error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t/* Start of unquoted value */\n\t\t\t\tvalue = x;\n\t\t\t\twhile (*x && *x != ' ') x++;\n\t\t\t\tif (*x == ' ') {\n\t\t\t\t\t*x = '\\0';\n\t\t\t\t\tx++;\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (*x == ' ') {\n\t\t\t/* Value-less argument where we need to set nil byte */\n\t\t\t*x++ = '\\0';\n\t\t}\n\n\t\thashmap_set(kernel_args_map, key, value ? strdup(value) : NULL);\n\t}\n\n_parse_error:\n\tfree(argbuf);\n\treturn;\n}\n\n#ifndef _KERNEL_\nchar * args_from_procfs(void) {\n\t/* Open */\n\tFILE * f = fopen(\"/proc/cmdline\", \"r\");\n\tif (!f) return NULL;\n\n\t/* Determine size */\n\tfseek(f, 0, SEEK_END);\n\tsize_t size = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\n\t/* Read */\n\tchar * cmdline = calloc(size + 1, 1);\n\tsize_t rsize = fread(cmdline, 1, size, f);\n\tif (cmdline[rsize-1] == '\\n') cmdline[rsize-1] = '\\0';\n\tfclose(f);\n\n\t/* Parse */\n\tkernel_args_map = hashmap_create(10);\n\targs_parse(cmdline);\n\n\treturn cmdline;\n}\n\n#endif\n\n"
  },
  {
    "path": "kernel/misc/assert.c",
    "content": "/**\n * @file  kernel/misc/assert.h\n * @brief Kernel assertion handler.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/assert.h>\n#include <kernel/printf.h>\n#include <kernel/misc.h>\n\nvoid __assert_failed(const char * file, int line, const char * func, const char * cond) {\n\tarch_fatal_prepare();\n\tdprintf(\"%s:%d (%s) Assertion failed: %s\\n\", file, line, func, cond);\n\tarch_dump_traceback();\n\tarch_fatal();\n}\n"
  },
  {
    "path": "kernel/misc/elf64.c",
    "content": "/**\n * @file kernel/misc/elf64.c\n * @brief Elf64 parsing tools for modules and static userspace binaries.\n *\n * Provides exec() for Elf64 binaries. Note that the loader only directly\n * loads static binaries; for dynamic binaries, the requested interpreter\n * is loaded, which should generally be /lib/ld.so, which should itself\n * be a static binary. This loader is platform-generic.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2023 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/symboltable.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/elf.h>\n#include <kernel/vfs.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n#include <kernel/misc.h>\n#include <kernel/ksym.h>\n#include <kernel/module.h>\n#include <kernel/hashmap.h>\n#include <kernel/mutex.h>\n\nhashmap_t * _modules_table = NULL;\nsched_mutex_t * _modules_mutex = NULL;\n\nvoid modules_install(void) {\n\t_modules_table = hashmap_create(10);\n\t_modules_mutex = mutex_init(\"module loader\");\n}\n\nhashmap_t * modules_get_list(void) {\n\treturn _modules_table;\n}\n\n/**\n * Encode immediate for ADR(p) instruction\n */\nstatic uint32_t aarch64_imm_adr(uint32_t val) {\n\tuint32_t low  = (val & 0x3) << 29;\n\tuint32_t high = ((val >> 2) & 0x7ffff) << 5;\n\treturn low | high;\n}\n\n/**\n * Encode immediate for 12-bit instructions\n */\nstatic uint32_t aarch64_imm_12(uint32_t val) {\n\treturn (val & 0xFFF) << 10;\n}\n\nint elf_module(char ** args) {\n\tint error = 0;\n\tElf64_Header header;\n\n\tfs_node_t * file = kopen(args[0],0);\n\n\tif (!file) {\n\t\treturn -ENOENT;\n\t}\n\n\tread_fs(file, 0, sizeof(Elf64_Header), (uint8_t*)&header);\n\n\tif (header.e_ident[0] != ELFMAG0 ||\n\t    header.e_ident[1] != ELFMAG1 ||\n\t    header.e_ident[2] != ELFMAG2 ||\n\t    header.e_ident[3] != ELFMAG3) {\n\t\tprintf(\"Invalid file: Bad header.\\n\");\n\t\tclose_fs(file);\n\t\treturn -EINVAL;\n\t}\n\n\tif (header.e_ident[EI_CLASS] != ELFCLASS64) {\n\t\tprintf(\"(Wrong Elf class)\\n\");\n\t\tclose_fs(file);\n\t\treturn -EINVAL;\n\t}\n\n\tif (header.e_type != ET_REL) {\n\t\tprintf(\"(Not a relocatable object)\\n\");\n\t\tclose_fs(file);\n\t\treturn -EINVAL;\n\t}\n\n\tmutex_acquire(_modules_mutex);\n\n\t/* Just slap the whole thing into memory, why not... */\n\tchar * module_load_address = mmu_map_module(file->length);\n\tread_fs(file, 0, file->length, (void*)module_load_address);\n\n\t/**\n\t * Set up section header entries to have correct loaded addresses, and map\n\t * any NOBITS sections to new memory. We'll page-align anything, which\n\t * should be good enough for any object files we make...\n\t */\n\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\tElf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i);\n\t\tif (sectionHeader->sh_type == SHT_NOBITS) {\n\t\t\tsectionHeader->sh_addr = (uintptr_t)mmu_map_module(sectionHeader->sh_size);\n\t\t\tmemset((void*)sectionHeader->sh_addr, 0, sectionHeader->sh_size);\n\t\t} else {\n\t\t\tsectionHeader->sh_addr = (uintptr_t)(module_load_address + sectionHeader->sh_offset);\n\t\t\tif (sectionHeader->sh_addralign &&\n\t\t\t\t(sectionHeader->sh_addr & (sectionHeader->sh_addralign -1))) {\n\t\t\t\tdprintf(\"mod: probably not aligned correctly: %#zx %ld\\n\",\n\t\t\t\t\tsectionHeader->sh_addr, sectionHeader->sh_addralign);\n\t\t\t}\n\t\t}\n\t}\n\n\tstruct Module * moduleData = NULL;\n\n\t/**\n\t * Let's start loading symbols...\n\t */\n\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\tElf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i);\n\t\tif (sectionHeader->sh_type != SHT_SYMTAB) continue;\n\t\tElf64_Shdr * strtab_hdr = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_link);\n\t\tchar * symNames = (char*)strtab_hdr->sh_addr;\n\t\tElf64_Sym * symTable = (Elf64_Sym*)sectionHeader->sh_addr;\n\t\t/* Uh, we should be able to figure out how many symbols we have by doing something less dumb than\n\t\t * just checking the size of the section, right? */\n\t\tfor (unsigned int sym = 0; sym < sectionHeader->sh_size / sizeof(Elf64_Sym); ++sym) {\n\t\t\t/* Unlike the previous implementation of this module loader in toaru32,\n\t\t\t * we specifically do not support binding symbols directly from newly\n\t\t\t * loaded modules. If a module wants to expose symbols, it should use\n\t\t\t * @c ksym_bind to supply new symbol names to the symbol table. */\n\n\t\t\tif (symTable[sym].st_shndx > 0 && symTable[sym].st_shndx < SHN_LOPROC) {\n\t\t\t\tElf64_Shdr * sh_hdr = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * symTable[sym].st_shndx);\n\t\t\t\tsymTable[sym].st_value = symTable[sym].st_value + sh_hdr->sh_addr;\n\t\t\t} else if (symTable[sym].st_shndx == SHN_UNDEF) {\n\t\t\t\tsymTable[sym].st_value = (uintptr_t)ksym_lookup(symNames + symTable[sym].st_name);\n\t\t\t}\n\n\t\t\tif (symTable[sym].st_name && !strcmp(symNames + symTable[sym].st_name, \"metadata\")) {\n\t\t\t\tmoduleData = (void*)symTable[sym].st_value;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!moduleData) {\n\t\terror = EINVAL;\n\t\tgoto _unmap_module;\n\t}\n\n\tfor (unsigned int i = 0; i < header.e_shnum; ++i) {\n\t\tElf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i);\n\t\tif (sectionHeader->sh_type != SHT_RELA) continue;\n\n\t\tElf64_Rela * table = (Elf64_Rela*)sectionHeader->sh_addr;\n\n\t\t/* Get the section these relocations apply to */\n\t\tElf64_Shdr * targetSection = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_info);\n\n\t\t/* Get the symbol table */\n\t\tElf64_Shdr * symbolSection = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_link);\n\t\tElf64_Sym * symbolTable = (Elf64_Sym *)symbolSection->sh_addr;\n\n#define S (symbolTable[ELF64_R_SYM(table[rela].r_info)].st_value)\n#define A (table[rela].r_addend)\n#define T32 (*(uint32_t*)target)\n#define T64 (*(uint64_t*)target)\n#define P  (target)\n\n\t\tfor (unsigned int rela = 0; rela < sectionHeader->sh_size / sizeof(Elf64_Rela); ++rela) {\n\t\t\tuintptr_t target = table[rela].r_offset + targetSection->sh_addr;\n\t\t\tswitch (ELF64_R_TYPE(table[rela].r_info)) {\n#if defined(__x86_64__)\n\t\t\t\tcase R_X86_64_64:\n\t\t\t\t\tT64 = S + A;\n\t\t\t\t\tbreak;\n\t\t\t\tcase R_X86_64_32:\n\t\t\t\t\tT32 = S + A;\n\t\t\t\t\tbreak;\n\t\t\t\tcase R_X86_64_PC32:\n\t\t\t\t\tT32 = S + A - P;\n\t\t\t\t\tbreak;\n#elif defined(__aarch64__)\n\t\t\t\tcase 275: { /* R_AARCH64_ADR_PREL_PG_HI21 */\n\t\t\t\t\tT32 = T32 | aarch64_imm_adr( ((S + A) >> 12) - (P >> 12) );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 286: /* R_AARCH64_LDST64_ABS_LO12_NC */\n\t\t\t\t\tT32 = T32 | aarch64_imm_12( ((S + A) >> 3) & 0x1FF );\n\t\t\t\t\tbreak;\n\t\t\t\tcase 282: /* R_AARCH64_JUMP26 */\n\t\t\t\tcase 283: /* R_AARCH64_CALL26 */\n\t\t\t\t\tT32 = T32 | (((S + A - P) >> 2) & 0x3ffffff);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 257: /* ABS64 */\n\t\t\t\t\tT64 = S + A;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 258: /* ABS32 */\n\t\t\t\t\tT32 = S + A;\n\t\t\t\t\tbreak;\n#endif\n\t\t\t\tdefault:\n\t\t\t\t\tdprintf(\"mod: unsupported relocation %ld found\\n\", ELF64_R_TYPE(table[rela].r_info));\n\t\t\t\t\terror = EINVAL;\n\t\t\t}\n\t\t}\n\t}\n\n#undef S\n#undef A\n#undef T32\n#undef T64\n#undef P\n\n\tif (error) goto _unmap_module;\n\n\tif (hashmap_has(_modules_table, moduleData->name)) {\n\t\terror = EEXIST;\n\t\tgoto _unmap_module;\n\t}\n\n\tstruct LoadedModule * loadedData = malloc(sizeof(struct LoadedModule));\n\tloadedData->metadata = moduleData;\n\tloadedData->baseAddress = (uintptr_t)module_load_address;\n\tloadedData->fileSize = file->length;\n\tloadedData->loadedSize = (uintptr_t)mmu_map_module(0) - (uintptr_t)module_load_address;\n\n\tclose_fs(file);\n\n\thashmap_set(_modules_table, moduleData->name, loadedData);\n\tmutex_release(_modules_mutex);\n\n\t/* Count arguments */\n\tint argc = 0;\n\tfor (char ** aa = args; *aa; ++aa) ++argc;\n\n\treturn moduleData->init(argc, args);\n\n_unmap_module:\n\tclose_fs(file);\n\n\tmmu_unmap_module((uintptr_t)module_load_address, (uintptr_t)mmu_map_module(0) - (uintptr_t)module_load_address);\n\n\tmutex_release(_modules_mutex);\n\treturn -error;\n}\n\nint elf_exec(const char * path, fs_node_t * file, int argc, const char *const argv[], const char *const env[], int interp) {\n\tElf64_Header header;\n\n\tread_fs(file, 0, sizeof(Elf64_Header), (uint8_t*)&header);\n\n\tif (header.e_ident[0] != ELFMAG0 ||\n\t    header.e_ident[1] != ELFMAG1 ||\n\t    header.e_ident[2] != ELFMAG2 ||\n\t    header.e_ident[3] != ELFMAG3) {\n\t\tprintf(\"Invalid file: Bad header.\\n\");\n\t\tclose_fs(file);\n\t\treturn -EINVAL;\n\t}\n\n\tif (header.e_ident[EI_CLASS] != ELFCLASS64) {\n\t\tprintf(\"(Wrong Elf class)\\n\");\n\t\treturn -EINVAL;\n\t}\n\n\t/* This loader can only handle basic executables. */\n\tif (header.e_type != ET_EXEC) {\n\t\tprintf(\"(Not an executable)\\n\");\n\t\t/* TODO: what about DYN? */\n\t\treturn -EINVAL;\n\t}\n\n\tif ((file->mask & S_ISUID) && !(this_core->current_process->flags & (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS))) {\n\t\t/* setuid */\n\t\tthis_core->current_process->user = file->uid;\n\t}\n\n\t/* First check if it is dynamic and needs an interpreter */\n\tfor (int i = 0; i < header.e_phnum; ++i) {\n\t\tElf64_Phdr phdr;\n\t\tread_fs(file, header.e_phoff + header.e_phentsize * i, sizeof(Elf64_Phdr), (uint8_t*)&phdr);\n\t\tif (phdr.p_type == PT_DYNAMIC) {\n\t\t\tclose_fs(file);\n\t\t\tunsigned int nargc = argc + 3;\n\t\t\tconst char * args[nargc+1]; /* oh yeah, great, a stack-allocated dynamic array... wonderful... */\n\t\t\targs[0] = \"ld.so\";\n\t\t\targs[1] = \"-e\";\n\t\t\targs[2] = strdup(this_core->current_process->name);\n\t\t\tint j = 3;\n\t\t\tfor (int i = 0; i < argc; ++i, ++j) {\n\t\t\t\targs[j] = argv[i];\n\t\t\t}\n\t\t\targs[j] = NULL;\n\t\t\tfs_node_t * file = kopen(\"/lib/ld.so\",0); /* FIXME PT_INTERP value */\n\t\t\tif (!file) return -EINVAL;\n\t\t\treturn elf_exec(NULL, file, nargc, args, env, 1);\n\t\t}\n\t}\n\n\tuintptr_t execBase = -1;\n\tuintptr_t heapBase = 0;\n\n\tmmu_set_directory(NULL);\n\tpage_directory_t * this_directory = this_core->current_process->thread.page_directory;\n\tthis_core->current_process->thread.page_directory = malloc(sizeof(page_directory_t));\n\tthis_core->current_process->thread.page_directory->refcount = 1;\n\tspin_init(this_core->current_process->thread.page_directory->lock);\n\tthis_core->current_process->thread.page_directory->directory = mmu_clone(NULL);\n\tmmu_set_directory(this_core->current_process->thread.page_directory->directory);\n\tprocess_release_directory(this_directory);\n\tfor (int i = 0; i < NUMSIGNALS; ++i) {\n\t\tif (this_core->current_process->signals[i].handler != 1) {\n\t\t\tthis_core->current_process->signals[i].handler = 0;\n\t\t\tthis_core->current_process->signals[i].flags = 0;\n\t\t}\n\t}\n\n\tfor (int i = 0; i < header.e_phnum; ++i) {\n\t\tElf64_Phdr phdr;\n\t\tread_fs(file, header.e_phoff + header.e_phentsize * i, sizeof(Elf64_Phdr), (uint8_t*)&phdr);\n\t\tif (phdr.p_type == PT_LOAD) {\n\t\t\tfor (uintptr_t i = phdr.p_vaddr; i < phdr.p_vaddr + phdr.p_memsz; i += 0x1000) {\n\t\t\t\tunion PML * page = mmu_get_page(i, MMU_GET_MAKE);\n\t\t\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t\t\t}\n\n\t\t\tread_fs(file, phdr.p_offset, phdr.p_filesz, (void*)phdr.p_vaddr);\n\t\t\tfor (size_t i = phdr.p_filesz; i < phdr.p_memsz; ++i) {\n\t\t\t\t*(char*)(phdr.p_vaddr + i) = 0;\n\t\t\t}\n\n\t\t\t#ifdef __aarch64__\n\t\t\textern void arch_clear_icache(uintptr_t,uintptr_t);\n\t\t\tarch_clear_icache(phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz);\n\t\t\t#endif\n\n\t\t\tif (phdr.p_vaddr + phdr.p_memsz > heapBase) {\n\t\t\t\theapBase = phdr.p_vaddr + phdr.p_memsz;\n\t\t\t}\n\n\t\t\tif (phdr.p_vaddr < execBase) {\n\t\t\t\texecBase = phdr.p_vaddr;\n\t\t\t}\n\t\t}\n\t\t/* TODO: Should also be setting up TLS PHDRs. */\n\t}\n\n\tthis_core->current_process->image.heap  = (heapBase + 0xFFF) & (~0xFFF);\n\tthis_core->current_process->image.entry = header.e_entry;\n\n\tclose_fs(file);\n\n\t// arch_set_...?\n\n\t/* Map stack space */\n\tuintptr_t userstack = 0x800000000000;\n\tfor (uintptr_t i = userstack - 512 * 0x400; i < userstack; i += 0x1000) {\n\t\tunion PML * page = mmu_get_page(i, MMU_GET_MAKE);\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t}\n\n\tthis_core->current_process->image.userstack = userstack - 16 * 0x400;\n\n#define PUSH(type,val) do { \\\n\tuserstack -= sizeof(type); \\\n\twhile (userstack & (sizeof(type)-1)) userstack--; \\\n\t*((type*)userstack) = (val); \\\n} while (0)\n#define PUSHSTR(s) do { \\\n\tssize_t l = strlen(s); \\\n\tdo { \\\n\t\tPUSH(char,s[l]); \\\n\t\tl--; \\\n\t} while (l>=0); \\\n} while (0)\n\n\t/* XXX This should probably be done backwards so we can be\n\t *     sure that we're aligning the stack properly. It\n\t *     doesn't matter too much as our crt0+libc align it\n\t *     correctly for us and environ + auxv detection is\n\t *     based on the addresses of argv, not the actual\n\t *     stack pointer, but it's still weird. */\n\tchar * argv_ptrs[argc];\n\tfor (int i = 0; i < argc; ++i) {\n\t\tPUSHSTR(argv[i]);\n\t\targv_ptrs[i] = (char*)userstack;\n\t}\n\n\t/* Now push envp */\n\tint envc = 0;\n\tchar ** envpp = (char**)env;\n\twhile (*envpp) {\n\t\tenvc++;\n\t\tenvpp++;\n\t}\n\tchar * envp_ptrs[envc];\n\tfor (int i = 0; i < envc; ++i) {\n\t\tPUSHSTR(env[i]);\n\t\tenvp_ptrs[i] = (char*)userstack;\n\t}\n\n\tPUSH(uintptr_t, 0);\n\tPUSH(uintptr_t, this_core->current_process->user);\n\tPUSH(uintptr_t, 11); /* AT_UID */\n\tPUSH(uintptr_t, this_core->current_process->real_user);\n\tPUSH(uintptr_t, 12); /* AT_EUID */\n\tPUSH(uintptr_t, 0);\n\n\tPUSH(uintptr_t, 0); /* envp NULL */\n\tfor (int i = envc; i > 0; i--) {\n\t\tPUSH(char*,envp_ptrs[i-1]);\n\t}\n\tchar ** _envp = (char**)userstack;\n\tPUSH(uintptr_t, 0); /* argv NULL */\n\tfor (int i = argc; i > 0; i--) {\n\t\tPUSH(char*,argv_ptrs[i-1]);\n\t}\n\tchar ** _argv = (char**)userstack;\n\tPUSH(uintptr_t, argc);\n\n\tarch_set_kernel_stack(this_core->current_process->image.stack);\n\tarch_enter_user(header.e_entry, argc, _argv, _envp, userstack);\n\n\treturn -EINVAL;\n}\n"
  },
  {
    "path": "kernel/misc/fbterm.c",
    "content": "/**\n * @file  kernel/misc/fbterm.c\n * @brief Crude framebuffer terminal for 32bpp framebuffer devices.\n *\n * Provides a simple graphical text renderer for early startup, with\n * support for simple escape sequences, on top of a framebuffer set up\n * with the `lfbvideo` module.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/args.h>\n#include <kernel/mmu.h>\n#include <kernel/video.h>\n\n/* Whether to scroll or wrap when cursor reaches the bottom. */\nstatic int fbterm_scroll = 0;\nstatic void (*write_char)(int, int, int, uint32_t) = NULL;\nstatic int (*get_width)(void) = NULL;\nstatic int (*get_height)(void) = NULL;\nstatic void (*scroll_terminal)(void) = NULL;\n\nstatic int x = 0;\nstatic int y = 0;\nstatic int term_state = 0;\nstatic char term_buf[1024] = {0};\nstatic int term_buf_c = 0;\n\n/* Bitmap font details */\n#include \"../../apps/terminal-font.h\"\n#define char_height LARGE_FONT_CELL_HEIGHT\n#define char_width  LARGE_FONT_CELL_WIDTH\n\n/* Default colors */\n#define BG_COLOR 0xFF000000 /* Background */\n#define FG_COLOR 0xFFCCCCCC /* Main text color */\n\nstatic uint32_t fg_color = FG_COLOR;\nstatic uint32_t bg_color = BG_COLOR;\n\nstatic inline void set_point(int x, int y, uint32_t value) {\n\tif (lfb_resolution_b == 32) {\n\t\t((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x] = value;\n\t\t#ifdef __aarch64__\n\t\t/* TODO just map it uncached in the first place... */\n\t\tasm volatile (\"dc cvac, %0\\n\" :: \"r\"((uintptr_t)&((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x]) : \"memory\");\n\t\t#endif\n\t} else if (lfb_resolution_b == 24) {\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 0] = (value >> 0) & 0xFF;\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 1] = (value >> 8) & 0xFF;\n\t\tlfb_vid_memory[y * lfb_resolution_s + x * 3 + 2] = (value >> 16) & 0xFF;\n\t}\n}\n\nstatic void fb_write_char(int _x, int _y, int val, uint32_t color) {\n\tif (val > 128) {\n\t\tval = 4;\n\t}\n\n\tint x = 1 + _x * char_width;\n\tint y = _y * char_height;\n\n\tuint8_t * c = large_font[val];\n\tfor (uint8_t i = 0; i < char_height; ++i) {\n\t\tfor (uint8_t j = 0; j < char_width; ++j) {\n\t\t\tif (c[i] & (1 << (LARGE_FONT_MASK-j))) {\n\t\t\t\tset_point(x+j,y+i,color);\n\t\t\t} else {\n\t\t\t\tset_point(x+j,y+i,bg_color);\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * @brief Basic 16-color ANSI palette with Tango colors.\n */\nstatic uint32_t term_colors[] = {\n\t0xFF000000,\n\t0xFFCC0000,\n\t0xFF4E9A06,\n\t0xFFC4A000,\n\t0xFF3465A4,\n\t0xFF75507B,\n\t0xFF06989A,\n\t0xFFD3D7CF,\n\n\t0xFF555753,\n\t0xFFEF2929,\n\t0xFF8AE234,\n\t0xFFFCE94F,\n\t0xFF729FCF,\n\t0xFFAD7FA8,\n\t0xFF34E2E2,\n\t0xFFEEEEEC,\n};\n\nstatic int fb_get_width(void) {\n\treturn (lfb_resolution_x - 1) / char_width;\n}\n\nstatic int fb_get_height(void) {\n\treturn lfb_resolution_y / char_height;\n}\n\nstatic void fb_scroll_terminal(void) {\n\tmemmove(lfb_vid_memory, lfb_vid_memory + sizeof(uint32_t) * lfb_resolution_x * char_height, (lfb_resolution_y - char_height) * lfb_resolution_x * 4);\n\tmemset(lfb_vid_memory + sizeof(uint32_t) * (lfb_resolution_y - char_height) * lfb_resolution_x, 0x00, char_height * lfb_resolution_x * 4);\n}\n\nstatic void draw_square(int x, int y) {\n\tint center_x = lfb_resolution_x / 2;\n\tint center_y = lfb_resolution_y / 2;\n\tfor (size_t _y = 0; _y < 7; ++_y) {\n\t\tuint32_t color = 0xFF00B2FF - (y * 8 + _y) * 0x200;\n\t\tfor (size_t _x = 0; _x < 7; ++_x) {\n\t\t\tset_point(center_x - 32 + x * 8 + _x, center_y - 32 + y * 8 + _y, color);\n\t\t}\n\t}\n}\n\nvoid fbterm_draw_logo(void) {\n\tuint64_t logo_squares = 0x981818181818FFFFUL;\n\tfor (size_t y = 0; y < 8; ++y) {\n\t\tfor (size_t x = 0; x < 8; ++x) {\n\t\t\tif (logo_squares & (1 << x)) {\n\t\t\t\tdraw_square(x,y);\n\t\t\t}\n\t\t}\n\t\tlogo_squares >>= 8;\n\t}\n}\n\nvoid fbterm_reset(void) {\n\tx = 0;\n\ty = 0;\n\tterm_state = 0;\n\tfg_color = FG_COLOR;\n\tbg_color = BG_COLOR;\n}\n\nstatic void fbterm_init_framebuffer(void) {\n\twrite_char = fb_write_char;\n\tget_width = fb_get_width;\n\tget_height = fb_get_height;\n\tscroll_terminal = fb_scroll_terminal;\n\tfbterm_draw_logo();\n}\n\nstatic void ega_write_char(int x, int y, int ch, uint32_t color) {\n\tunsigned short att = 7 << 8;\n\tunsigned short *where = (unsigned short*)(mmu_map_from_physical(0xB8000)) + (y * 80 + x);\n\t*where = (ch & 0xFF) | att;\n}\n\nstatic int ega_get_width(void) { return 80; }\nstatic int ega_get_height(void) { return 25; }\n\nstatic void ega_scroll_terminal(void) {\n\tmemmove(mmu_map_from_physical(0xB8000), (char*)mmu_map_from_physical(0xB8000) + sizeof(unsigned short) * 80, sizeof(unsigned short) * (80 * 24));\n\tmemset((char*)mmu_map_from_physical(0xB8000) + sizeof(unsigned short) * (80 * 24), 0x00, 80 * sizeof(unsigned short));\n}\n\nstatic void fbterm_init_ega(void) {\n\twrite_char = ega_write_char;\n\tget_width = ega_get_width;\n\tget_height = ega_get_height;\n\tscroll_terminal = ega_scroll_terminal;\n}\n\nstatic void cursor_update(void) {\n\tif (x >= get_width()) {\n\t\tx = 0;\n\t\ty++;\n\t}\n\tif (y >= get_height()) {\n\t\tif (fbterm_scroll) {\n\t\t\ty--;\n\t\t\tscroll_terminal();\n\t\t} else {\n\t\t\ty = 0;\n\t\t}\n\t}\n}\n\nstatic void process_char(char ch) {\n\tif (term_state == 1) {\n\t\tif (ch == '[') {\n\t\t\tterm_buf_c = 0;\n\t\t\tterm_buf[term_buf_c] = '\\0';\n\t\t\tterm_state = 2;\n\t\t} else {\n\t\t\tterm_state = 0;\n\t\t\tprocess_char(ch);\n\t\t}\n\t\treturn;\n\t} else if (term_state == 2) {\n\t\tif ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {\n\t\t\t/* do the thing */\n\t\t\tswitch (ch) {\n\t\t\t\tcase 'm': {\n\t\t\t\t\tchar * arg = &term_buf[0];\n\t\t\t\t\tchar * next;\n\t\t\t\t\tint argC = 0;\n\t\t\t\t\tint isBold = 0;\n\t\t\t\t\tdo {\n\t\t\t\t\t\tnext = strchr(arg, ';');\n\t\t\t\t\t\tif (next) { *next = '\\0'; next++; }\n\t\t\t\t\t\tint asInt = atoi(arg);\n\t\t\t\t\t\tif (asInt == 0) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t\tbg_color = BG_COLOR;\n\t\t\t\t\t\t\tisBold = 0;\n\t\t\t\t\t\t} else if (asInt == 1) {\n\t\t\t\t\t\t\tisBold = 1;\n\t\t\t\t\t\t} else if (asInt == 22) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t\tisBold = 0;\n\t\t\t\t\t\t} else if (asInt >= 30 && asInt <= 37) {\n\t\t\t\t\t\t\tfg_color = term_colors[asInt-30 + (isBold ? 8 : 0)];\n\t\t\t\t\t\t} else if (asInt >= 90 && asInt <= 97) {\n\t\t\t\t\t\t\tfg_color = term_colors[asInt-90 + 8];\n\t\t\t\t\t\t} else if (asInt >= 40 && asInt <= 47) {\n\t\t\t\t\t\t\tbg_color = term_colors[asInt-40 + (isBold ? 8 : 0)];\n\t\t\t\t\t\t} else if (asInt >= 100 && asInt <= 107) {\n\t\t\t\t\t\t\tbg_color = term_colors[asInt-100 + 8];\n\t\t\t\t\t\t} else if (asInt == 38) {\n\t\t\t\t\t\t\tfg_color = FG_COLOR;\n\t\t\t\t\t\t} else if (asInt == 48) {\n\t\t\t\t\t\t\tbg_color = BG_COLOR;\n\t\t\t\t\t\t} else if (asInt == 7) {\n\t\t\t\t\t\t\tuint32_t tmp = fg_color;\n\t\t\t\t\t\t\tfg_color = bg_color;\n\t\t\t\t\t\t\tbg_color = tmp;\n\t\t\t\t\t\t}\n\t\t\t\t\t\targ = next;\n\t\t\t\t\t\targC++;\n\t\t\t\t\t} while (arg);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'G': {\n\t\t\t\t\t/* Set cursor column */\n\t\t\t\t\tx = atoi(term_buf) - 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'K': {\n\t\t\t\t\tif (atoi(term_buf) == 0) {\n\t\t\t\t\t\tfor (int i = x; i < get_width(); ++i) {\n\t\t\t\t\t\t\twrite_char(i,y,' ',bg_color);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tterm_state = 0;\n\t\t} else {\n\t\t\tterm_buf[term_buf_c++] = ch;\n\t\t\tterm_buf[term_buf_c] = '\\0';\n\t\t}\n\t\treturn;\n\t} else if (ch == '\\033') {\n\t\tterm_state = 1;\n\t\treturn;\n\t}\n\n\twrite_char(x,y,' ',bg_color);\n\tswitch (ch) {\n\t\tcase '\\n':\n\t\t\tx = 0;\n\t\t\ty++;\n\t\t\tbreak;\n\t\tcase '\\r':\n\t\t\tx = 0;\n\t\t\tbreak;\n\t\tcase '\\b':\n\t\t\tif (x) {\n\t\t\t\tx--;\n\t\t\t\twrite_char(x,y,' ',fg_color);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tif ((unsigned int)ch > 127) return;\n\t\t\twrite_char(x,y,ch,fg_color);\n\t\t\tx++;\n\t\t\tbreak;\n\t}\n\tcursor_update();\n}\n\nstatic size_t (*previous_writer)(size_t,uint8_t*) = NULL;\n\nsize_t fbterm_write(size_t size, uint8_t *buffer) {\n\tif (!buffer) return 0;\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tprocess_char(buffer[i]);\n\t}\n\n\tif (previous_writer) previous_writer(size,buffer);\n\treturn size;\n}\n\nvoid fbterm_initialize(void) {\n\tif (lfb_resolution_x) {\n\t\tif (args_present(\"fbterm-scroll\")) {\n\t\t\tfbterm_scroll = 1;\n\t\t}\n\t\tfbterm_init_framebuffer();\n\t} else {\n#ifdef __x86_64__\n\t\tfbterm_scroll = 1;\n\t\tfbterm_init_ega();\n#else\n\t\treturn;\n#endif\n\t}\n\n\tprevious_writer = printf_output;\n\tprintf_output = fbterm_write;\n\tconsole_set_output(fbterm_write);\n\n\tdprintf(\"fbterm: Generic framebuffer text output enabled.\\n\");\n}\n"
  },
  {
    "path": "kernel/misc/gzip.c",
    "content": "/**\n * @file  kernel/misc/gzip.c\n * @brief Gzip/DEFLATE decompression.\n *\n * Provides decompression for ramdisks.\n * Based on the same approach to DEFLATE decompression as libraries\n * like \"tinf\", this is a slow-but-simple implementation of Huffman\n * decoding. The kernel version operates directly on two pointers,\n * @c gzip_inputPtr and @c gzip_outputPtr. For a more robust API,\n * see the userspace version.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n\nstatic uint8_t bit_buffer = 0;\nstatic char buffer_size = 0;\n\nuint8_t * gzip_inputPtr = NULL;\nuint8_t * gzip_outputPtr = NULL;\n\n__attribute__((always_inline))\nstatic inline uint8_t read_byte(void) {\n\treturn *gzip_inputPtr++;\n}\n\n__attribute__((always_inline))\nstatic inline void _write(unsigned int sym) {\n\t*gzip_outputPtr++ = sym;\n}\n\n/**\n * Decoded Huffman table\n */\nstruct huff {\n\tuint16_t counts[16];   /* Number of symbols of each length */\n\tuint16_t symbols[288]; /* Ordered symbols */\n};\n\n/**\n * Fixed Huffman code tables, generated later.\n */\nstruct huff fixed_lengths;\nstruct huff fixed_dists;\n\n/**\n * Read a little-endian short from the input.\n */\nstatic uint16_t read_16le(void) {\n\tuint16_t a, b;\n\ta = read_byte();\n\tb = read_byte();\n\treturn (a << 0) | (b << 8);\n}\n\n/**\n * Read a single bit from the source.\n * Fills the byte buffer with one byte when it runs out.\n */\nstatic _Bool read_bit(void) {\n\n\t/* When we run out of bits... */\n\tif (buffer_size == 0) {\n\t\t/* Refill from the next input byte */\n\t\tbit_buffer = read_byte();\n\t\t/* And restore bit buffer size to 8 bits */\n\t\tbuffer_size = 8;\n\t}\n\n\t/* Get the next available bit */\n\tint out = bit_buffer & 1;\n\n\t/* Shift the bit buffer forward */\n\tbit_buffer >>= 1;\n\n\t/* There is now one less bit available */\n\tbuffer_size--;\n\n\treturn out;\n}\n\n/**\n * Read multible bits, in bit order, from the source.\n */\nstatic uint32_t read_bits(unsigned int count) {\n\tuint32_t out = 0;\n\tfor (unsigned int bit = 0; bit < count; bit++) {\n\t\t/* Read one bit at a time, from least to most significant */\n\t\tout |= (read_bit() << bit);\n\t}\n\treturn out;\n}\n\n/**\n * Build a Huffman table from an array of lengths.\n */\nstatic void build_huffman(const uint8_t * lengths, size_t size, struct huff * out) {\n\n\tuint16_t offsets[16];\n\tunsigned int count = 0;\n\n\t/* Zero symbol counts */\n\tfor (unsigned int i = 0; i < 16; ++i) out->counts[i] = 0;\n\n\t/* Count symbols */\n\tfor (unsigned int i = 0; i < size; ++i) out->counts[lengths[i]]++;\n\n\t/* Special case... */\n\tout->counts[0] = 0;\n\n\t/* Figure out offsets */\n\tfor (unsigned int i = 0; i < 16; ++i) {\n\t\toffsets[i] = count;\n\t\tcount += out->counts[i];\n\t}\n\n\t/* Build symbol ordering */\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tif (lengths[i]) out->symbols[offsets[lengths[i]]++] = i;\n\t}\n}\n\n/**\n * Build the fixed Huffman tables\n */\nstatic void build_fixed(void) {\n\t/* From 3.2.6:\n\t * Lit Value    Bits        Codes\n\t * ---------    ----        -----\n\t *   0 - 143     8          00110000 through\n\t *                          10111111\n\t * 144 - 255     9          110010000 through\n\t *                          111111111\n\t * 256 - 279     7          0000000 through\n\t *                          0010111\n\t * 280 - 287     8          11000000 through\n\t *                          11000111\n\t */\n\tuint8_t lengths[288];\n\tfor (int i = 0; i < 144; ++i)   lengths[i] = 8;\n\tfor (int i = 144; i < 256; ++i) lengths[i] = 9;\n\tfor (int i = 256; i < 280; ++i) lengths[i] = 7;\n\tfor (int i = 280; i < 288; ++i) lengths[i] = 8;\n\tbuild_huffman(lengths, 288, &fixed_lengths);\n\n\t/* Continued from 3.2.6:\n\t * Distance codes 0-31 are represented by (fixed-length) 5-bit\n\t * codes, with possible additional bits as shown in the table\n\t * shown in Paragraph 3.2.5, above.  Note that distance codes 30-\n\t * 31 will never actually occur in the compressed data.\n\t */\n\tfor (int i = 0; i < 30; ++i) lengths[i] = 5;\n\tbuild_huffman(lengths, 30, &fixed_dists);\n}\n\n\n/**\n * Decode a symbol from the source using a Huffman table.\n */\nstatic int decode(const struct huff * huff) {\n\tint count = 0, cur = 0;\n\tfor (int i = 1; cur >= 0; i++) {\n\t\tcur = (cur << 1) | read_bit(); /* Shift */\n\t\tcount += huff->counts[i];\n\t\tcur -= huff->counts[i];\n\t}\n\treturn huff->symbols[count + cur];\n}\n\nstruct huff_ring {\n\tsize_t pointer;\n\tuint8_t data[32768];\n};\n\nstatic struct huff_ring data = {0, {0}};\n\n/**\n * Emit one byte to the output, maintaining the ringbuffer.\n * The ringbuffer ensures we can always look back 32K bytes\n * while keeping output streaming.\n */\nstatic void emit(unsigned char byte) {\n\tdata.data[data.pointer++] = byte;\n\tdata.pointer &= 0x7FFF;\n\t_write(byte);\n}\n\n/**\n * Look backwards in the output ring buffer.\n */\nstatic void peek(unsigned int offset) {\n\tdata.data[data.pointer] = data.data[(data.pointer + 0x8000 - offset) & 0x7FFF];\n\t_write(data.data[data.pointer++]);\n\tdata.pointer &= 0x7FFF;\n}\n\n/**\n * Decompress a block of Huffman-encoded data.\n */\nstatic int inflate(const struct huff * huff_len, const struct huff * huff_dist) {\n\n\t/* These are the extra bits for lengths from the tables in section 3.2.5\n\t *           Extra               Extra               Extra\n\t *      Code Bits Length(s) Code Bits Lengths   Code Bits Length(s)\n\t *      ---- ---- ------     ---- ---- -------   ---- ---- -------\n\t *       257   0     3       267   1   15,16     277   4   67-82\n\t *       258   0     4       268   1   17,18     278   4   83-98\n\t *       259   0     5       269   2   19-22     279   4   99-114\n\t *       260   0     6       270   2   23-26     280   4  115-130\n\t *       261   0     7       271   2   27-30     281   5  131-162\n\t *       262   0     8       272   2   31-34     282   5  163-194\n\t *       263   0     9       273   3   35-42     283   5  195-226\n\t *       264   0    10       274   3   43-50     284   5  227-257\n\t *       265   1  11,12      275   3   51-58     285   0    258\n\t *       266   1  13,14      276   3   59-66\n\t */\n\tstatic const uint16_t lens[] = {\n\t\t3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51,\n\t\t59, 67, 83, 99, 115, 131, 163, 195, 227, 258\n\t};\n\tstatic const uint16_t lext[] = {\n\t\t0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,\n\t\t4, 5, 5, 5, 5, 0\n\t};\n\n\t/* Extra bits for distances....\n\t *            Extra           Extra               Extra\n\t *       Code Bits Dist  Code Bits   Dist     Code Bits Distance\n\t *       ---- ---- ----  ---- ----  ------    ---- ---- --------\n\t *         0   0    1     10   4     33-48    20    9   1025-1536\n\t *         1   0    2     11   4     49-64    21    9   1537-2048\n\t *         2   0    3     12   5     65-96    22   10   2049-3072\n\t *         3   0    4     13   5     97-128   23   10   3073-4096\n\t *         4   1   5,6    14   6    129-192   24   11   4097-6144\n\t *         5   1   7,8    15   6    193-256   25   11   6145-8192\n\t *         6   2   9-12   16   7    257-384   26   12  8193-12288\n\t *         7   2  13-16   17   7    385-512   27   12 12289-16384\n\t *         8   3  17-24   18   8    513-768   28   13 16385-24576\n\t *         9   3  25-32   19   8   769-1024   29   13 24577-32768\n\t */\n\tstatic const uint16_t dists[] = {\n\t\t1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,\n\t\t513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577\n\t};\n\tstatic const uint16_t dext[] = {\n\t\t0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,\n\t\t10, 11, 11, 12, 12, 13, 13\n\t};\n\n\twhile (1) {\n\t\tunsigned int symbol = decode(huff_len);\n\t\tif (symbol < 256) {\n\t\t\temit(symbol);\n\t\t} else if (symbol == 256) {\n\t\t\t/* \"The literal/length symbol 256 (end of data), ...\" */\n\t\t\tbreak;\n\t\t} else {\n\t\t\tunsigned int length, distance, offset;\n\t\t\tsymbol -= 257;\n\t\t\tlength = read_bits(lext[symbol]) + lens[symbol];\n\t\t\tdistance = decode(huff_dist);\n\t\t\toffset = read_bits(dext[distance]) + dists[distance];\n\t\t\tfor (unsigned int i = 0; i < length; ++i) {\n\t\t\t\tpeek(offset);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Decode a dynamic Huffman block.\n */\nstatic void decode_huffman(void) {\n\n\t/* Ordering of code length codes:\n\t * (HCLEN + 4) x 3 bits: code lengths for the code length\n\t * alphabet given just above, in the order: ...\n\t */\n\tstatic const uint8_t clens[] = {\n\t\t16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15\n\t};\n\n\tunsigned int literals, distances, clengths;\n\tuint8_t lengths[320] = {0};\n\n\tliterals  = 257 + read_bits(5); /* 5 Bits: HLIT ... 257 */\n\tdistances = 1 + read_bits(5);   /* 5 Bits: HDIST ... 1 */\n\tclengths  = 4 + read_bits(4);   /* 4 Bits: HCLEN ... 4 */\n\n\t/* (HCLEN + 4) x 3 bits... */\n\tfor (unsigned int i = 0; i < clengths; ++i) {\n\t\tlengths[clens[i]] = read_bits(3);\n\t}\n\n\tstruct huff codes;\n\tbuild_huffman(lengths, 19, &codes);\n\n\t/* Decode symbols:\n\t * HLIT + 257 code lengths for the literal/length alphabet...\n\t * HDIST + 1 code lengths for the distance alphabet...\n\t */\n\tunsigned int count = 0;\n\twhile (count < literals + distances) {\n\t\tint symbol = decode(&codes);\n\t\tint rep = 0, length;\n\t\tswitch (symbol) {\n\t\t\tcase 16:\n\t\t\t\t/* 16: Copy the previous code length 3-6 times */\n\t\t\t\trep = lengths[count-1];\n\t\t\t\tlength = read_bits(2) + 3; /* The next 2 bits indicate repeat length */\n\t\t\t\tbreak;\n\t\t\tcase 17:\n\t\t\t\t/* Repeat a code length of 0 for 3 - 10 times */\n\t\t\t\tlength = read_bits(3) + 3; /* 3 bits of length */\n\t\t\t\tbreak;\n\t\t\tcase 18:\n\t\t\t\t/* Repeat a code length of 0 for 11 - 138 times */\n\t\t\t\tlength = read_bits(7) + 11; /* 7 bits of length */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tlength = 1;\n\t\t\t\trep = symbol;\n\t\t\t\tbreak;\n\t\t}\n\t\twhile (length--) {\n\t\t\tlengths[count++] = rep;\n\t\t}\n\t}\n\n\t/* Build tables from lenghts decoded above */\n\tstruct huff huff_len;\n\tbuild_huffman(lengths, literals, &huff_len);\n\tstruct huff huff_dist;\n\tbuild_huffman(lengths + literals, distances, &huff_dist);\n\n\tinflate(&huff_len, &huff_dist);\n}\n\n/**\n * Decode an uncompressed block.\n */\nstatic int uncompressed(void) {\n\t/* Reset byte alignment */\n\tbit_buffer = 0;\n\tbuffer_size = 0;\n\n\t/* \"The rest of the block consists of the following information:\"\n\t *    0   1   2   3   4...\n\t *  +---+---+---+---+================================+\n\t *  |  LEN  | NLEN  |... LEN bytes of literal data...|\n\t *  +---+---+---+---+================================+\n\t */\n\tuint16_t len = read_16le(); /* \"the number of data bytes in the block\" */\n\tuint16_t nlen = read_16le(); /* \"the one's complement of LEN */\n\n\t/* Sanity check - does the ones-complement length actually match? */\n\tif ((nlen & 0xFFFF) != (~len & 0xFFFF)) {\n\t\treturn 1;\n\t}\n\n\t/* Emit LEN bytes from the source to the output */\n\tfor (int i = 0; i < len; ++i) {\n\t\temit(read_byte());\n\t}\n\n\treturn 0;\n}\n\n/**\n * Decompress DEFLATE-compressed data.\n */\n__attribute__((optimize(\"O2\")))\n__attribute__((hot))\nint deflate_decompress(void) {\n\tbit_buffer = 0;\n\tbuffer_size = 0;\n\n\tbuild_fixed();\n\n\t/* read compressed data */\n\twhile (1) {\n\t\t/* Read bit */\n\n\t\tint is_final = read_bit();\n\t\tint type = read_bits(2);\n\n\t\tswitch (type) {\n\t\t\tcase 0x00: /* BTYPE=00 Non-compressed blocks */\n\t\t\t\tuncompressed();\n\t\t\t\tbreak;\n\t\t\tcase 0x01: /* BYTPE=01 Compressed with fixed Huffman codes */\n\t\t\t\tinflate(&fixed_lengths, &fixed_dists);\n\t\t\t\tbreak;\n\t\t\tcase 0x02: /* BTYPE=02 Compression with dynamic Huffman codes */\n\t\t\t\tdecode_huffman();\n\t\t\t\tbreak;\n\t\t\tcase 0x03:\n\t\t\t\treturn 1;\n\t\t\tdefault:\n\t\t\t\t__builtin_unreachable();\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (is_final) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n#define GZIP_FLAG_TEXT (1 << 0)\n#define GZIP_FLAG_HCRC (1 << 1)\n#define GZIP_FLAG_EXTR (1 << 2)\n#define GZIP_FLAG_NAME (1 << 3)\n#define GZIP_FLAG_COMM (1 << 4)\n\nstatic unsigned int read_32le(void) {\n\tunsigned int a, b, c, d;\n\ta = read_byte();\n\tb = read_byte();\n\tc = read_byte();\n\td = read_byte();\n\n\treturn (d << 24) | (c << 16) | (b << 8) | (a << 0);\n}\n\nint gzip_decompress(void) {\n\n\t/* Read gzip headers */\n\tif (read_byte() != 0x1F) return 1;\n\tif (read_byte() != 0x8B) return 1;\n\n\tunsigned int cm = read_byte();\n\tif (cm != 8) return 1;\n\n\tunsigned int flags = read_byte();\n\n\t/* Read mtime */\n\tunsigned int mtime = read_32le();\n\t(void)mtime;\n\n\t/* Read extra flags */\n\tunsigned int xflags = read_byte();\n\t(void)xflags;\n\n\t/* Read and discord OS flag */\n\tunsigned int os = read_byte();\n\t(void)os;\n\n\t/* Extra bytes */\n\tif (flags & GZIP_FLAG_EXTR) {\n\t\tunsigned short size = read_16le();\n\t\tfor (unsigned int i = 0; i < size; ++i) read_byte();\n\t}\n\n\tif (flags & GZIP_FLAG_NAME) {\n\t\tunsigned int c;\n\t\twhile ((c = read_byte()) != 0);\n\t}\n\n\tif (flags & GZIP_FLAG_COMM) {\n\t\tunsigned int c;\n\t\twhile ((c = read_byte()) != 0);\n\t}\n\n\tunsigned int crc16 = 0;\n\tif (flags & GZIP_FLAG_HCRC) {\n\t\tcrc16 = read_16le();\n\t}\n\t(void)crc16;\n\n\tint status = deflate_decompress();\n\n\t/* Read CRC and decompressed size from end of input */\n\tunsigned int crc32 = read_32le();\n\tunsigned int dsize = read_32le();\n\n\t(void)crc32;\n\t(void)dsize;\n\n\treturn status;\n}\n\n"
  },
  {
    "path": "kernel/misc/hashmap.c",
    "content": "/**\n * @brief Flexible mapping container.\n * @author K. Lange\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <kernel/string.h>\n#include <kernel/list.h>\n#include <kernel/hashmap.h>\n\nunsigned int hashmap_string_hash(const void * _key) {\n\tunsigned int hash = 0;\n\tchar * key = (char *)_key;\n\tint c;\n\t/* This is the so-called \"sdbm\" hash. It comes from a piece of\n\t * public domain code from a clone of ndbm. */\n\twhile ((c = *key++)) {\n\t\thash = c + (hash << 6) + (hash << 16) - hash;\n\t}\n\treturn hash;\n}\n\nint hashmap_string_comp(const void * a, const void * b) {\n\treturn !strcmp(a,b);\n}\n\nvoid * hashmap_string_dupe(const void * key) {\n\treturn strdup(key);\n}\n\nunsigned int hashmap_int_hash(const void * key) {\n\treturn (intptr_t)key;\n}\n\nint hashmap_int_comp(const void * a, const void * b) {\n\treturn (intptr_t)a == (intptr_t)b;\n}\n\nvoid * hashmap_int_dupe(const void * key) {\n\treturn (void*)key;\n}\n\nstatic void hashmap_int_free(void * ptr) {\n\t(void)ptr;\n\treturn;\n}\n\n\nhashmap_t * hashmap_create(int size) {\n\thashmap_t * map = malloc(sizeof(hashmap_t));\n\n\tmap->hash_func     = &hashmap_string_hash;\n\tmap->hash_comp     = &hashmap_string_comp;\n\tmap->hash_key_dup  = &hashmap_string_dupe;\n\tmap->hash_key_free = &free;\n\tmap->hash_val_free = &free;\n\n\tmap->size = size;\n\tmap->entries = malloc(sizeof(hashmap_entry_t *) * size);\n\tmemset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);\n\n\treturn map;\n}\n\nhashmap_t * hashmap_create_int(int size) {\n\thashmap_t * map = malloc(sizeof(hashmap_t));\n\n\tmap->hash_func     = &hashmap_int_hash;\n\tmap->hash_comp     = &hashmap_int_comp;\n\tmap->hash_key_dup  = &hashmap_int_dupe;\n\tmap->hash_key_free = &hashmap_int_free;\n\tmap->hash_val_free = &free;\n\n\tmap->size = size;\n\tmap->entries = malloc(sizeof(hashmap_entry_t *) * size);\n\tmemset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);\n\n\treturn map;\n}\n\nvoid * hashmap_set(hashmap_t * map, const void * key, void * value) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\thashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));\n\t\te->key   = map->hash_key_dup(key);\n\t\te->value = value;\n\t\te->next = NULL;\n\t\tmap->entries[hash] = e;\n\t\treturn NULL;\n\t} else {\n\t\thashmap_entry_t * p = NULL;\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\tvoid * out = x->value;\n\t\t\t\tx->value = value;\n\t\t\t\treturn out;\n\t\t\t} else {\n\t\t\t\tp = x;\n\t\t\t\tx = x->next;\n\t\t\t}\n\t\t} while (x);\n\t\thashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));\n\t\te->key   = map->hash_key_dup(key);\n\t\te->value = value;\n\t\te->next = NULL;\n\n\t\tp->next = e;\n\t\treturn NULL;\n\t}\n}\n\nvoid * hashmap_get(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn NULL;\n\t} else {\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\treturn x->value;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t} while (x);\n\t\treturn NULL;\n\t}\n}\n\nvoid * hashmap_remove(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn NULL;\n\t} else {\n\t\tif (map->hash_comp(x->key, key)) {\n\t\t\tvoid * out = x->value;\n\t\t\tmap->entries[hash] = x->next;\n\t\t\tmap->hash_key_free(x->key);\n\t\t\tmap->hash_val_free(x);\n\t\t\treturn out;\n\t\t} else {\n\t\t\thashmap_entry_t * p = x;\n\t\t\tx = x->next;\n\t\t\tdo {\n\t\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\t\tvoid * out = x->value;\n\t\t\t\t\tp->next = x->next;\n\t\t\t\t\tmap->hash_key_free(x->key);\n\t\t\t\t\tmap->hash_val_free(x);\n\t\t\t\t\treturn out;\n\t\t\t\t}\n\t\t\t\tp = x;\n\t\t\t\tx = x->next;\n\t\t\t} while (x);\n\t\t}\n\t\treturn NULL;\n\t}\n}\n\nint hashmap_has(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn 0;\n\t} else {\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t} while (x);\n\t\treturn 0;\n\t}\n\n}\n\nlist_t * hashmap_keys(hashmap_t * map) {\n\tlist_t * l = list_create(\"hashmap keys\",map);\n\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i];\n\t\twhile (x) {\n\t\t\tlist_insert(l, x->key);\n\t\t\tx = x->next;\n\t\t}\n\t}\n\n\treturn l;\n}\n\nlist_t * hashmap_values(hashmap_t * map) {\n\tlist_t * l = list_create(\"hashmap values\",map);\n\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i];\n\t\twhile (x) {\n\t\t\tlist_insert(l, x->value);\n\t\t\tx = x->next;\n\t\t}\n\t}\n\n\treturn l;\n}\n\nvoid hashmap_free(hashmap_t * map) {\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i], * p;\n\t\twhile (x) {\n\t\t\tp = x;\n\t\t\tx = x->next;\n\t\t\tmap->hash_key_free(p->key);\n\t\t\tmap->hash_val_free(p);\n\t\t}\n\t}\n\tfree(map->entries);\n}\n\nint hashmap_is_empty(hashmap_t * map) {\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\tif (map->entries[i]) return 0;\n\t}\n\treturn 1;\n}\n"
  },
  {
    "path": "kernel/misc/kprintf.c",
    "content": "/**\n * @file  kprintf.c\n * @brief Kernel printf implementation.\n *\n * This is the newer, 64-bit-friendly printf originally implemented\n * for EFI builds of Kuroko. It was merged into the ToaruOS libc\n * and later became the kernel printf in Misaka. It supports the\n * standard set of width specifiers, '0' or ' ' padding, left or\n * right alignment, and the usermode version has a (rudimentary,\n * inaccurate) floating point printer. This kernel version excludes\n * float support. printf output is passed to callback functions\n * which can write the output to a string buffer or spit them\n * directly at an MMIO port. Support is also present for bounded\n * writes, and we've left @c sprintf out of the kernel entirely\n * in favor of @c snprintf.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n#include <stdarg.h>\n\nsize_t (*printf_output)(size_t, uint8_t *) = NULL;\n\n#define OUT(c) do { callback(userData, (c)); written++; } while (0)\nstatic size_t print_dec(unsigned long long value, unsigned int width, int (*callback)(void*,char), void * userData, int fill_zero, int align_right, int precision) {\n\tsize_t written = 0;\n\tunsigned long long n_width = 1;\n\tunsigned long long i = 9;\n\tif (precision == -1) precision = 1;\n\n\tif (value == 0) {\n\t\tn_width = 0;\n\t} else {\n\t\tunsigned long long val = value;\n\t\twhile (val >= 10UL) {\n\t\t\tval /= 10UL;\n\t\t\tn_width++;\n\t\t}\n\t}\n\n\tif (n_width < (unsigned long long)precision) n_width = precision;\n\n\tint printed = 0;\n\tif (align_right) {\n\t\twhile (n_width + printed < width) {\n\t\t\tOUT(fill_zero ? '0' : ' ');\n\t\t\tprinted += 1;\n\t\t}\n\n\t\ti = n_width;\n\t\tchar tmp[100];\n\t\twhile (i > 0) {\n\t\t\tunsigned long long n = value / 10;\n\t\t\tlong long r = value % 10;\n\t\t\ttmp[i - 1] = r + '0';\n\t\t\ti--;\n\t\t\tvalue = n;\n\t\t}\n\t\twhile (i < n_width) {\n\t\t\tOUT(tmp[i]);\n\t\t\ti++;\n\t\t}\n\t} else {\n\t\ti = n_width;\n\t\tchar tmp[100];\n\t\twhile (i > 0) {\n\t\t\tunsigned long long n = value / 10;\n\t\t\tlong long r = value % 10;\n\t\t\ttmp[i - 1] = r + '0';\n\t\t\ti--;\n\t\t\tvalue = n;\n\t\t\tprinted++;\n\t\t}\n\t\twhile (i < n_width) {\n\t\t\tOUT(tmp[i]);\n\t\t\ti++;\n\t\t}\n\t\twhile (printed < (long long)width) {\n\t\t\tOUT(fill_zero ? '0' : ' ');\n\t\t\tprinted += 1;\n\t\t}\n\t}\n\n\treturn written;\n}\n\n/*\n * Hexadecimal to string\n */\nstatic size_t print_hex(unsigned long long value, unsigned int width, int (*callback)(void*,char), void* userData, int fill_zero, int alt, int caps, int align) {\n\tsize_t written = 0;\n\tint i = width;\n\n\tunsigned long long n_width = 1;\n\tunsigned long long j = 0x0F;\n\twhile (value > j && j < UINT64_MAX) {\n\t\tn_width += 1;\n\t\tj *= 0x10;\n\t\tj += 0x0F;\n\t}\n\n\tif (!fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\tif (alt) {\n\t\tOUT('0');\n\t\tOUT(caps ? 'X' : 'x');\n\t}\n\n\tif (fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT('0');\n\t\t\ti--;\n\t\t}\n\t}\n\n\ti = (long long)n_width;\n\twhile (i-- > 0) {\n\t\tchar c = (caps ? \"0123456789ABCDEF\" : \"0123456789abcdef\")[(value>>(i*4))&0xF];\n\t\tOUT(c);\n\t}\n\n\tif (align == 0) {\n\t\ti = width;\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\treturn written;\n}\n\n/*\n * vasprintf()\n */\nsize_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args) {\n\tconst char * s;\n\tsize_t written = 0;\n\tfor (const char *f = fmt; *f; f++) {\n\t\tif (*f != '%') {\n\t\t\tOUT(*f);\n\t\t\tcontinue;\n\t\t}\n\t\t++f;\n\t\tunsigned int arg_width = 0;\n\t\tint align = 1; /* right */\n\t\tint fill_zero = 0;\n\t\tint big = 0;\n\t\tint alt = 0;\n\t\tint always_sign = 0;\n\t\tint precision = -1;\n\t\twhile (1) {\n\t\t\tif (*f == '-') {\n\t\t\t\talign = 0;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '#') {\n\t\t\t\talt = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '*') {\n\t\t\t\targ_width = (int)va_arg(args, int);\n\t\t\t\t++f;\n\t\t\t} else if (*f == '0') {\n\t\t\t\tfill_zero = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '+') {\n\t\t\t\talways_sign = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == ' ') {\n\t\t\t\talways_sign = 2;\n\t\t\t\t++f;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\twhile (*f >= '0' && *f <= '9') {\n\t\t\targ_width *= 10;\n\t\t\targ_width += *f - '0';\n\t\t\t++f;\n\t\t}\n\t\tif (*f == '.') {\n\t\t\t++f;\n\t\t\tprecision = 0;\n\t\t\tif (*f == '*') {\n\t\t\t\tprecision = (int)va_arg(args, int);\n\t\t\t\t++f;\n\t\t\t} else  {\n\t\t\t\twhile (*f >= '0' && *f <= '9') {\n\t\t\t\t\tprecision *= 10;\n\t\t\t\t\tprecision += *f - '0';\n\t\t\t\t\t++f;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (*f == 'l') {\n\t\t\tbig = 1;\n\t\t\t++f;\n\t\t\tif (*f == 'l') {\n\t\t\t\tbig = 2;\n\t\t\t\t++f;\n\t\t\t}\n\t\t}\n\t\tif (*f == 'j') {\n\t\t\tbig = (sizeof(uintmax_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(uintmax_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\tif (*f == 'z') {\n\t\t\tbig = (sizeof(size_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(size_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\tif (*f == 't') {\n\t\t\tbig = (sizeof(ptrdiff_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(ptrdiff_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\t/* fmt[i] == '%' */\n\t\tswitch (*f) {\n\t\t\tcase 's': /* String pointer -> String */\n\t\t\t\t{\n\t\t\t\t\tsize_t count = 0;\n\t\t\t\t\tif (big) {\n\t\t\t\t\t\treturn written;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts = (char *)va_arg(args, char *);\n\t\t\t\t\t\tif (s == NULL) {\n\t\t\t\t\t\t\ts = \"(null)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (precision >= 0) {\n\t\t\t\t\t\t\twhile (*s && precision > 0) {\n\t\t\t\t\t\t\t\tOUT(*s++);\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tprecision--;\n\t\t\t\t\t\t\t\tif (arg_width && count == arg_width) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile (*s) {\n\t\t\t\t\t\t\t\tOUT(*s++);\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tif (arg_width && count == arg_width) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twhile (count < arg_width) {\n\t\t\t\t\t\tOUT(' ');\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'c': /* Single character */\n\t\t\t\tOUT((char)va_arg(args,int));\n\t\t\t\tbreak;\n\t\t\tcase 'p':\n\t\t\t\talt = 1;\n\t\t\t\tif (sizeof(void*) == sizeof(long long)) big = 2;\n\t\t\t\t/* fallthrough */\n\t\t\tcase 'X':\n\t\t\tcase 'x': /* Hexadecimal number */\n\t\t\t\t{\n\t\t\t\t\tunsigned long long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (unsigned long long)va_arg(args, unsigned long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (unsigned long)va_arg(args, unsigned long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (unsigned int)va_arg(args, unsigned int);\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_hex(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'i':\n\t\t\tcase 'd': /* Decimal number */\n\t\t\t\t{\n\t\t\t\t\tlong long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (long long)va_arg(args, long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (long)va_arg(args, long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (int)va_arg(args, int);\n\t\t\t\t\t}\n\t\t\t\t\tif (val < 0) {\n\t\t\t\t\t\tOUT('-');\n\t\t\t\t\t\tval = -val;\n\t\t\t\t\t} else if (always_sign) {\n\t\t\t\t\t\tOUT(always_sign == 2 ? ' ' : '+');\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_dec(val, arg_width, callback, userData, fill_zero, align, precision);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'u': /* Unsigned ecimal number */\n\t\t\t\t{\n\t\t\t\t\tunsigned long long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (unsigned long long)va_arg(args, unsigned long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (unsigned long)va_arg(args, unsigned long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (unsigned int)va_arg(args, unsigned int);\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_dec(val, arg_width, callback, userData, fill_zero, align, precision);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase '%': /* Escape */\n\t\t\t\tOUT('%');\n\t\t\t\tbreak;\n\t\t\tdefault: /* Nothing at all, just dump it */\n\t\t\t\tOUT(*f);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn written;\n}\n\nstruct CBData {\n\tchar * str;\n\tsize_t size;\n\tsize_t written;\n};\n\nstatic int cb_sprintf(void * user, char c) {\n\tstruct CBData * data = user;\n\tif (data->size > data->written + 1) {\n\t\tdata->str[data->written] = c;\n\t\tdata->written++;\n\t\tif (data->written < data->size) {\n\t\t\tdata->str[data->written] = '\\0';\n\t\t}\n\t}\n\treturn 0;\n}\n\nint vsnprintf(char *str, size_t size, const char *format, va_list ap) {\n\tstruct CBData data = {str,size,0};\n\tint out = xvasprintf(cb_sprintf, &data, format, ap);\n\tcb_sprintf(&data, '\\0');\n\treturn out;\n}\n\nint snprintf(char * str, size_t size, const char * format, ...) {\n\tstruct CBData data = {str,size,0};\n\tva_list args;\n\tva_start(args, format);\n\tint out = xvasprintf(cb_sprintf, &data, format, args);\n\tva_end(args);\n\tcb_sprintf(&data, '\\0');\n\treturn out;\n}\n\nstatic int cb_printf(void * user, char c) {\n\tprintf_output(1,(uint8_t*)&c);\n\treturn 0;\n}\n\nint printf(const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = xvasprintf(cb_printf, NULL, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n"
  },
  {
    "path": "kernel/misc/ksym.c",
    "content": "/**\n * @file  kernel/misc/ksym.c\n * @brief Kernel symbol table management.\n *\n * Essentially some wrappers around a hashmap; allows different\n * boot methods to provide symbol tables for use with linking\n * kernel modules.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/hashmap.h>\n#include <kernel/assert.h>\n#include <kernel/ksym.h>\n\nstatic hashmap_t * ksym_hash = NULL;\n\nvoid ksym_install(void) {\n\tassert(ksym_hash == NULL);\n\tksym_hash = hashmap_create(20);\n}\n\nvoid ksym_bind(const char * symname, void * value) {\n\tassert(ksym_hash != NULL);\n\n\thashmap_set(ksym_hash, symname, value);\n}\n\nvoid * ksym_lookup(const char * symname) {\n\treturn hashmap_get(ksym_hash, symname);\n}\n\nlist_t * ksym_list(void) {\n\tassert(ksym_hash != NULL);\n\treturn hashmap_keys(ksym_hash);\n}\n\nhashmap_t * ksym_get_map(void) {\n\treturn ksym_hash;\n}\n"
  },
  {
    "path": "kernel/misc/list.c",
    "content": "/**\n * @brief General-purpose list implementations.\n * @author K. Lange\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n */\n\n#include <stddef.h>\n#include <kernel/string.h>\n#include <kernel/list.h>\n\nvoid list_destroy(list_t * list) {\n\t/* Free all of the contents of a list */\n\tnode_t * n = list->head;\n\twhile (n) {\n\t\tfree(n->value);\n\t\tn = n->next;\n\t}\n}\n\nvoid list_free(list_t * list) {\n\t/* Free the actual structure of a list */\n\tnode_t * n = list->head;\n\twhile (n) {\n\t\tnode_t * s = n->next;\n\t\tfree(n);\n\t\tn = s;\n\t}\n}\n\nvoid list_append(list_t * list, node_t * node) {\n\t//assert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->next = NULL;\n\t/* Insert a node onto the end of a list */\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist->head = node;\n\t\tlist->tail = node;\n\t\tnode->prev = NULL;\n\t\tnode->next = NULL;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tlist->tail->next = node;\n\tnode->prev = list->tail;\n\tlist->tail = node;\n\tlist->length++;\n}\n\nnode_t * list_insert(list_t * list, void * item) {\n\t/* Insert an item into a list */\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append(list, node);\n\n\treturn node;\n}\n\nvoid list_append_after(list_t * list, node_t * before, node_t * node) {\n\t//assert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist_append(list, node);\n\t\treturn;\n\t}\n\tif (before == NULL) {\n\t\tnode->next = list->head;\n\t\tnode->prev = NULL;\n\t\tlist->head->prev = node;\n\t\tlist->head = node;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tif (before == list->tail) {\n\t\tlist->tail = node;\n\t} else {\n\t\tbefore->next->prev = node;\n\t\tnode->next = before->next;\n\t}\n\tnode->prev = before;\n\tbefore->next = node;\n\tlist->length++;\n}\n\nnode_t * list_insert_after(list_t * list, node_t * before, void * item) {\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append_after(list, before, node);\n\treturn node;\n}\n\nvoid list_append_before(list_t * list, node_t * after, node_t * node) {\n\t//assert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist_append(list, node);\n\t\treturn;\n\t}\n\tif (after == NULL) {\n\t\tnode->next = NULL;\n\t\tnode->prev = list->tail;\n\t\tlist->tail->next = node;\n\t\tlist->tail = node;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tif (after == list->head) {\n\t\tlist->head = node;\n\t} else {\n\t\tafter->prev->next = node;\n\t\tnode->prev = after->prev;\n\t}\n\tnode->next = after;\n\tafter->prev = node;\n\tlist->length++;\n}\n\nnode_t * list_insert_before(list_t * list, node_t * after, void * item) {\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append_before(list, after, node);\n\treturn node;\n}\n\nlist_t * list_create(const char * name, const void * metadata) {\n\t/* Create a fresh list */\n\tlist_t * out = malloc(sizeof(list_t));\n\tout->head = NULL;\n\tout->tail = NULL;\n\tout->length = 0;\n\tout->name = name;\n\tout->metadata = metadata;\n\treturn out;\n}\n\nnode_t * list_find(list_t * list, void * value) {\n\tforeach(item, list) {\n\t\tif (item->value == value) {\n\t\t\treturn item;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nint list_index_of(list_t * list, void * value) {\n\tint i = 0;\n\tforeach(item, list) {\n\t\tif (item->value == value) {\n\t\t\treturn i;\n\t\t}\n\t\ti++;\n\t}\n\treturn -1; /* not find */\n}\n\nvoid * list_index(list_t * list, int index) {\n\tint i = 0;\n\tforeach(item, list) {\n\t\tif (i == index) return item->value;\n\t\ti++;\n\t}\n\treturn NULL;\n}\n\nvoid list_remove(list_t * list, size_t index) {\n\t/* remove index from the list */\n\tif (index > list->length) return;\n\tsize_t i = 0;\n\tnode_t * n = list->head;\n\twhile (i < index) {\n\t\tn = n->next;\n\t\ti++;\n\t}\n\tlist_delete(list, n);\n}\n\nvoid list_delete(list_t * list, node_t * node) {\n\t/* remove node from the list */\n\t//assert(node->owner == list && \"Tried to remove a list node from a list it does not belong to.\");\n\tif (node == list->head) {\n\t\tlist->head = node->next;\n\t}\n\tif (node == list->tail) {\n\t\tlist->tail = node->prev;\n\t}\n\tif (node->prev) {\n\t\tnode->prev->next = node->next;\n\t}\n\tif (node->next) {\n\t\tnode->next->prev = node->prev;\n\t}\n\tnode->prev = NULL;\n\tnode->next = NULL;\n\tnode->owner = NULL;\n\tlist->length--;\n}\n\nnode_t * list_pop(list_t * list) {\n\t/* Remove and return the last value in the list\n\t * If you don't need it, you still probably want to free it!\n\t * Try free(list_pop(list)); !\n\t * */\n\tif (!list->tail) return NULL;\n\tnode_t * out = list->tail;\n\tlist_delete(list, out);\n\treturn out;\n}\n\nnode_t * list_dequeue(list_t * list) {\n\tif (!list->head) return NULL;\n\tnode_t * out = list->head;\n\tlist_delete(list, out);\n\treturn out;\n}\n\nlist_t * list_copy(list_t * original) {\n\t/* Create a new copy of original */\n\tlist_t * out = list_create(original->name, original->metadata);\n\tnode_t * node = original->head;\n\twhile (node) {\n\t\tlist_insert(out, node->value);\n\t}\n\treturn out;\n}\n\nvoid list_merge(list_t * target, list_t * source) {\n\t/* Destructively merges source into target */\n\tforeach(node, source) {\n\t\tnode->owner = target;\n\t}\n\tif (source->head) {\n\t\tsource->head->prev = target->tail;\n\t}\n\tif (target->tail) {\n\t\ttarget->tail->next = source->head;\n\t} else {\n\t\ttarget->head = source->head;\n\t}\n\tif (source->tail) {\n\t\ttarget->tail = source->tail;\n\t}\n\ttarget->length += source->length;\n\tfree(source);\n}\n\n"
  },
  {
    "path": "kernel/misc/malloc.c",
    "content": "/**\r\n * @file  kernel/misc/malloc.c\r\n * @brief klange's Slab Allocator\r\n *\r\n * This is one of the oldest parts of ToaruOS: the infamous heap allocator.\r\n * Used in userspace and the kernel alike, this is a straightforward \"slab\"-\r\n * style allocator. It has a handful of fixed sizes to stick small objects\r\n * in and keeps several together in a single page. It's surprisingly fast,\r\n * needs only an 'sbrk', makes only page-multiple calls to that sbrk, and\r\n * throwing a big lock around the whole thing seems to have worked just fine\r\n * for making it thread-safe in userspace applications (not necessarily\r\n * tested in the kernel).\r\n *\r\n * FIXME The heap allocator has long been lacking an ability to merge large\r\n *       freed blocks. There's #if 0'd code dating back over a decade in here.\r\n *\r\n * @copyright\r\n * This file is part of ToaruOS and is released under the terms\r\n * of the NCSA / University of Illinois License - see LICENSE.md\r\n * Copyright (c) 2010-2021 K. Lange.  All rights reserved.\r\n *\r\n * Developed by: K. Lange <klange@toaruos.org>\r\n *               Dave Majnemer <dmajnem2@acm.uiuc.edu>\r\n *               Assocation for Computing Machinery\r\n *               University of Illinois, Urbana-Champaign\r\n *               http://acm.uiuc.edu\r\n */\r\n\r\n/* Includes {{{ */\r\n#include <stddef.h>\r\n#include <stdint.h>\r\n#include <limits.h>\r\n\r\n#include <kernel/string.h>\r\n#include <kernel/printf.h>\r\n#include <kernel/spinlock.h>\r\n#include <kernel/mmu.h>\r\n#include <kernel/misc.h>\r\n/* }}} */\r\n/* Definitions {{{ */\r\n\r\n/*\r\n * Defines for often-used integral values\r\n * related to our binning and paging strategy.\r\n */\r\n#if defined(__x86_64__) || defined(__aarch64__)\r\n#define NUM_BINS 10U\t\t\t\t\t\t\t\t/* Number of bins, total, under 64-bit. */\r\n#define SMALLEST_BIN_LOG 3U\t\t\t\t\t\t\t/* Logarithm base two of the smallest bin: log_2(sizeof(int64)). */\r\n#else\r\n#define NUM_BINS 11U\t\t\t\t\t\t\t\t/* Number of bins, total, under 32-bit. */\r\n#define SMALLEST_BIN_LOG 2U\t\t\t\t\t\t\t/* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */\r\n#endif\r\n#define BIG_BIN (NUM_BINS - 1)\t\t\t\t\t\t/* Index for the big bin, (NUM_BINS - 1) */\r\n#define SMALLEST_BIN (1UL << SMALLEST_BIN_LOG)\t\t/* Size of the smallest bin. */\r\n\r\n#define PAGE_SIZE 0x1000\t\t\t\t\t\t\t/* Size of a page (in bytes), should be 4KB */\r\n#define PAGE_MASK (PAGE_SIZE - 1)\t\t\t\t\t/* Block mask, size of a page * number of pages - 1. */\r\n#define SKIP_P INT32_MAX\t\t\t\t\t\t\t/* INT32_MAX is half of UINT32_MAX; this gives us a 50% marker for skip lists. */\r\n#define SKIP_MAX_LEVEL 6\t\t\t\t\t\t\t/* We have a maximum of 6 levels in our skip lists. */\r\n\r\n#define BIN_MAGIC 0xDEFAD00D\r\n\r\n#if 1\r\n#define assert(statement) ((statement) ? (void)0 : __assert_fail(__FILE__, __LINE__, #statement))\r\n#else\r\n#define assert(statement) (void)0\r\n#endif\r\n\r\nstatic void __assert_fail(const char * f, int l, const char * stmt) {\r\n\tarch_fatal_prepare();\r\n\tdprintf(\"assertion failed in %s:%d %s\\n\", f, l, stmt);\r\n\tarch_dump_traceback();\r\n\tarch_fatal();\r\n}\r\n\r\n\r\n/* }}} */\r\n\r\n/*\r\n * Internal functions.\r\n */\r\nstatic void * __attribute__ ((malloc)) klmalloc(uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klrealloc(void * ptr, uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klvalloc(uintptr_t size);\r\nstatic void klfree(void * ptr);\r\n\r\nstatic spin_lock_t mem_lock =  { 0 };\r\n\r\nvoid * __attribute__ ((malloc)) malloc(uintptr_t size) {\r\n\tspin_lock(mem_lock);\r\n\tvoid * out = klmalloc(size);\r\n\tspin_unlock(mem_lock);\r\n\treturn out;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size) {\r\n\tspin_lock(mem_lock);\r\n\tvoid * out = klrealloc(ptr, size);\r\n\tspin_unlock(mem_lock);\r\n\treturn out;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size) {\r\n\tspin_lock(mem_lock);\r\n\tvoid * out = klcalloc(nmemb, size);\r\n\tspin_unlock(mem_lock);\r\n\treturn out;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) valloc(uintptr_t size) {\r\n\tspin_lock(mem_lock);\r\n\tvoid * out = klvalloc(size);\r\n\tspin_unlock(mem_lock);\r\n\treturn out;\r\n}\r\n\r\nvoid free(void * ptr) {\r\n\tspin_lock(mem_lock);\r\n#ifndef __aarch64__\r\n\tif (ptr < (void*)0xffffff0000000000) {\r\n\t\tprintf(\"Invalid free detected (%p)\\n\", ptr);\r\n\t\twhile (1) {};\r\n\t}\r\n#endif\r\n\tklfree(ptr);\r\n\tspin_unlock(mem_lock);\r\n}\r\n\r\n/* Bin management {{{ */\r\n\r\n/*\r\n * Adjust bin size in bin_size call to proper bounds.\r\n */\r\nstatic inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_adjust_bin(uintptr_t bin)\r\n{\r\n\tif (bin <= (uintptr_t)SMALLEST_BIN_LOG)\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n\tbin -= SMALLEST_BIN_LOG + 1;\r\n\tif (bin > (uintptr_t)BIG_BIN) {\r\n\t\treturn BIG_BIN;\r\n\t}\r\n\treturn bin;\r\n}\r\n\r\n/*\r\n * Given a size value, find the correct bin\r\n * to place the requested allocation in.\r\n */\r\nstatic inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_bin_size(uintptr_t size) {\r\n\tuintptr_t bin = sizeof(size) * CHAR_BIT - __builtin_clzl(size);\r\n\tbin += !!(size & (size - 1));\r\n\treturn klmalloc_adjust_bin(bin);\r\n}\r\n\r\n/*\r\n * Bin header - One page of memory.\r\n * Appears at the front of a bin to point to the\r\n * previous bin (or NULL if the first), the next bin\r\n * (or NULL if the last) and the head of the bin, which\r\n * is a stack of cells of data.\r\n */\r\ntypedef struct _klmalloc_bin_header {\r\n\tstruct _klmalloc_bin_header *  next;\t/* Pointer to the next node. */\r\n\tvoid * head;\t\t\t\t\t\t\t/* Head of this bin. */\r\n\tuintptr_t size;\t\t\t\t\t\t\t/* Size of this bin, if big; otherwise bin index. */\r\n\tuintptr_t bin_magic;\r\n} klmalloc_bin_header;\r\n\r\n/*\r\n * A big bin header is basically the same as a regular bin header\r\n * only with a pointer to the previous (physically) instead of\r\n * a \"next\" and with a list of forward headers.\r\n */\r\ntypedef struct _klmalloc_big_bin_header {\r\n\tstruct _klmalloc_big_bin_header * next;\r\n\tvoid * head;\r\n\tuintptr_t size;\r\n\tuintptr_t bin_magic;\r\n\tstruct _klmalloc_big_bin_header * prev;\r\n\tstruct _klmalloc_big_bin_header * forward[SKIP_MAX_LEVEL+1];\r\n} klmalloc_big_bin_header;\r\n\r\n\r\n/*\r\n * List of pages in a bin.\r\n */\r\ntypedef struct _klmalloc_bin_header_head {\r\n\tklmalloc_bin_header * first;\r\n} klmalloc_bin_header_head;\r\n\r\n/*\r\n * Array of available bins.\r\n */\r\nstatic klmalloc_bin_header_head klmalloc_bin_head[NUM_BINS - 1];\t/* Small bins */\r\nstatic struct _klmalloc_big_bins {\r\n\tklmalloc_big_bin_header head;\r\n\tint level;\r\n} klmalloc_big_bins;\r\nstatic klmalloc_big_bin_header * klmalloc_newest_big = NULL;\t\t/* Newest big bin */\r\n\r\n/* }}} Bin management */\r\n/* Doubly-Linked List {{{ */\r\n\r\n/*\r\n * Remove an entry from a page list.\r\n * Decouples the element from its\r\n * position in the list by linking\r\n * its neighbors to eachother.\r\n */\r\nstatic inline void __attribute__ ((always_inline)) klmalloc_list_decouple(klmalloc_bin_header_head *head, klmalloc_bin_header *node) {\r\n\tklmalloc_bin_header *next\t= node->next;\r\n\thead->first = next;\r\n\tnode->next = NULL;\r\n}\r\n\r\n/*\r\n * Insert an entry into a page list.\r\n * The new entry is placed at the front\r\n * of the list and the existing border\r\n * elements are updated to point back\r\n * to it (our list is doubly linked).\r\n */\r\nstatic inline void __attribute__ ((always_inline)) klmalloc_list_insert(klmalloc_bin_header_head *head, klmalloc_bin_header *node) {\r\n\tnode->next = head->first;\r\n\thead->first = node;\r\n}\r\n\r\n/*\r\n * Get the head of a page list.\r\n * Because redundant function calls\r\n * are really great, and just in case\r\n * we change the list implementation.\r\n */\r\nstatic inline klmalloc_bin_header * __attribute__ ((always_inline)) klmalloc_list_head(klmalloc_bin_header_head *head) {\r\n\treturn head->first;\r\n}\r\n\r\n/* }}} Lists */\r\n/* Skip List {{{ */\r\n\r\n/*\r\n * Skip lists are efficient\r\n * data structures for storing\r\n * and searching ordered data.\r\n *\r\n * Here, the skip lists are used\r\n * to keep track of big bins.\r\n */\r\n\r\n/*\r\n * Generate a random value in an appropriate range.\r\n * This is a xor-shift RNG.\r\n */\r\nstatic uint32_t __attribute__ ((pure)) klmalloc_skip_rand(void) {\r\n\tstatic uint32_t x = 123456789;\r\n\tstatic uint32_t y = 362436069;\r\n\tstatic uint32_t z = 521288629;\r\n\tstatic uint32_t w = 88675123;\r\n\r\n\tuint32_t t;\r\n\r\n\tt = x ^ (x << 11);\r\n\tx = y; y = z; z = w;\r\n\treturn w = w ^ (w >> 19) ^ t ^ (t >> 8);\r\n}\r\n\r\n/*\r\n * Generate a random level for a skip node\r\n */\r\nstatic inline int __attribute__ ((pure, always_inline)) klmalloc_random_level(void) {\r\n\tint level = 0;\r\n\t/*\r\n\t * Keep trying to check rand() against 50% of its maximum.\r\n\t * This provides 50%, 25%, 12.5%, etc. chance for each level.\r\n\t */\r\n\twhile (klmalloc_skip_rand() < SKIP_P && level < SKIP_MAX_LEVEL) {\r\n\t\t++level;\r\n\t}\r\n\treturn level;\r\n}\r\n\r\n/*\r\n * Find best fit for a given value.\r\n */\r\nstatic klmalloc_big_bin_header * klmalloc_skip_list_findbest(uintptr_t search_size) {\r\n\tklmalloc_big_bin_header * node = &klmalloc_big_bins.head;\r\n\t/*\r\n\t * Loop through the skip list until we hit something > our search value.\r\n\t */\r\n\tint i;\r\n\tfor (i = klmalloc_big_bins.level; i >= 0; --i) {\r\n\t\twhile (node->forward[i] && (node->forward[i]->size < search_size)) {\r\n\t\t\tnode = node->forward[i];\r\n\t\t\tif (node)\r\n\t\t\t\tassert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t}\r\n\t}\r\n\t/*\r\n\t * This value will either be NULL (we found nothing)\r\n\t * or a node (we found a minimum fit).\r\n\t */\r\n\tnode = node->forward[0];\r\n\tif (node) {\r\n\t\tassert((uintptr_t)node % PAGE_SIZE == 0);\r\n\t\tassert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t}\r\n\treturn node;\r\n}\r\n\r\n/*\r\n * Insert a header into the skip list.\r\n */\r\nstatic void klmalloc_skip_list_insert(klmalloc_big_bin_header * value) {\r\n\t/*\r\n\t * You better be giving me something valid to insert,\r\n\t * or I will slit your ****ing throat.\r\n\t */\r\n\tassert(value != NULL);\r\n\tassert(value->head != NULL);\r\n\tassert((uintptr_t)value->head > (uintptr_t)value);\r\n\tif (value->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)value->head < (uintptr_t)value + value->size);\r\n\t} else {\r\n\t\tassert((uintptr_t)value->head < (uintptr_t)value + PAGE_SIZE);\r\n\t}\r\n\tassert((uintptr_t)value % PAGE_SIZE == 0);\r\n\tassert((value->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\tassert(value->size != 0);\r\n\r\n\t/*\r\n\t * Starting from the head node of the bin locator...\r\n\t */\r\n\tklmalloc_big_bin_header * node = &klmalloc_big_bins.head;\r\n\tklmalloc_big_bin_header * update[SKIP_MAX_LEVEL + 1];\r\n\r\n\t/*\r\n\t * Loop through the skiplist to find the right place\r\n\t * to insert the node (where ->forward[] > value)\r\n\t */\r\n\tint i;\r\n\tfor (i = klmalloc_big_bins.level; i >= 0; --i) {\r\n\t\twhile (node->forward[i] && node->forward[i]->size < value->size) {\r\n\t\t\tnode = node->forward[i];\r\n\t\t\tif (node)\r\n\t\t\t\tassert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t}\r\n\t\tupdate[i] = node;\r\n\t}\r\n\tnode = node->forward[0];\r\n\r\n\t/*\r\n\t * Make the new skip node and update\r\n\t * the forward values.\r\n\t */\r\n\tif (node != value) {\r\n\t\tint level = klmalloc_random_level();\r\n\t\t/*\r\n\t\t * Get all of the nodes before this.\r\n\t\t */\r\n\t\tif (level > klmalloc_big_bins.level) {\r\n\t\t\tfor (i = klmalloc_big_bins.level + 1; i <= level; ++i) {\r\n\t\t\t\tupdate[i] = &klmalloc_big_bins.head;\r\n\t\t\t}\r\n\t\t\tklmalloc_big_bins.level = level;\r\n\t\t}\r\n\r\n\t\t/*\r\n\t\t * Make the new node.\r\n\t\t */\r\n\t\tnode = value;\r\n\r\n\t\t/*\r\n\t\t * Run through and point the preceeding nodes\r\n\t\t * for each level to the new node.\r\n\t\t */\r\n\t\tfor (i = 0; i <= level; ++i) {\r\n\t\t\tnode->forward[i] = update[i]->forward[i];\r\n\t\t\tif (node->forward[i])\r\n\t\t\t\tassert((node->forward[i]->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\tupdate[i]->forward[i] = node;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/*\r\n * Delete a header from the skip list.\r\n * Be sure you didn't change the size, or we won't be able to find it.\r\n */\r\nstatic void klmalloc_skip_list_delete(klmalloc_big_bin_header * value) {\r\n\t/*\r\n\t * Debug assertions\r\n\t */\r\n\tassert(value != NULL);\r\n\tassert(value->head);\r\n\tassert((uintptr_t)value->head > (uintptr_t)value);\r\n\tif (value->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)value->head < (uintptr_t)value + value->size);\r\n\t} else {\r\n\t\tassert((uintptr_t)value->head < (uintptr_t)value + PAGE_SIZE);\r\n\t}\r\n\r\n\t/*\r\n\t * Starting from the bin header, again...\r\n\t */\r\n\tklmalloc_big_bin_header * node = &klmalloc_big_bins.head;\r\n\tklmalloc_big_bin_header * update[SKIP_MAX_LEVEL + 1];\r\n\r\n\t/*\r\n\t * Find the node.\r\n\t */\r\n\tint i;\r\n\tfor (i = klmalloc_big_bins.level; i >= 0; --i) {\r\n\t\twhile (node->forward[i] && node->forward[i]->size < value->size) {\r\n\t\t\tnode = node->forward[i];\r\n\t\t\tif (node)\r\n\t\t\t\tassert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t}\r\n\t\tupdate[i] = node;\r\n\t}\r\n\tnode = node->forward[0];\r\n\twhile (node != value) {\r\n\t\tnode = node->forward[0];\r\n\t}\r\n\r\n\tif (node != value) {\r\n\t\tnode = klmalloc_big_bins.head.forward[0];\r\n\t\twhile (node->forward[0] && node->forward[0] != value) {\r\n\t\t\tnode = node->forward[0];\r\n\t\t}\r\n\t\tnode = node->forward[0];\r\n\t}\r\n\t/*\r\n\t * If we found the node, delete it;\r\n\t * otherwise, we do nothing.\r\n\t */\r\n\tif (node == value) {\r\n\t\tfor (i = 0; i <= klmalloc_big_bins.level; ++i) {\r\n\t\t\tif (update[i]->forward[i] != node) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tupdate[i]->forward[i] = node->forward[i];\r\n\t\t\tif (update[i]->forward[i]) {\r\n\t\t\t\tassert((uintptr_t)(update[i]->forward[i]) % PAGE_SIZE == 0);\r\n\t\t\t\tassert((update[i]->forward[i]->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\twhile (klmalloc_big_bins.level > 0 && klmalloc_big_bins.head.forward[klmalloc_big_bins.level] == NULL) {\r\n\t\t\t--klmalloc_big_bins.level;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/* }}} */\r\n/* Stack {{{ */\r\n/*\r\n * Pop an item from a block.\r\n * Free space is stored as a stack,\r\n * so we get a free space for a bin\r\n * by popping a free node from the\r\n * top of the stack.\r\n */\r\nstatic void * klmalloc_stack_pop(klmalloc_bin_header *header) {\r\n\tassert(header);\r\n\tassert(header->head != NULL);\r\n\tassert((uintptr_t)header->head > (uintptr_t)header);\r\n\tif (header->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)header->head < (uintptr_t)header + header->size);\r\n\t} else {\r\n\t\tassert((uintptr_t)header->head < (uintptr_t)header + PAGE_SIZE);\r\n\t\tassert((uintptr_t)header->head > (uintptr_t)header + sizeof(klmalloc_bin_header) - 1);\r\n\t}\r\n\t\r\n\t/*\r\n\t * Remove the current head and point\r\n\t * the head to where the old head pointed.\r\n\t */\r\n\tvoid *item = header->head;\r\n\tuintptr_t **head = header->head;\r\n\tuintptr_t *next = *head;\r\n\theader->head = next;\r\n\treturn item;\r\n}\r\n\r\n/*\r\n * Push an item into a block.\r\n * When we free memory, we need\r\n * to add the freed cell back\r\n * into the stack of free spaces\r\n * for the block.\r\n */\r\nstatic void klmalloc_stack_push(klmalloc_bin_header *header, void *ptr) {\r\n\tassert(ptr != NULL);\r\n\tassert((uintptr_t)ptr > (uintptr_t)header);\r\n\tif (header->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)ptr < (uintptr_t)header + header->size);\r\n\t} else {\r\n\t\tassert((((uintptr_t)ptr - sizeof(klmalloc_bin_header)) & ((1UL << (header->size + SMALLEST_BIN_LOG)) - 1)) == 0);\r\n\t\tassert((uintptr_t)ptr < (uintptr_t)header + PAGE_SIZE);\r\n\t}\r\n\tuintptr_t **item = (uintptr_t **)ptr;\r\n\t*item = (uintptr_t *)header->head;\r\n\theader->head = item;\r\n}\r\n\r\n/*\r\n * Is this cell stack empty?\r\n * If the head of the stack points\r\n * to NULL, we have exhausted the\r\n * stack, so there is no more free\r\n * space available in the block.\r\n */\r\nstatic inline int __attribute__ ((always_inline)) klmalloc_stack_empty(klmalloc_bin_header *header) {\r\n\treturn header->head == NULL;\r\n}\r\n\r\n/* }}} Stack */\r\n\r\n/* malloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klmalloc(uintptr_t size) {\r\n\t/*\r\n\t * C standard implementation:\r\n\t * If size is zero, we can choose do a number of things.\r\n\t * This implementation will return a NULL pointer.\r\n\t */\r\n\tif (__builtin_expect(size == 0, 0))\r\n\t\treturn NULL;\r\n\r\n\t/*\r\n\t * Find the appropriate bin for the requested\r\n\t * allocation and start looking through that list.\r\n\t */\r\n\tunsigned int bucket_id = klmalloc_bin_size(size);\r\n\r\n\tif (bucket_id < BIG_BIN) {\r\n\t\t/*\r\n\t\t * Small bins.\r\n\t\t */\r\n\t\tklmalloc_bin_header * bin_header = klmalloc_list_head(&klmalloc_bin_head[bucket_id]);\r\n\t\tif (!bin_header) {\r\n\t\t\t/*\r\n\t\t\t * Grow the heap for the new bin.\r\n\t\t\t */\r\n\t\t\tbin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE);\r\n\t\t\tbin_header->bin_magic = BIN_MAGIC;\r\n\t\t\tassert((uintptr_t)bin_header % PAGE_SIZE == 0);\r\n\r\n\t\t\t/*\r\n\t\t\t * Set the head of the stack.\r\n\t\t\t */\r\n\t\t\tbin_header->head = (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header));\r\n\t\t\t/*\r\n\t\t\t * Insert the new bin at the front of\r\n\t\t\t * the list of bins for this size.\r\n\t\t\t */\r\n\t\t\tklmalloc_list_insert(&klmalloc_bin_head[bucket_id], bin_header);\r\n\t\t\t/*\r\n\t\t\t * Initialize the stack inside the bin.\r\n\t\t\t * The stack is initially full, with each\r\n\t\t\t * entry pointing to the next until the end\r\n\t\t\t * which points to NULL.\r\n\t\t\t */\r\n\t\t\tuintptr_t adj = SMALLEST_BIN_LOG + bucket_id;\r\n\t\t\tuintptr_t i, available = ((PAGE_SIZE - sizeof(klmalloc_bin_header)) >> adj) - 1;\r\n\r\n\t\t\tuintptr_t **base = bin_header->head;\r\n\t\t\tfor (i = 0; i < available; ++i) {\r\n\t\t\t\t/*\r\n\t\t\t\t * Our available memory is made into a stack, with each\r\n\t\t\t\t * piece of memory turned into a pointer to the next\r\n\t\t\t\t * available piece. When we want to get a new piece\r\n\t\t\t\t * of memory from this block, we just pop off a free\r\n\t\t\t\t * spot and give its address.\r\n\t\t\t\t */\r\n\t\t\t\tbase[i << bucket_id] = (uintptr_t *)&base[(i + 1) << bucket_id];\r\n\t\t\t}\r\n\t\t\tbase[available << bucket_id] = NULL;\r\n\t\t\tbin_header->size = bucket_id;\r\n\t\t} else {\r\n\t\t\tassert(bin_header->bin_magic == BIN_MAGIC);\r\n\t\t}\r\n\t\tuintptr_t ** item = klmalloc_stack_pop(bin_header);\r\n\t\tif (klmalloc_stack_empty(bin_header)) {\r\n\t\t\tklmalloc_list_decouple(&(klmalloc_bin_head[bucket_id]),bin_header);\r\n\t\t}\r\n\t\treturn item;\r\n\t} else {\r\n\t\t/*\r\n\t\t * Big bins.\r\n\t\t */\r\n\t\tklmalloc_big_bin_header * bin_header = klmalloc_skip_list_findbest(size);\r\n\t\tif (bin_header) {\r\n\t\t\tassert(bin_header->size >= size);\r\n\t\t\t/*\r\n\t\t\t * If we found one, delete it from the skip list\r\n\t\t\t */\r\n\t\t\tklmalloc_skip_list_delete(bin_header);\r\n\t\t\t/*\r\n\t\t\t * Retreive the head of the block.\r\n\t\t\t */\r\n\t\t\tuintptr_t ** item = klmalloc_stack_pop((klmalloc_bin_header *)bin_header);\r\n#if 0\r\n\t\t\t/*\r\n\t\t\t * Resize block, if necessary\r\n\t\t\t */\r\n\t\t\tassert(bin_header->head == NULL);\r\n\t\t\tuintptr_t old_size = bin_header->size;\r\n\t\t\t//uintptr_t rsize = size;\r\n\t\t\t/*\r\n\t\t\t * Round the requeste size to our full required size.\r\n\t\t\t */\r\n\t\t\tsize = ((size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1) * PAGE_SIZE - sizeof(klmalloc_big_bin_header);\r\n\t\t\tassert((size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\tif (bin_header->size > size * 2) {\r\n\t\t\t\tassert(old_size != size);\r\n\t\t\t\t/*\r\n\t\t\t\t * If we have extra space, start splitting.\r\n\t\t\t\t */\r\n\t\t\t\tbin_header->size = size;\r\n\t\t\t\tassert(sbrk(0) >= bin_header->size + (uintptr_t)bin_header);\r\n\t\t\t\t/*\r\n\t\t\t\t * Make a new block at the end of the needed space.\r\n\t\t\t\t */\r\n\t\t\t\tklmalloc_big_bin_header * header_new = (klmalloc_big_bin_header *)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header) + size);\r\n\t\t\t\tassert((uintptr_t)header_new % PAGE_SIZE == 0);\r\n\t\t\t\tmemset(header_new, 0, sizeof(klmalloc_big_bin_header) + sizeof(void *));\r\n\t\t\t\theader_new->prev = bin_header;\r\n\t\t\t\tif (bin_header->next) {\r\n\t\t\t\t\tbin_header->next->prev = header_new;\r\n\t\t\t\t}\r\n\t\t\t\theader_new->next = bin_header->next;\r\n\t\t\t\tbin_header->next = header_new;\r\n\t\t\t\tif (klmalloc_newest_big == bin_header) {\r\n\t\t\t\t\tklmalloc_newest_big = header_new;\r\n\t\t\t\t}\r\n\t\t\t\theader_new->size = old_size - (size + sizeof(klmalloc_big_bin_header));\r\n\t\t\t\tassert(((uintptr_t)header_new->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\t\tfprintf(stderr, \"Splitting %p [now %zx] at %p [%zx] from [%zx,%zx].\\n\", (void*)bin_header, bin_header->size, (void*)header_new, header_new->size, old_size, size);\r\n\t\t\t\t/*\r\n\t\t\t\t * Free the new block.\r\n\t\t\t\t */\r\n\t\t\t\tklfree((void *)((uintptr_t)header_new + sizeof(klmalloc_big_bin_header)));\r\n\t\t\t}\r\n#endif\r\n\t\t\treturn item;\r\n\t\t} else {\r\n\t\t\t/*\r\n\t\t\t * Round requested size to a set of pages, plus the header size.\r\n\t\t\t */\r\n\t\t\tuintptr_t pages = (size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1;\r\n\t\t\tbin_header = (klmalloc_big_bin_header*)sbrk(PAGE_SIZE * pages);\r\n\t\t\tbin_header->bin_magic = BIN_MAGIC;\r\n\t\t\tassert((uintptr_t)bin_header % PAGE_SIZE == 0);\r\n\t\t\t/*\r\n\t\t\t * Give the header the remaining space.\r\n\t\t\t */\r\n\t\t\tbin_header->size = pages * PAGE_SIZE - sizeof(klmalloc_big_bin_header);\r\n\t\t\tassert((bin_header->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\t/*\r\n\t\t\t * Link the block in physical memory.\r\n\t\t\t */\r\n\t\t\tbin_header->prev = klmalloc_newest_big;\r\n\t\t\tif (bin_header->prev) {\r\n\t\t\t\tbin_header->prev->next = bin_header;\r\n\t\t\t}\r\n\t\t\tklmalloc_newest_big = bin_header;\r\n\t\t\tbin_header->next = NULL;\r\n\t\t\t/*\r\n\t\t\t * Return the head of the block.\r\n\t\t\t */\r\n\t\t\tbin_header->head = NULL;\r\n\t\t\treturn (void*)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header));\r\n\t\t}\r\n\t}\r\n}\r\n/* }}} */\r\n/* free() {{{ */\r\nstatic void klfree(void *ptr) {\r\n\t/*\r\n\t * C standard implementation: Do nothing when NULL is passed to free.\r\n\t */\r\n\tif (__builtin_expect(ptr == NULL, 0)) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t/*\r\n\t * Woah, woah, hold on, was this a page-aligned block?\r\n\t */\r\n\tif ((uintptr_t)ptr % PAGE_SIZE == 0) {\r\n\t\t/*\r\n\t\t * Well howdy-do, it was.\r\n\t\t */\r\n\t\tptr = (void *)((uintptr_t)ptr - 1);\r\n\t}\r\n\r\n\t/*\r\n\t * Get our pointer to the head of this block by\r\n\t * page aligning it.\r\n\t */\r\n\tklmalloc_bin_header * header = (klmalloc_bin_header *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK);\r\n\tassert((uintptr_t)header % PAGE_SIZE == 0);\r\n\r\n\tif (header->bin_magic != BIN_MAGIC)\r\n\t\treturn;\r\n\r\n\t/*\r\n\t * For small bins, the bin number is stored in the size\r\n\t * field of the header. For large bins, the actual size\r\n\t * available in the bin is stored in this field. It's\r\n\t * easy to tell which is which, though.\r\n\t */\r\n\tuintptr_t bucket_id = header->size;\r\n\tif (bucket_id > (uintptr_t)NUM_BINS) {\r\n\t\tbucket_id = BIG_BIN;\r\n\t\tklmalloc_big_bin_header *bheader = (klmalloc_big_bin_header*)header;\r\n\t\t\r\n\t\tassert(bheader);\r\n\t\tassert(bheader->head == NULL);\r\n\t\tassert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t/*\r\n\t\t * Coalesce forward blocks into us.\r\n\t\t */\r\n#if 0\r\n\t\tif (bheader != klmalloc_newest_big) {\r\n\t\t\t/*\r\n\t\t\t * If we are not the newest big bin, there is most definitely\r\n\t\t\t * something in front of us that we can read.\r\n\t\t\t */\r\n\t\t\tassert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t\tklmalloc_big_bin_header * next = (void *)((uintptr_t)bheader + sizeof(klmalloc_big_bin_header) + bheader->size);\r\n\t\t\tassert((uintptr_t)next % PAGE_SIZE == 0);\r\n\t\t\tif (next == bheader->next && next->head) { //next->size > NUM_BINS && next->head) {\r\n\t\t\t\t/*\r\n\t\t\t\t * If that something is an available big bin, we can\r\n\t\t\t\t * coalesce it into us to form one larger bin.\r\n\t\t\t\t */\r\n\r\n\t\t\t\tuintptr_t old_size = bheader->size;\r\n\r\n\t\t\t\tklmalloc_skip_list_delete(next);\r\n\t\t\t\tbheader->size = (uintptr_t)bheader->size + (uintptr_t)sizeof(klmalloc_big_bin_header) + next->size;\r\n\t\t\t\tassert((bheader->size + sizeof(klmalloc_big_bin_header))  % PAGE_SIZE == 0);\r\n\r\n\t\t\t\tif (next == klmalloc_newest_big) {\r\n\t\t\t\t\t/*\r\n\t\t\t\t\t * If the guy in front of us was the newest,\r\n\t\t\t\t\t * we are now the newest (as we are him).\r\n\t\t\t\t\t */\r\n\t\t\t\t\tklmalloc_newest_big = bheader;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (next->next) {\r\n\t\t\t\t\t\tnext->next->prev = bheader;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tfprintf(stderr,\"Coelesced (forwards)  %p [%zx] <- %p [%zx] = %zx\\n\", (void*)bheader, old_size, (void*)next, next->size, bheader->size);\r\n\t\t\t}\r\n\t\t}\r\n#endif\r\n\t\t/*\r\n\t\t * Coalesce backwards\r\n\t\t */\r\n#if 0\r\n\t\tif (bheader->prev && bheader->prev->head) {\r\n\t\t\t/*\r\n\t\t\t * If there is something behind us, it is available, and there is nothing between\r\n\t\t\t * it and us, we can coalesce ourselves into it to form a big block.\r\n\t\t\t */\r\n\t\t\tif ((uintptr_t)bheader->prev + (bheader->prev->size + sizeof(klmalloc_big_bin_header)) == (uintptr_t)bheader) {\r\n\r\n\t\t\t\tuintptr_t old_size = bheader->prev->size;\r\n\r\n\t\t\t\tklmalloc_skip_list_delete(bheader->prev);\r\n\t\t\t\tbheader->prev->size = (uintptr_t)bheader->prev->size + (uintptr_t)bheader->size + sizeof(klmalloc_big_bin_header);\r\n\t\t\t\tassert((bheader->prev->size + sizeof(klmalloc_big_bin_header))  % PAGE_SIZE == 0);\r\n\t\t\t\tklmalloc_skip_list_insert(bheader->prev);\r\n\t\t\t\tif (klmalloc_newest_big == bheader) {\r\n\t\t\t\t\tklmalloc_newest_big = bheader->prev;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (bheader->next) {\r\n\t\t\t\t\t\tbheader->next->prev = bheader->prev;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tfprintf(stderr,\"Coelesced (backwards) %p [%zx] <- %p [%zx] = %zx\\n\", (void*)bheader->prev, old_size, (void*)bheader, bheader->size, bheader->size);\r\n\t\t\t\t/*\r\n\t\t\t\t * If we coalesced backwards, we are done.\r\n\t\t\t\t */\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n#endif\r\n\t\t/*\r\n\t\t * Push new space back into the stack.\r\n\t\t */\r\n\t\tklmalloc_stack_push((klmalloc_bin_header *)bheader, (void *)((uintptr_t)bheader + sizeof(klmalloc_big_bin_header)));\r\n\t\tassert(bheader->head != NULL);\r\n\t\t/*\r\n\t\t * Insert the block into list of available slabs.\r\n\t\t */\r\n\t\tklmalloc_skip_list_insert(bheader);\r\n\t} else {\r\n\r\n\t\t/*\r\n\t\t * If the stack is empty, we are freeing\r\n\t\t * a block from a previously full bin.\r\n\t\t * Return it to the busy bins list.\r\n\t\t */\r\n\t\tif (klmalloc_stack_empty(header)) {\r\n\t\t\tklmalloc_list_insert(&klmalloc_bin_head[bucket_id], header);\r\n\t\t}\r\n\t\t/*\r\n\t\t * Push new space back into the stack.\r\n\t\t */\r\n\t\tklmalloc_stack_push(header, ptr);\r\n\t}\r\n}\r\n/* }}} */\r\n/* valloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klvalloc(uintptr_t size) {\r\n\t/*\r\n\t * Allocate a page-aligned block.\r\n\t * XXX: THIS IS HORRIBLY, HORRIBLY WASTEFUL!! ONLY USE THIS\r\n\t *      IF YOU KNOW WHAT YOU ARE DOING!\r\n\t */\r\n\tuintptr_t true_size = size + PAGE_SIZE - sizeof(klmalloc_big_bin_header); /* Here we go... */\r\n\tvoid * result = klmalloc(true_size);\r\n\tvoid * out = (void *)((uintptr_t)result + (PAGE_SIZE - sizeof(klmalloc_big_bin_header)));\r\n\tassert((uintptr_t)out % PAGE_SIZE == 0);\r\n\treturn out;\r\n}\r\n/* }}} */\r\n/* realloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klrealloc(void *ptr, uintptr_t size) {\r\n\t/*\r\n\t * C standard implementation: When NULL is passed to realloc,\r\n\t * simply malloc the requested size and return a pointer to that.\r\n\t */\r\n\tif (__builtin_expect(ptr == NULL, 0))\r\n\t\treturn klmalloc(size);\r\n\r\n\t/*\r\n\t * C standard implementation: For a size of zero, free the\r\n\t * pointer and return NULL, allocating no new memory.\r\n\t */\r\n\tif (__builtin_expect(size == 0, 0))\r\n\t{\r\n\t\tfree(ptr);\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\t/*\r\n\t * Find the bin for the given pointer\r\n\t * by aligning it to a page.\r\n\t */\r\n\tklmalloc_bin_header * header_old = (void *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK);\r\n\tif (header_old->bin_magic != BIN_MAGIC) {\r\n\t\tassert(0 && \"Bad magic on realloc.\");\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\tuintptr_t old_size = header_old->size;\r\n\tif (old_size < (uintptr_t)BIG_BIN) {\r\n\t\t/*\r\n\t\t * If we are copying from a small bin,\r\n\t\t * we need to get the size of the bin\r\n\t\t * from its id.\r\n\t\t */\r\n\t\told_size = (1UL << (SMALLEST_BIN_LOG + old_size));\r\n\t}\r\n\r\n\t/*\r\n\t * (This will only happen for a big bin, mathematically speaking)\r\n\t * If we still have room in our bin for the additonal space,\r\n\t * we don't need to do anything.\r\n\t */\r\n\tif (old_size >= size) {\r\n\r\n\t\t/*\r\n\t\t * TODO: Break apart blocks here, which is far more important\r\n\t\t *       than breaking them up on allocations.\r\n\t\t */\r\n\t\treturn ptr;\r\n\t}\r\n\r\n\t/*\r\n\t * Reallocate more memory.\r\n\t */\r\n\tvoid * newptr = klmalloc(size);\r\n\tif (__builtin_expect(newptr != NULL, 1)) {\r\n\r\n\t\t/*\r\n\t\t * Copy the old value into the new value.\r\n\t\t * Be sure to only copy as much as was in\r\n\t\t * the old block.\r\n\t\t */\r\n\t\tmemcpy(newptr, ptr, old_size);\r\n\t\tklfree(ptr);\r\n\t\treturn newptr;\r\n\t}\r\n\r\n\t/*\r\n\t * We failed to allocate more memory,\r\n\t * which means we're probably out.\r\n\t *\r\n\t * Bail and return NULL.\r\n\t */\r\n\treturn NULL;\r\n}\r\n/* }}} */\r\n/* calloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size) {\r\n\t/*\r\n\t * Allocate memory and zero it before returning\r\n\t * a pointer to the newly allocated memory.\r\n\t * \r\n\t * Implemented by way of a simple malloc followed\r\n\t * by a memset to 0x00 across the length of the\r\n\t * requested memory chunk.\r\n\t */\r\n\r\n\tvoid *ptr = klmalloc(nmemb * size);\r\n\tif (__builtin_expect(ptr != NULL, 1))\r\n\t\tmemset(ptr,0x00,nmemb * size);\r\n\treturn ptr;\r\n}\r\n/* }}} */\r\n\r\n\r\n"
  },
  {
    "path": "kernel/misc/pci.c",
    "content": "/**\n * @file  kernel/misc/pci.c\n * @brief PCI configuration and scanning.\n *\n * Functions for dealing with PCI devices through configuration mode #1\n * (CPU port I/O methods), including scanning and modifying device\n * configuration bytes.\n *\n * This used to have methods for dealing with ISA bridge IRQ remapping,\n * but it has been removed for the moment.\n *\n * TODO: Implement MSI configuration?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n */\n#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/pci.h>\n#include <kernel/printf.h>\n\n#include <kernel/mmu.h>\n\n#ifdef __x86_64__\n#include <kernel/arch/x86_64/ports.h>\n#endif\n\nstatic uintptr_t pcie_addr(uint32_t device, int field) {\n\treturn (pci_extract_bus(device) << 20) | (pci_extract_slot(device) << 15) | (pci_extract_func(device) << 12) | (field);\n}\n\nuintptr_t pcie_ecam_phys = 0x3f000000;\n\n/**\n * @brief Write to a PCI device configuration space field.\n */\nvoid pci_write_field(uint32_t device, int field, int size, uint32_t value) {\n#ifdef __x86_64__\n\toutportl(PCI_ADDRESS_PORT, pci_get_addr(device, field));\n\toutportl(PCI_VALUE_PORT, value);\n#else\n\n\t/* ECAM space */\n\tif (size == 4) {\n\t\t*(volatile uint32_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value;\n\t\treturn;\n\t} else if (size == 2) {\n\t\t*(volatile uint16_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value;\n\t\treturn;\n\t} else if (size == 1) {\n\t\t*(volatile uint8_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value;\n\t\treturn;\n\t}\n\n\tdprintf(\"rejected invalid field write\\n\");\n\n#endif\n}\n\n/**\n * @brief Read from a PCI device configuration space field.\n */\nuint32_t pci_read_field(uint32_t device, int field, int size) {\n#ifdef __x86_64__\n\toutportl(PCI_ADDRESS_PORT, pci_get_addr(device, field));\n\n\tif (size == 4) {\n\t\tuint32_t t = inportl(PCI_VALUE_PORT);\n\t\treturn t;\n\t} else if (size == 2) {\n\t\tuint16_t t = inports(PCI_VALUE_PORT + (field & 2));\n\t\treturn t;\n\t} else if (size == 1) {\n\t\tuint8_t t = inportb(PCI_VALUE_PORT + (field & 3));\n\t\treturn t;\n\t}\n#else\n\tuintptr_t field_addr = pcie_addr(device,field);\n\tif (size == 4) {\n\t\treturn *(volatile uint32_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr);\n\t} else if (size == 2) {\n\t\treturn *(volatile uint16_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr);\n\t} else if (size == 1) {\n\t\treturn *(volatile uint8_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr);\n\t}\n#endif\n\treturn 0xFFFF;\n}\n\n/**\n * @brief Obtain the device type from the class and subclass fields.\n */\nuint16_t pci_find_type(uint32_t dev) {\n\treturn (pci_read_field(dev, PCI_CLASS, 1) << 8) | pci_read_field(dev, PCI_SUBCLASS, 1);\n}\n\nstatic void pci_scan_hit(pci_func_t f, uint32_t dev, void * extra) {\n\tint dev_vend = (int)pci_read_field(dev, PCI_VENDOR_ID, 2);\n\tint dev_dvid = (int)pci_read_field(dev, PCI_DEVICE_ID, 2);\n\n\tf(dev, dev_vend, dev_dvid, extra);\n}\n\nvoid pci_scan_func(pci_func_t f, int type, int bus, int slot, int func, void * extra) {\n\tuint32_t dev = pci_box_device(bus, slot, func);\n\tif (type == -1 || type == pci_find_type(dev)) {\n\t\tpci_scan_hit(f, dev, extra);\n\t}\n\tif (pci_find_type(dev) == PCI_TYPE_BRIDGE) {\n\t\tpci_scan_bus(f, type, pci_read_field(dev, PCI_SECONDARY_BUS, 1), extra);\n\t}\n}\n\nvoid pci_scan_slot(pci_func_t f, int type, int bus, int slot, void * extra) {\n\tuint32_t dev = pci_box_device(bus, slot, 0);\n\tif (pci_read_field(dev, PCI_VENDOR_ID, 2) == PCI_NONE) {\n\t\treturn;\n\t}\n\tpci_scan_func(f, type, bus, slot, 0, extra);\n\tif (!pci_read_field(dev, PCI_HEADER_TYPE, 1)) {\n\t\treturn;\n\t}\n\tfor (int func = 1; func < 8; func++) {\n\t\tuint32_t dev = pci_box_device(bus, slot, func);\n\t\tif (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) {\n\t\t\tpci_scan_func(f, type, bus, slot, func, extra);\n\t\t}\n\t}\n}\n\nvoid pci_scan_bus(pci_func_t f, int type, int bus, void * extra) {\n\tfor (int slot = 0; slot < 32; ++slot) {\n\t\tpci_scan_slot(f, type, bus, slot, extra);\n\t}\n}\n\n/**\n * @brief Scan PCI buses for devices, calling the given function for each device.\n *\n * Used by drivers to implement device discovery, runs a callback function for ever\n * device found. A device consists of a bus, slot, and function. Also performs\n * recursive scans of bridges.\n */\nvoid pci_scan(pci_func_t f, int type, void * extra) {\n\tif ((pci_read_field(0, PCI_HEADER_TYPE, 1) & 0x80) == 0) {\n\t\tpci_scan_bus(f,type,0,extra);\n\t\treturn;\n\t}\n\n\tint hit = 0;\n\tfor (int func = 0; func < 8; ++func) {\n\t\tuint32_t dev = pci_box_device(0, 0, func);\n\t\tif (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) {\n\t\t\thit = 1;\n\t\t\tpci_scan_bus(f, type, func, extra);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!hit) {\n\t\tfor (int bus = 0; bus < 256; ++bus) {\n\t\t\tfor (int slot = 0; slot < 32; ++slot) {\n\t\t\t\tpci_scan_slot(f,type,bus,slot,extra);\n\t\t\t}\n\t\t}\n\t}\n}\n\nint pci_get_interrupt(uint32_t device) {\n\treturn pci_read_field(device, PCI_INTERRUPT_LINE, 1);\n}\n"
  },
  {
    "path": "kernel/misc/ringbuffer.c",
    "content": "/**\n * @file kernel/misc/ringbuffer.c\n * @brief Generic ringbuffer with blocking reads and writes.\n *\n * Provides a buffer interface for devices such as at PTYs with\n * blocking reads and writes.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/ringbuffer.h>\n#include <kernel/process.h>\n#include <kernel/spinlock.h>\n#include <kernel/string.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/mmu.h>\n\nsize_t ring_buffer_unread(ring_buffer_t * ring_buffer) {\n\tif (ring_buffer->read_ptr == ring_buffer->write_ptr) {\n\t\treturn 0;\n\t}\n\tif (ring_buffer->read_ptr > ring_buffer->write_ptr) {\n\t\treturn (ring_buffer->size - ring_buffer->read_ptr) + ring_buffer->write_ptr;\n\t} else {\n\t\treturn (ring_buffer->write_ptr - ring_buffer->read_ptr);\n\t}\n}\n\nsize_t ring_buffer_size(fs_node_t * node) {\n\tring_buffer_t * ring_buffer = (ring_buffer_t *)node->device;\n\treturn ring_buffer_unread(ring_buffer);\n}\n\nsize_t ring_buffer_available(ring_buffer_t * ring_buffer) {\n\tif (ring_buffer->read_ptr == ring_buffer->write_ptr) {\n\t\treturn ring_buffer->size - 1;\n\t}\n\n\tif (ring_buffer->read_ptr > ring_buffer->write_ptr) {\n\t\treturn ring_buffer->read_ptr - ring_buffer->write_ptr - 1;\n\t} else {\n\t\treturn (ring_buffer->size - ring_buffer->write_ptr) + ring_buffer->read_ptr - 1;\n\t}\n}\n\nstatic inline void ring_buffer_increment_read(ring_buffer_t * ring_buffer) {\n\tring_buffer->read_ptr++;\n\tif (ring_buffer->read_ptr == ring_buffer->size) {\n\t\tring_buffer->read_ptr = 0;\n\t}\n}\n\nstatic inline void ring_buffer_increment_write(ring_buffer_t * ring_buffer) {\n\tring_buffer->write_ptr++;\n\tif (ring_buffer->write_ptr == ring_buffer->size) {\n\t\tring_buffer->write_ptr = 0;\n\t}\n}\n\nvoid ring_buffer_alert_waiters(ring_buffer_t * ring_buffer) {\n\tif (ring_buffer->alert_waiters) {\n\t\twhile (ring_buffer->alert_waiters->head) {\n\t\t\tnode_t * node = list_dequeue(ring_buffer->alert_waiters);\n\t\t\tprocess_t * p = node->value;\n\t\t\tprocess_alert_node(p, ring_buffer);\n\t\t\tfree(node);\n\t\t}\n\t}\n}\n\nvoid ring_buffer_select_wait(ring_buffer_t * ring_buffer, void * process) {\n\tif (!ring_buffer->alert_waiters) {\n\t\tring_buffer->alert_waiters = list_create(\"ringbuffer alerts\", ring_buffer);\n\t}\n\n\tif (!list_find(ring_buffer->alert_waiters, process)) {\n\t\tlist_insert(ring_buffer->alert_waiters, process);\n\t}\n\tlist_insert(((process_t *)process)->node_waits, ring_buffer);\n}\n\nvoid ring_buffer_discard(ring_buffer_t * ring_buffer) {\n\tspin_lock(ring_buffer->lock);\n\tring_buffer->read_ptr = ring_buffer->write_ptr;\n\tspin_unlock(ring_buffer->lock);\n}\n\nsize_t ring_buffer_read(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) {\n\tsize_t collected = 0;\n\twhile (collected == 0) {\n\t\tspin_lock(ring_buffer->lock);\n\t\twhile (ring_buffer_unread(ring_buffer) > 0 && collected < size) {\n\t\t\tbuffer[collected] = ring_buffer->buffer[ring_buffer->read_ptr];\n\t\t\tring_buffer_increment_read(ring_buffer);\n\t\t\tcollected++;\n\t\t}\n\t\twakeup_queue(ring_buffer->wait_queue_writers);\n\t\tif (collected == 0) {\n\t\t\tif (ring_buffer->internal_stop || ring_buffer->soft_stop) {\n\t\t\t\tring_buffer->soft_stop = 0;\n\t\t\t\tspin_unlock(ring_buffer->lock);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tif (sleep_on_unlocking(ring_buffer->wait_queue_readers, &ring_buffer->lock)) {\n\t\t\t\treturn -ERESTARTSYS;\n\t\t\t}\n\t\t} else {\n\t\t\tspin_unlock(ring_buffer->lock);\n\t\t}\n\t}\n\twakeup_queue(ring_buffer->wait_queue_writers);\n\treturn collected;\n}\n\nsize_t ring_buffer_write(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) {\n\tsize_t written = 0;\n\twhile (written < size) {\n\t\tspin_lock(ring_buffer->lock);\n\n\t\twhile (ring_buffer_available(ring_buffer) > 0 && written < size) {\n\t\t\tring_buffer->buffer[ring_buffer->write_ptr] = buffer[written];\n\t\t\tring_buffer_increment_write(ring_buffer);\n\t\t\twritten++;\n\t\t}\n\n\t\twakeup_queue(ring_buffer->wait_queue_readers);\n\t\tring_buffer_alert_waiters(ring_buffer);\n\t\tif (written < size) {\n\t\t\tif (ring_buffer->discard) {\n\t\t\t\tspin_unlock(ring_buffer->lock);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (sleep_on_unlocking(ring_buffer->wait_queue_writers, &ring_buffer->lock)) {\n\t\t\t\tif (!written) return -ERESTARTSYS;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (ring_buffer->internal_stop) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tspin_unlock(ring_buffer->lock);\n\t\t}\n\t}\n\n\twakeup_queue(ring_buffer->wait_queue_readers);\n\tring_buffer_alert_waiters(ring_buffer);\n\treturn written;\n}\n\nring_buffer_t * ring_buffer_create(size_t size) {\n\tring_buffer_t * out = malloc(sizeof(ring_buffer_t));\n\n\tif (size == 4096) {\n\t\tout->buffer = mmu_map_from_physical(mmu_allocate_a_frame() << 12);\n\t} else {\n\t\tout->buffer     = malloc(size);\n\t}\n\tout->write_ptr  = 0;\n\tout->read_ptr   = 0;\n\tout->size       = size;\n\tout->alert_waiters = NULL;\n\n\tspin_init(out->lock);\n\n\tout->internal_stop = 0;\n\tout->discard = 0;\n\tout->soft_stop = 0;\n\n\tout->wait_queue_readers = list_create(\"ringbuffer readers\",out);\n\tout->wait_queue_writers = list_create(\"ringbuffer writers\",out);\n\n\treturn out;\n}\n\nvoid ring_buffer_destroy(ring_buffer_t * ring_buffer) {\n\tif (ring_buffer->size == 4096) {\n\t\tmmu_frame_release((uintptr_t)ring_buffer->buffer & 0xFFFFFFFFF);\n\t} else {\n\t\tfree(ring_buffer->buffer);\n\t}\n\n\twakeup_queue(ring_buffer->wait_queue_writers);\n\twakeup_queue(ring_buffer->wait_queue_readers);\n\tring_buffer_alert_waiters(ring_buffer);\n\n\tlist_free(ring_buffer->wait_queue_writers);\n\tlist_free(ring_buffer->wait_queue_readers);\n\n\tfree(ring_buffer->wait_queue_writers);\n\tfree(ring_buffer->wait_queue_readers);\n\n\tif (ring_buffer->alert_waiters) {\n\t\tlist_free(ring_buffer->alert_waiters);\n\t\tfree(ring_buffer->alert_waiters);\n\t}\n}\n\nvoid ring_buffer_interrupt(ring_buffer_t * ring_buffer) {\n\tring_buffer->internal_stop = 1;\n\twakeup_queue(ring_buffer->wait_queue_readers);\n\twakeup_queue(ring_buffer->wait_queue_writers);\n}\n\nvoid ring_buffer_eof(ring_buffer_t * ring_buffer) {\n\tring_buffer->soft_stop = 1;\n\twakeup_queue(ring_buffer->wait_queue_readers);\n\twakeup_queue(ring_buffer->wait_queue_writers);\n}\n\n"
  },
  {
    "path": "kernel/misc/string.c",
    "content": "/**\n * @file  kernel/misc/string.c\n * @brief Generic string functions and C standard library implementations for the kernel.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2021 K. Lange\n * Copyright (C) 2015      Dale Weiler\n */\n#include <kernel/types.h>\n#include <kernel/string.h>\n\n#define MAX(a,b) ((a) > (b) ? (a) : (b))\n\n#define BITOP(A, B, OP) \\\n ((A)[(size_t)(B)/(8*sizeof *(A))] OP (size_t)1<<((size_t)(B)%(8*sizeof *(A))))\n\nunsigned short * memsetw(unsigned short * dest, unsigned short val, int count) {\n\tint i = 0;\n\tfor ( ; i < count; ++i ) {\n\t\tdest[i] = val;\n\t}\n\treturn dest;\n}\n\n#if 1\nvoid * memcpy(void * restrict dest, const void * restrict src, size_t n) {\n\tuint64_t * d_64 = dest;\n\tconst uint64_t * s_64 = src;\n\n\tfor (; n >= 8; n -= 8) {\n\t\t*d_64++ = *s_64++;\n\t}\n\n\tuint32_t * d_32 = (void*)d_64;\n\tconst uint32_t * s_32 = (const void*)s_64;\n\n\tfor (; n >= 4; n -= 4) {\n\t\t*d_32++ = *s_32++;\n\t}\n\n\tuint8_t * d = (void*)d_32;\n\tconst uint8_t * s = (const void*)s_32;\n\n\tfor (; n > 0; n--) {\n\t\t*d++ = *s++;\n\t}\n\n\treturn dest;\n}\n#else\n/* FIXME why is there an x86-specific memcpy outside of the arch dir... */\nvoid * memcpy(void * restrict dest, const void * restrict src, size_t n) {\n\tasm volatile(\"rep movsb\"\n\t            : : \"D\"(dest), \"S\"(src), \"c\"(n)\n\t            : \"flags\", \"memory\");\n\treturn dest;\n}\n#endif\n\n\nsize_t strlen(const char * s) {\n\tconst char * a = s;\n\tconst size_t * w;\n\tfor (; (uintptr_t)s % ALIGN; s++) {\n\t\tif (!*s) {\n\t\t\treturn s-a;\n\t\t}\n\t}\n\tfor (w = (const void *)s; !HASZERO(*w); w++);\n\tfor (s = (const void *)w; *s; s++);\n\treturn s-a;\n}\n\n\nint strcmp(const char * a, const char * b) {\n\tuint32_t i = 0;\n\twhile (1) {\n\t\tif (a[i] < b[i]) {\n\t\t\treturn -1;\n\t\t} else if (a[i] > b[i]) {\n\t\t\treturn 1;\n\t\t} else {\n\t\t\tif (a[i] == '\\0') {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\t++i;\n\t\t}\n\t}\n}\n\nvoid * memset(void * dest, int c, size_t n) {\n\tsize_t i = 0;\n\tfor ( ; i < n; ++i ) {\n\t\t((char *)dest)[i] = c;\n\t}\n\treturn dest;\n}\n\nvoid * memmove(void * dest, const void * src, size_t n) {\n\tchar * d = dest;\n\tconst char * s = src;\n\n\tif (d==s) {\n\t\treturn d;\n\t}\n\n\tif (s+n <= d || d+n <= s) {\n\t\treturn memcpy(d, s, n);\n\t}\n\n\tif (d<s) {\n\t\tif ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) {\n\t\t\twhile ((uintptr_t)d % sizeof(size_t)) {\n\t\t\t\tif (!n--) {\n\t\t\t\t\treturn dest;\n\t\t\t\t}\n\t\t\t\t*d++ = *s++;\n\t\t\t}\n\t\t\tfor (; n >= sizeof(size_t); n -= sizeof(size_t), d += sizeof(size_t), s += sizeof(size_t)) {\n\t\t\t\t*(size_t *)d = *(size_t *)s;\n\t\t\t}\n\t\t}\n\t\tfor (; n; n--) {\n\t\t\t*d++ = *s++;\n\t\t}\n\t} else {\n\t\tif ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) {\n\t\t\twhile ((uintptr_t)(d+n) % sizeof(size_t)) {\n\t\t\t\tif (!n--) {\n\t\t\t\t\treturn dest;\n\t\t\t\t}\n\t\t\t\td[n] = s[n];\n\t\t\t}\n\t\t\twhile (n >= sizeof(size_t)) {\n\t\t\t\tn -= sizeof(size_t);\n\t\t\t\t*(size_t *)(d+n) = *(size_t *)(s+n);\n\t\t\t}\n\t\t}\n\t\twhile (n) {\n\t\t\tn--;\n\t\t\td[n] = s[n];\n\t\t}\n\t}\n\n\treturn dest;\n}\n\nvoid * memchr(const void * src, int c, size_t n) {\n\tconst unsigned char * s = src;\n\tc = (unsigned char)c;\n\tfor (; ((uintptr_t)s & (ALIGN - 1)) && n && *s != c; s++, n--);\n\tif (n && *s != c) {\n\t\tconst size_t * w;\n\t\tsize_t k = ONES * c;\n\t\tfor (w = (const void *)s; n >= sizeof(size_t) && !HASZERO(*w^k); w++, n -= sizeof(size_t));\n\t\tfor (s = (const void *)w; n && *s != c; s++, n--);\n\t}\n\treturn n ? (void *)s : 0;\n}\n\n\nvoid * memrchr(const void * m, int c, size_t n) {\n\tconst unsigned char * s = m;\n\tc = (unsigned char)c;\n\twhile (n--) {\n\t\tif (s[n] == c) {\n\t\t\treturn (void*)(s+n);\n\t\t}\n\t}\n\treturn 0;\n}\n\nsize_t strspn(const char * s, const char * c) {\n\tconst char * a = s;\n\tsize_t byteset[32/sizeof(size_t)] = { 0 };\n\n\tif (!c[0]) {\n\t\treturn 0;\n\t}\n\tif (!c[1]) {\n\t\tfor (; *s == *c; s++);\n\t\treturn s-a;\n\t}\n\n\tfor (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++);\n\tfor (; *s && BITOP(byteset, *(unsigned char *)s, &); s++);\n\n\treturn s-a;\n}\n\n\nchar * strchrnul(const char * s, int c) {\n\tsize_t * w;\n\tsize_t k;\n\n\tc = (unsigned char)c;\n\tif (!c) {\n\t\treturn (char *)s + strlen(s);\n\t}\n\n\tfor (; (uintptr_t)s % ALIGN; s++) {\n\t\tif (!*s || *(unsigned char *)s == c) {\n\t\t\treturn (char *)s;\n\t\t}\n\t}\n\n\tk = ONES * c;\n\tfor (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++);\n\tfor (s = (void *)w; *s && *(unsigned char *)s != c; s++);\n\treturn (char *)s;\n}\n\nchar * strchr(const char * s, int c) {\n\tchar *r = strchrnul(s, c);\n\treturn *(unsigned char *)r == (unsigned char)c ? r : 0;\n}\n\nchar * strrchr(const char * s, int c) {\n\treturn memrchr(s, c, strlen(s) + 1);\n}\n\n\nchar * stpcpy(char * restrict d, const char * restrict s) {\n\tsize_t * wd;\n\tconst size_t * ws;\n\n\tif ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) {\n\t\tfor (; (uintptr_t)s % ALIGN; s++, d++) {\n\t\t\tif (!(*d = *s)) {\n\t\t\t\treturn d;\n\t\t\t}\n\t\t}\n\t\twd = (void *)d;\n\t\tws = (const void *)s;\n\t\tfor (; !HASZERO(*ws); *wd++ = *ws++);\n\t\td = (void *)wd;\n\t\ts = (const void *)ws;\n\t}\n\n\tfor (; (*d=*s); s++, d++);\n\n\treturn d;\n}\n\nchar * strcpy(char * restrict dest, const char * restrict src) {\n\tstpcpy(dest, src);\n\treturn dest;\n}\n\nsize_t lfind(const char * str, const char accept) {\n\treturn (size_t)strchr(str, accept);\n}\n\nsize_t rfind(const char * str, const char accept) {\n\treturn (size_t)strrchr(str, accept);\n}\n\nsize_t strcspn(const char * s, const char * c) {\n\tconst char *a = s;\n\tif (c[0] && c[1]) {\n\t\tsize_t byteset[32/sizeof(size_t)] = { 0 };\n\t\tfor (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++);\n\t\tfor (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++);\n\t\treturn s-a;\n\t}\n\treturn strchrnul(s, *c)-a;\n}\n\nchar * strpbrk(const char * s, const char * b) {\n\ts += strcspn(s, b);\n\treturn *s ? (char *)s : 0;\n}\n\nchar * strtok_r(char * str, const char * delim, char ** saveptr) {\n\tchar * token;\n\tif (str == NULL) {\n\t\tstr = *saveptr;\n\t}\n\tstr += strspn(str, delim);\n\tif (*str == '\\0') {\n\t\t*saveptr = str;\n\t\treturn NULL;\n\t}\n\ttoken = str;\n\tstr = strpbrk(token, delim);\n\tif (str == NULL) {\n\t\t*saveptr = (char *)lfind(token, '\\0');\n\t} else {\n\t\t*str = '\\0';\n\t\t*saveptr = str + 1;\n\t}\n\treturn token;\n}\n\nstatic char *strstr_2b(const unsigned char * h, const unsigned char * n) {\n\tuint16_t nw = n[0] << 8 | n[1];\n\tuint16_t hw = h[0] << 8 | h[1];\n\tfor (h++; *h && hw != nw; hw = hw << 8 | *++h);\n\treturn *h ? (char *)h-1 : 0;\n}\n\nstatic char *strstr_3b(const unsigned char * h, const unsigned char * n) {\n\tuint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8;\n\tuint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8;\n\tfor (h += 2; *h && hw != nw; hw = (hw|*++h) << 8);\n\treturn *h ? (char *)h-2 : 0;\n}\n\nstatic char *strstr_4b(const unsigned char * h, const unsigned char * n) {\n\tuint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8 | n[3];\n\tuint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3];\n\tfor (h += 3; *h && hw != nw; hw = hw << 8 | *++h);\n\treturn *h ? (char *)h-3 : 0;\n}\n\nint memcmp(const void * vl, const void * vr, size_t n) {\n\tconst unsigned char *l = vl;\n\tconst unsigned char *r = vr;\n\tfor (; n && *l == *r; n--, l++, r++);\n\treturn n ? *l-*r : 0;\n}\n\nstatic char *strstr_twoway(const unsigned char * h, const unsigned char * n) {\n\tsize_t mem;\n\tsize_t mem0;\n\tsize_t byteset[32 / sizeof(size_t)] = { 0 };\n\tsize_t shift[256];\n\tsize_t l;\n\n\t/* Computing length of needle and fill shift table */\n\tfor (l = 0; n[l] && h[l]; l++) {\n\t\tBITOP(byteset, n[l], |=);\n\t\tshift[n[l]] = l+1;\n\t}\n\n\tif (n[l]) {\n\t\treturn 0; /* hit the end of h */\n\t}\n\n\t/* Compute maximal suffix */\n\tsize_t ip = -1;\n\tsize_t jp = 0;\n\tsize_t k = 1;\n\tsize_t p = 1;\n\twhile (jp+k<l) {\n\t\tif (n[ip+k] == n[jp+k]) {\n\t\t\tif (k == p) {\n\t\t\t\tjp += p;\n\t\t\t\tk = 1;\n\t\t\t} else {\n\t\t\t\tk++;\n\t\t\t}\n\t\t} else if (n[ip+k] > n[jp+k]) {\n\t\t\tjp += k;\n\t\t\tk = 1;\n\t\t\tp = jp - ip;\n\t\t} else {\n\t\t\tip = jp++;\n\t\t\tk = p = 1;\n\t\t}\n\t}\n\tsize_t ms = ip;\n\tsize_t p0 = p;\n\n\t/* And with the opposite comparison */\n\tip = -1;\n\tjp = 0;\n\tk = p = 1;\n\twhile (jp+k<l) {\n\t\tif (n[ip+k] == n[jp+k]) {\n\t\t\tif (k == p) {\n\t\t\t\tjp += p;\n\t\t\t\tk = 1;\n\t\t\t} else {\n\t\t\t\tk++;\n\t\t\t}\n\t\t} else if (n[ip+k] < n[jp+k]) {\n\t\t\tjp += k;\n\t\t\tk = 1;\n\t\t\tp = jp - ip;\n\t\t} else {\n\t\t\tip = jp++;\n\t\t\tk = p = 1;\n\t\t}\n\t}\n\tif (ip+1 > ms+1) {\n\t\tms = ip;\n\t} else {\n\t\tp = p0;\n\t}\n\n\t/* Periodic needle? */\n\tif (memcmp(n, n+p, ms+1)) {\n\t\tmem0 = 0;\n\t\tp = MAX(ms, l-ms-1) + 1;\n\t} else {\n\t\tmem0 = l-p;\n\t}\n\tmem = 0;\n\n\t/* Initialize incremental end-of-haystack pointer */\n\tconst unsigned char * z = h;\n\n\t/* Search loop */\n\tfor (;;) {\n\t\t/* Update incremental end-of-haystack pointer */\n\t\tif ((size_t)(z-h) < l) {\n\t\t\t/* Fast estimate for MIN(l,63) */\n\t\t\tsize_t grow = l | 63;\n\t\t\tconst unsigned char *z2 = memchr(z, 0, grow);\n\t\t\tif (z2) {\n\t\t\t\tz = z2;\n\t\t\t\tif ((size_t)(z-h) < l) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tz += grow;\n\t\t\t}\n\t\t}\n\n\t\t/* Check last byte first; advance by shift on mismatch */\n\t\tif (BITOP(byteset, h[l-1], &)) {\n\t\t\tk = l-shift[h[l-1]];\n\t\t\tif (k) {\n\t\t\t\tif (mem0 && mem && k < p) k = l-p;\n\t\t\t\th += k;\n\t\t\t\tmem = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\th += l;\n\t\t\tmem = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Compare right half */\n\t\tfor (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++);\n\t\tif (n[k]) {\n\t\t\th += k-ms;\n\t\t\tmem = 0;\n\t\t\tcontinue;\n\t\t}\n\t\t/* Compare left half */\n\t\tfor (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);\n\t\tif (k <= mem) {\n\t\t\treturn (char *)h;\n\t\t}\n\t\th += p;\n\t\tmem = mem0;\n\t}\n}\n\nchar *strstr(const char * h, const char * n) {\n\t/* Return immediately on empty needle */\n\tif (!n[0]) {\n\t\treturn (char *)h;\n\t}\n\n\t/* Use faster algorithms for short needles */\n\th = strchr(h, *n);\n\tif (!h || !n[1]) {\n\t\treturn (char *)h;\n\t}\n\n\tif (!h[1]) return 0;\n\tif (!n[2]) return strstr_2b((void *)h, (void *)n);\n\tif (!h[2]) return 0;\n\tif (!n[3]) return strstr_3b((void *)h, (void *)n);\n\tif (!h[3]) return 0;\n\tif (!n[4]) return strstr_4b((void *)h, (void *)n);\n\n\t/* Two-way on large needles */\n\treturn strstr_twoway((void *)h, (void *)n);\n}\n\nuint8_t startswith(const char * str, const char * accept) {\n\treturn strstr(str, accept) == str;\n}\n\nchar * strdup(const char * c) {\n\tchar * out = malloc(strlen(c) + 1);\n\tmemcpy(out, c, strlen(c)+1);\n\treturn out;\n}\n\nint atoi(const char * c) {\n\tint sign = 1;\n\tlong out = 0;\n\tif (*c == '-') {\n\t\tsign = -1;\n\t\tc++;\n\t}\n\n\twhile (*c) {\n\t\tout *= 10;\n\t\tout += (*c - '0');\n\t\tc++;\n\t}\n\n\treturn out * sign;\n}\n\n"
  },
  {
    "path": "kernel/misc/tokenize.c",
    "content": "/**\n * @file  kernel/misc/tokenize.c\n * @brief Wrapper around strtok_r, used to turn strings into arrays.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015-2021 K. Lange\n */\n#include <kernel/string.h>\n#include <kernel/tokenize.h>\n\nint tokenize(char * str, const char * sep, char **buf) {\n\tchar * pch_i;\n\tchar * save_i;\n\tint    argc = 0;\n\tpch_i = strtok_r(str,sep,&save_i);\n\tif (!pch_i) { return 0; }\n\twhile (pch_i != NULL) {\n\t\tbuf[argc] = (char *)pch_i;\n\t\t++argc;\n\t\tpch_i = strtok_r(NULL,sep,&save_i);\n\t}\n\tbuf[argc] = NULL;\n\treturn argc;\n}\n"
  },
  {
    "path": "kernel/misc/tree.c",
    "content": "/**\n * @file  kernel/misc/tree.c\n * @brief General-purpose tree implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2018 K. Lange\n */\n\n#include <stddef.h>\n#include <stdint.h>\n#include <kernel/string.h>\n#include <kernel/tree.h>\n\ntree_t * tree_create(void) {\n\t/* Create a new tree */\n\ttree_t * out = malloc(sizeof(tree_t));\n\tout->nodes  = 0;\n\tout->root   = NULL;\n\treturn out;\n}\n\nvoid tree_set_root(tree_t * tree, void * value) {\n\t/* Set the root node for a new tree. */\n\ttree_node_t * root = tree_node_create(value);\n\ttree->root = root;\n\ttree->nodes = 1;\n}\n\nvoid tree_node_destroy(tree_node_t * node) {\n\t/* Free the contents of a node and its children, but not the nodes themselves */\n\tforeach(child, node->children) {\n\t\ttree_node_destroy((tree_node_t *)child->value);\n\t}\n\tfree(node->value);\n}\n\nvoid tree_destroy(tree_t * tree) {\n\t/* Free the contents of a tree, but not the nodes */\n\tif (tree->root) {\n\t\ttree_node_destroy(tree->root);\n\t}\n}\n\nvoid tree_node_free(tree_node_t * node) {\n\t/* Free a node and its children, but not their contents */\n\tif (!node) return;\n\tforeach(child, node->children) {\n\t\ttree_node_free(child->value);\n\t}\n\tfree(node);\n}\n\nvoid tree_free(tree_t * tree) {\n\t/* Free all of the nodes in a tree, but not their contents */\n\ttree_node_free(tree->root);\n}\n\ntree_node_t * tree_node_create(void * value) {\n\t/* Create a new tree node pointing to the given value */\n\ttree_node_t * out = malloc(sizeof(tree_node_t));\n\tout->value = value;\n\tout->children = list_create(\"tree node children\",out);\n\tout->parent = NULL;\n\treturn out;\n}\n\nvoid tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node) {\n\t/* Insert a node as a child of parent */\n\tlist_insert(parent->children, node);\n\tnode->parent = parent;\n\ttree->nodes++;\n}\n\ntree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value) {\n\t/* Insert a (fresh) node as a child of parent */\n\ttree_node_t * out = tree_node_create(value);\n\ttree_node_insert_child_node(tree, parent, out);\n\treturn out;\n}\n\ntree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle) {\n\t/* Recursive node part of tree_find_parent */\n\ttree_node_t * found = NULL;\n\tforeach(child, haystack->children) {\n\t\tif (child->value == needle) {\n\t\t\treturn haystack;\n\t\t}\n\t\tfound = tree_node_find_parent((tree_node_t *)child->value, needle);\n\t\tif (found) {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn found;\n}\n\ntree_node_t * tree_find_parent(tree_t * tree, tree_node_t * node) {\n\t/* Return the parent of a node, inefficiently. */\n\tif (!tree->root) return NULL;\n\treturn tree_node_find_parent(tree->root, node);\n}\n\nsize_t tree_count_children(tree_node_t * node) {\n\t/* return the number of children this node has */\n\tif (!node) return 0;\n\tif (!node->children) return 0;\n\tsize_t out = node->children->length;\n\tforeach(child, node->children) {\n\t\tout += tree_count_children((tree_node_t *)child->value);\n\t}\n\treturn out;\n}\n\nvoid tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node) {\n\t/* remove a node when we know its parent; update node counts for the tree */\n\ttree->nodes -= tree_count_children(node) + 1;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\ttree_node_free(node);\n}\n\nvoid tree_node_remove(tree_t * tree, tree_node_t * node) {\n\t/* remove an entire branch given its root */\n\ttree_node_t * parent = node->parent;\n\tif (!parent) {\n\t\tif (node == tree->root) {\n\t\t\ttree->nodes = 0;\n\t\t\ttree->root  = NULL;\n\t\t\ttree_node_free(node);\n\t\t}\n\t}\n\ttree_node_parent_remove(tree, parent, node);\n}\n\nvoid tree_remove(tree_t * tree, tree_node_t * node) {\n\t/* Remove this node and move its children into its parent's list of children */\n\ttree_node_t * parent = node->parent;\n\t/* This is something we just can't do. We don't know how to merge our\n\t * children into our \"parent\" because then we'd have more than one root node.\n\t * A good way to think about this is actually what this tree struct\n\t * primarily exists for: processes. Trying to remove the root is equivalent\n\t * to trying to kill init! Which is bad. We immediately fault on such\n\t * a case anyway (\"Tried to kill init, shutting down!\").\n\t */\n\tif (!parent) return;\n\ttree->nodes--;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\tforeach(child, node->children) {\n\t\t/* Reassign the parents */\n\t\t((tree_node_t *)child->value)->parent = parent;\n\t}\n\tlist_merge(parent->children, node->children);\n\tfree(node);\n}\n\nvoid tree_remove_reparent_root(tree_t * tree, tree_node_t * node) {\n\t/* Remove this node and move its children into the root children */\n\ttree_node_t * parent = node->parent;\n\tif (!parent) return;\n\ttree->nodes--;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\tforeach(child, node->children) {\n\t\t/* Reassign the parents */\n\t\t((tree_node_t *)child->value)->parent = tree->root;\n\t}\n\tlist_merge(tree->root->children, node->children);\n\tfree(node);\n}\n\nvoid tree_break_off(tree_t * tree, tree_node_t * node) {\n\ttree_node_t * parent = node->parent;\n\tif (!parent) return;\n\tlist_delete(parent->children, list_find(parent->children, node));\n}\n\ntree_node_t * tree_node_find(tree_node_t * node, void * search, tree_comparator_t comparator) {\n\tif (comparator(node->value,search)) {\n\t\treturn node;\n\t}\n\ttree_node_t * found;\n\tforeach(child, node->children) {\n\t\tfound = tree_node_find((tree_node_t *)child->value, search, comparator);\n\t\tif (found) return found;\n\t}\n\treturn NULL;\n}\n\ntree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator) {\n\treturn tree_node_find(tree->root, value, comparator);\n}\n"
  },
  {
    "path": "kernel/net/arp.c",
    "content": "/**\n * @file  kernel/net/arp.c\n * @brief Address resolution\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/syscall.h>\n#include <kernel/vfs.h>\n#include <kernel/hashmap.h>\n#include <kernel/net/netif.h>\n#include <kernel/net/eth.h>\n\n#include <sys/socket.h>\n\n#ifndef MISAKA_DEBUG_NET\n#define printf(...)\n#endif\n\nstruct arp_header {\n\tuint16_t arp_htype;\n\tuint16_t arp_ptype;\n\tuint8_t  arp_hlen;\n\tuint8_t  arp_plen;\n\tuint16_t arp_oper;\n\tunion {\n\t\tstruct {\n\t\t\tuint8_t  arp_sha[6];\n\t\t\tuint32_t arp_spa;\n\t\t\tuint8_t arp_tha[6];\n\t\t\tuint32_t arp_tpa;\n\t\t} __attribute__((packed)) arp_eth_ipv4;\n\t} arp_data;\n} __attribute__((packed));\n\nstatic void ip_ntoa(const uint32_t src_addr, char * out) {\n\tsnprintf(out, 16, \"%d.%d.%d.%d\",\n\t\t(src_addr & 0xFF000000) >> 24,\n\t\t(src_addr & 0xFF0000) >> 16,\n\t\t(src_addr & 0xFF00) >> 8,\n\t\t(src_addr & 0xFF));\n}\n\nspin_lock_t net_arp_cache_lock = {0};\nhashmap_t * net_arp_cache = NULL;\n\nvoid net_arp_cache_add(struct EthernetDevice * iface, uint32_t addr, uint8_t * hwaddr, uint16_t flags) {\n\tspin_lock(net_arp_cache_lock);\n\tstruct ArpCacheEntry * entry = hashmap_get(net_arp_cache, (void*)(uintptr_t)addr);\n\tif (!entry) entry = malloc(sizeof(struct ArpCacheEntry));\n\tmemcpy(entry->hwaddr, hwaddr, 6);\n\tentry->flags = flags;\n\tentry->iface = iface;\n\thashmap_set(net_arp_cache, (void*)(uintptr_t)addr, entry);\n\tspin_unlock(net_arp_cache_lock);\n}\n\nstruct ArpCacheEntry * net_arp_cache_get(uint32_t addr) {\n\tspin_lock(net_arp_cache_lock);\n\tstruct ArpCacheEntry * out = hashmap_get(net_arp_cache, (void*)(uintptr_t)addr);\n\tspin_unlock(net_arp_cache_lock);\n\treturn out;\n}\n\nvoid net_arp_ask(uint32_t addr, fs_node_t * fsnic) {\n\tstruct EthernetDevice * ethnic = fsnic->device;\n\tstruct arp_header arp_request = {0};\n\n\tarp_request.arp_htype = htons(1); /* Ethernet */\n\tarp_request.arp_ptype = htons(ETHERNET_TYPE_IPV4);\n\tarp_request.arp_hlen  = 6;\n\tarp_request.arp_plen  = 4;\n\tarp_request.arp_oper  = htons(1); /* Who is...? */\n\tarp_request.arp_data.arp_eth_ipv4.arp_tpa = addr;\n\tmemcpy(arp_request.arp_data.arp_eth_ipv4.arp_sha, ethnic->mac, 6);\n\n\tif (ethnic->ipv4_addr) {\n\t\tarp_request.arp_data.arp_eth_ipv4.arp_spa = ethnic->ipv4_addr;\n\t}\n\n\tnet_eth_send(ethnic, sizeof(struct arp_header), &arp_request, ETHERNET_TYPE_ARP, ETHERNET_BROADCAST_MAC);\n}\n\nvoid net_arp_handle(struct arp_header * packet, fs_node_t * nic) {\n\tprintf(\"net: arp: hardware %d protocol %d operation %d hlen %d plen %d\\n\",\n\t\tntohs(packet->arp_htype), ntohs(packet->arp_ptype), ntohs(packet->arp_oper),\n\t\tpacket->arp_hlen, packet->arp_plen);\n\tstruct EthernetDevice * eth_dev = nic->device;\n\n\tif (ntohs(packet->arp_htype) == 1 && ntohs(packet->arp_ptype) == ETHERNET_TYPE_IPV4) {\n\t\t/* Ethernet, IPv4 */\n\t\tif (packet->arp_data.arp_eth_ipv4.arp_spa) {\n\t\t\tnet_arp_cache_add(eth_dev, packet->arp_data.arp_eth_ipv4.arp_spa, packet->arp_data.arp_eth_ipv4.arp_sha, 0);\n\t\t}\n\t\tif (ntohs(packet->arp_oper) == 1) {\n\t\t\tchar spa[17];\n\t\t\tip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_spa), spa);\n\t\t\tchar tpa[17];\n\t\t\tip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_tpa), tpa);\n\t\t\tprintf(\"net: arp: \" MAC_FORMAT \" (%s) wants to know who %s is\\n\",\n\t\t\t\tFORMAT_MAC(packet->arp_data.arp_eth_ipv4.arp_sha),\n\t\t\t\tspa, tpa);\n\t\t\tif (eth_dev->ipv4_addr &&  packet->arp_data.arp_eth_ipv4.arp_tpa == eth_dev->ipv4_addr) {\n\t\t\t\tprintf(\"net: arp: that's us, we should reply...\\n\");\n\n\t\t\t\tstruct arp_header response = {0};\n\t\t\t\tresponse.arp_htype = htons(1);\n\t\t\t\tresponse.arp_ptype = htons(ETHERNET_TYPE_IPV4);\n\t\t\t\tresponse.arp_hlen = 6;\n\t\t\t\tresponse.arp_plen = 4;\n\t\t\t\tresponse.arp_oper = htons(2);\n\t\t\t\tmemcpy(response.arp_data.arp_eth_ipv4.arp_sha, eth_dev->mac, 6);\n\t\t\t\tmemcpy(response.arp_data.arp_eth_ipv4.arp_tha, packet->arp_data.arp_eth_ipv4.arp_sha, 6);\n\t\t\t\tresponse.arp_data.arp_eth_ipv4.arp_spa = eth_dev->ipv4_addr;\n\t\t\t\tresponse.arp_data.arp_eth_ipv4.arp_tpa = packet->arp_data.arp_eth_ipv4.arp_spa;\n\t\t\t\tnet_eth_send(eth_dev, sizeof(struct arp_header), &response, ETHERNET_TYPE_ARP, packet->arp_data.arp_eth_ipv4.arp_sha);\n\t\t\t}\n\t\t} else if (ntohs(packet->arp_oper) == 2) {\n\t\t\tchar spa[17];\n\t\t\tip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_spa), spa);\n\t\t\tprintf(\"net: arp: \" MAC_FORMAT \" says they are %s\\n\",\n\t\t\t\tFORMAT_MAC(packet->arp_data.arp_eth_ipv4.arp_sha),\n\t\t\t\tspa);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "kernel/net/eth.c",
    "content": "/**\n * @file  kernel/net/eth.c\n * @brief Generic Ethernet frame management\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/pipe.h>\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n#include <kernel/vfs.h>\n#include <kernel/net/netif.h>\n#include <kernel/net/eth.h>\n#include <kernel/net/ipv4.h>\n#include <errno.h>\n\n#include <sys/socket.h>\n\n#ifndef MISAKA_DEBUG_NET\n#define printf(...)\n#endif\n\nextern spin_lock_t net_raw_sockets_lock;\nextern list_t * net_raw_sockets_list;\nextern void net_ipv4_handle(void * packet, fs_node_t * nic, size_t);\nextern void net_arp_handle(void * packet, fs_node_t * nic);\n\nvoid net_eth_handle(struct ethernet_packet * frame, fs_node_t * nic, size_t size) {\n\tstruct EthernetDevice * nic_eth = nic->device;\n\n\tif (size < sizeof(struct ethernet_packet)) {\n\t\tdprintf(\"eth: %s: invalid ethernet frame (too small)\\n\",\n\t\t\tnic_eth->if_name);\n\t\treturn;\n\t}\n\n\tspin_lock(net_raw_sockets_lock);\n\tforeach(node, net_raw_sockets_list) {\n\t\tsock_t * sock = node->value;\n\t\tif (!sock->_fnode.device || sock->_fnode.device == nic) {\n\t\t\tnet_sock_add(sock, frame, size);\n\t\t}\n\t}\n\tspin_unlock(net_raw_sockets_lock);\n\n\tif (!memcmp(frame->destination, nic_eth->mac, 6) || !memcmp(frame->destination, ETHERNET_BROADCAST_MAC, 6)) {\n\t\t/* Now pass the frame to the appropriate handler... */\n\t\tswitch (ntohs(frame->type)) {\n\t\t\tcase ETHERNET_TYPE_ARP:\n\t\t\t\tnet_arp_handle(&frame->payload, nic);\n\t\t\t\tbreak;\n\t\t\tcase ETHERNET_TYPE_IPV4: {\n\t\t\t\tstruct ipv4_packet * packet = (struct ipv4_packet*)&frame->payload;\n\t\t\t\tprintf(\"net: eth: %s: rx ipv4 packet\\n\", nic->name);\n\t\t\t\tif (packet->source != 0xFFFFFFFF) {\n\t\t\t\t\tnet_arp_cache_add(nic->device, packet->source, frame->source, 0);\n\t\t\t\t}\n\t\t\t\tnet_ipv4_handle(packet, nic, size - sizeof(struct ethernet_packet));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid net_eth_send(struct EthernetDevice * nic, size_t len, void* data, uint16_t type, uint8_t * dest) {\n\tsize_t total_size = sizeof(struct ethernet_packet) + len;\n\tstruct ethernet_packet * packet = malloc(total_size);\n\tmemcpy(packet->payload, data, len);\n\tmemcpy(packet->destination, dest, 6);\n\tmemcpy(packet->source, nic->mac, 6);\n\tpacket->type = htons(type);\n\twrite_fs(nic->device_node, 0, total_size, (uint8_t*)packet);\n\tfree(packet);\n}\n"
  },
  {
    "path": "kernel/net/ipv4.c",
    "content": "/**\n * @file  kernel/net/ipv4.c\n * @brief IPv4, TCP, UDP protocol implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/syscall.h>\n#include <kernel/hashmap.h>\n#include <kernel/vfs.h>\n#include <kernel/time.h>\n#include <kernel/misc.h>\n#include <kernel/assert.h>\n\n#include <kernel/net/netif.h>\n#include <kernel/net/eth.h>\n#include <kernel/net/ipv4.h>\n\n#include <sys/socket.h>\n#include <arpa/inet.h>\n\n#ifndef MISAKA_DEBUG_NET\n#define printf(...) if (_debug) printf(__VA_ARGS__)\n//#define printf(...)\n#endif\n\n#define DEFAULT_TCP_WINDOW_SIZE 65535\n\nstatic int _debug __attribute__((unused)) = 0;\n\nstatic void ip_ntoa(const uint32_t src_addr, char * out) {\n\tsnprintf(out, 16, \"%d.%d.%d.%d\",\n\t\t(src_addr & 0xFF000000) >> 24,\n\t\t(src_addr & 0xFF0000) >> 16,\n\t\t(src_addr & 0xFF00) >> 8,\n\t\t(src_addr & 0xFF));\n}\n\nstatic uint16_t icmp_checksum(struct ipv4_packet * packet) {\n\tuint32_t sum = 0;\n\tuint16_t * s = (uint16_t *)packet->payload;\n\tfor (int i = 0; i < (ntohs(packet->length) - 20) / 2; ++i) {\n\t\tsum += ntohs(s[i]);\n\t}\n\tif (sum > 0xFFFF) {\n\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t}\n\treturn ~(sum & 0xFFFF) & 0xFFFF;\n}\n\nuint16_t calculate_ipv4_checksum(struct ipv4_packet * p) {\n\tuint32_t sum = 0;\n\tuint16_t * s = (uint16_t *)p;\n\n\t/* TODO: Checksums for options? */\n\tfor (int i = 0; i < 10; ++i) {\n\t\tsum += ntohs(s[i]);\n\t\tif (sum > 0xFFFF) {\n\t\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t\t}\n\t}\n\n\n\treturn ~(sum & 0xFFFF) & 0xFFFF;\n}\n\nuint16_t calculate_tcp_checksum(struct tcp_check_header * p, struct tcp_header * h, void * d, size_t payload_size) {\n\tuint32_t sum = 0;\n\tuint16_t * s = (uint16_t *)p;\n\n\t/* TODO: Checksums for options? */\n\tfor (int i = 0; i < 6; ++i) {\n\t\tsum += ntohs(s[i]);\n\t\tif (sum > 0xFFFF) {\n\t\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t\t}\n\t}\n\n\ts = (uint16_t *)h;\n\tfor (int i = 0; i < 10; ++i) {\n\t\tsum += ntohs(s[i]);\n\t\tif (sum > 0xFFFF) {\n\t\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t\t}\n\t}\n\n\tuint16_t d_words = payload_size / 2;\n\n\ts = (uint16_t *)d;\n\tfor (unsigned int i = 0; i < d_words; ++i) {\n\t\tsum += ntohs(s[i]);\n\t\tif (sum > 0xFFFF) {\n\t\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t\t}\n\t}\n\n\tif (d_words * 2 != payload_size) {\n\t\tuint8_t * t = (uint8_t *)d;\n\t\tuint8_t tmp[2];\n\t\ttmp[0] = t[d_words * sizeof(uint16_t)];\n\t\ttmp[1] = 0;\n\n\t\tuint16_t * f = (uint16_t *)tmp;\n\n\t\tsum += ntohs(f[0]);\n\t\tif (sum > 0xFFFF) {\n\t\t\tsum = (sum >> 16) + (sum & 0xFFFF);\n\t\t}\n\t}\n\n\treturn ~(sum & 0xFFFF) & 0xFFFF;\n}\n\nstatic hashmap_t * udp_sockets = NULL;\nstatic hashmap_t * tcp_sockets = NULL;\nstatic hashmap_t * icmp_sockets = NULL;\n\nvoid ipv4_install(void) {\n\tudp_sockets = hashmap_create_int(10);\n\ttcp_sockets = hashmap_create_int(10);\n\ticmp_sockets = hashmap_create_int(10);\n}\n\nint net_ipv4_send(struct ipv4_packet * response, fs_node_t * nic) {\n\t/* TODO: This should be routing, with a _hint_ about the interface, not the actual nic to send from! */\n\tstruct EthernetDevice * enic = nic->device;\n\n\t/* where are we going? */\n\tuint32_t ipdest = response->destination;\n\n\t/* Get the ethernet address of the destination */\n\tstruct ArpCacheEntry * resp;\n\n\t/* Is this local or should we send it to the gateway? */\n\tif (!enic->ipv4_subnet || ((ipdest & enic->ipv4_subnet) != (enic->ipv4_addr & enic->ipv4_subnet))) {\n\t\tipdest = enic->ipv4_gateway;\n\t\tresp = net_arp_cache_get(ipdest);\n\t} else {\n\t\tresp = net_arp_cache_get(ipdest);\n\t\tif (!resp) {\n\t\t\tnet_arp_ask(ipdest, nic);\n\n\t\t\tunsigned long s, ss;\n\t\t\trelative_time(0, 1000, &s, &ss);\n\t\t\tsleep_until((process_t *)this_core->current_process, s, ss);\n\t\t\tswitch_task(0);\n\n\t\t\tresp = net_arp_cache_get(ipdest);\n\t\t}\n\t}\n\n\n\t/* Pass the packet to the next stage */\n\tnet_eth_send(enic, ntohs(response->length), response, ETHERNET_TYPE_IPV4, resp ? resp->hwaddr : ETHERNET_BROADCAST_MAC);\n\n\treturn 0;\n}\n\nstatic void sock_ipv4_control_common(sock_t * sock, struct msghdr * msg, struct ipv4_packet * src, int proto) {\n\t/* TODO Other options; priv32[2] should be for flags? */\n\tif (sock->priv32[2] && msg->msg_controllen > sizeof(struct cmsghdr) + 1) {\n\t\tstruct cmsghdr * out = msg->msg_control;\n\t\tout->cmsg_len = sizeof(struct cmsghdr) + 1;\n\t\tout->cmsg_level = IPPROTO_IP;\n\t\tout->cmsg_type = IP_RECVTTL;\n\t\tout->cmsg_data[0] = src->ttl;\n\t\tmsg->msg_controllen = sizeof(struct cmsghdr) + 1;\n\t} else {\n\t\tmsg->msg_controllen = 0;\n\t}\n}\n\nstatic void icmp_handle(struct ipv4_packet * packet, const char * src, const char * dest, fs_node_t * nic) {\n\tstruct icmp_header * header = (void*)&packet->payload;\n\n\t/* Is this a PING request? */\n\tif (header->type == 8 && header->code == 0) {\n\t\tprintf(\"net: ping with %d bytes of payload\\n\", ntohs(packet->length));\n\t\tif (ntohs(packet->length) & 1) {\n\t\t\tpacket->length = htons(ntohs(packet->length) + 1);\n\t\t}\n\n\t\tstruct ipv4_packet * response = malloc(ntohs(packet->length));\n\t\tmemcpy(response, packet, ntohs(packet->length));\n\t\tresponse->length = packet->length;\n\t\tresponse->destination = packet->source;\n\t\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\t\tresponse->ttl = 64;\n\t\tresponse->protocol = 1;\n\t\tresponse->ident = packet->ident;\n\t\tresponse->flags_fragment = htons(0x4000);\n\t\tresponse->version_ihl = 0x45;\n\t\tresponse->dscp_ecn = 0;\n\t\tresponse->checksum = 0;\n\t\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\t\tstruct icmp_header * ping_reply = (void*)&response->payload;\n\t\tping_reply->csum = 0;\n\t\tping_reply->type = 0;\n\t\tping_reply->csum = htons(icmp_checksum(response));\n\n\t\t/* send ipv4... */\n\t\tnet_ipv4_send(response,nic);\n\t\tfree(response);\n\t} else if (header->type == 0 && header->code == 0) {\n\t\t/* Did we have a client waiting for this? */\n\t\tsock_t * handler = hashmap_get(icmp_sockets, (void*)(uintptr_t)ntohs(header->identifier));\n\t\tif (handler) {\n\t\t\tnet_sock_add(handler, packet, ntohs(packet->length));\n\t\t}\n\t} else {\n\t\tprintf(\"net: ipv4: %s: %s -> %s ICMP %d (code = %d)\\n\", nic->name, src, dest, header->type, header->code);\n\t}\n}\n\nstatic void sock_icmp_close(sock_t * sock) {\n\thashmap_remove(icmp_sockets, (void*)(uintptr_t)sock->priv32[0]);\n}\n\nstatic long sock_icmp_recv(sock_t * sock, struct msghdr * msg, int flags) {\n\tif (msg->msg_iovlen > 1) {\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\n\tif (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN;\n\n\tchar * packet = net_sock_get(sock);\n\tif (!packet) return -EINTR;\n\tsize_t packet_size = *(size_t*)packet - sizeof(struct ipv4_packet);\n\n\tstruct ipv4_packet * src = (struct ipv4_packet*)(packet + sizeof(size_t));\n\n\tif (packet_size > msg->msg_iov[0].iov_len) {\n\t\tdprintf(\"ICMP recv too big for vector\\n\");\n\t\tpacket_size = msg->msg_iov[0].iov_len;\n\t}\n\n\tif (msg->msg_namelen == sizeof(struct sockaddr_in)) {\n\t\tif (msg->msg_name) {\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_family = AF_INET;\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_port = 0;\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_addr.s_addr = src->source;\n\t\t}\n\t}\n\n\tsock_ipv4_control_common(sock,msg,src,IPPROTO_ICMP);\n\n\tmemcpy(msg->msg_iov[0].iov_base, src->payload, packet_size);\n\tfree(packet);\n\treturn packet_size;\n}\n\nstatic long sock_icmp_send(sock_t * sock, const struct msghdr *msg, int flags) {\n\tif (msg->msg_iovlen > 1) return -ENOTSUP;\n\tif (msg->msg_iovlen == 0) return 0;\n\tif (msg->msg_namelen != sizeof(struct sockaddr_in)) return -EINVAL;\n\tif (msg->msg_iov[0].iov_len < sizeof(struct icmp_header)) return -EINVAL;\n\n\tstruct icmp_header * icmp = msg->msg_iov[0].iov_base;\n\tif (icmp->type != 8 || icmp->code != 0) return -EINVAL;\n\tif (icmp->identifier != 0) return -EINVAL;\n\n\tstruct sockaddr_in * name = msg->msg_name;\n\tfs_node_t * nic = net_if_route(name->sin_addr.s_addr);\n\tif (!nic) return -ENONET;\n\tsize_t total_length = sizeof(struct ipv4_packet) + msg->msg_iov[0].iov_len;\n\n\tstruct ipv4_packet * response = malloc(total_length);\n\tresponse->length = htons(total_length);\n\tresponse->destination = name->sin_addr.s_addr;\n\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\tresponse->ttl = 64;\n\tresponse->protocol = 1;\n\tresponse->ident = 0;\n\tresponse->flags_fragment = htons(0x4000);\n\tresponse->version_ihl = 0x45;\n\tresponse->dscp_ecn = 0;\n\tresponse->checksum = 0;\n\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\tmemcpy(response->payload, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len);\n\tstruct icmp_header * micmp = (struct icmp_header*)response->payload;\n\tmicmp->identifier = htons(sock->priv32[0]);\n\tmicmp->csum = 0;\n\tmicmp->csum = htons(icmp_checksum(response));\n\n\tnet_ipv4_send(response,nic);\n\tfree(response);\n\n\treturn 0;\n}\n\nstatic int icmp_socket(void) {\n\tif (hashmap_has(icmp_sockets, (void*)(uintptr_t)this_core->current_process->id)) return -EINVAL;\n\tsock_t * sock = net_sock_create();\n\tsock->sock_recv = sock_icmp_recv;\n\tsock->sock_send = sock_icmp_send;\n\tsock->sock_close = sock_icmp_close;\n\tsock->priv32[0] = this_core->current_process->id;\n\thashmap_set(icmp_sockets, (void*)(uintptr_t)sock->priv32[0], sock);\n\treturn process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock);\n}\n\n#define TCP_FLAGS_FIN (1 << 0)\n#define TCP_FLAGS_SYN (1 << 1)\n#define TCP_FLAGS_RST (1 << 2)\n#define TCP_FLAGS_PSH (1 << 3)\n#define TCP_FLAGS_ACK (1 << 4)\n#define TCP_FLAGS_URG (1 << 5)\n#define TCP_FLAGS_ECE (1 << 6)\n#define TCP_FLAGS_CWR (1 << 7)\n#define TCP_FLAGS_NS  (1 << 8)\n#define DATA_OFFSET_5 (0x5 << 12)\n\nstatic int tcp_ack(fs_node_t * nic, sock_t * sock, struct ipv4_packet * packet, int isSynAck, size_t payload_len) {\n\tstruct tcp_header * tcp = (struct tcp_header*)&packet->payload;\n\tint retval = 1;\n\tint window_size = DEFAULT_TCP_WINDOW_SIZE;\n\tint send_thrice = 0;\n\n#if 0\n\t/* XXX: This means the header is bigger than we expect... */\n\tif ((ntohs(tcp->flags) & 0xF000) != 0x5000) {\n\t\tint _debug __attribute__((unused)) = 1;\n\t\tprintf(\"tcp: uh, weird flags? %#4x\\n\", ntohs(tcp->flags));\n\t}\n#endif\n\n\tif (sock->priv32[1] != 0 && !isSynAck &&\n\t\tsock->priv32[1] != ntohl(tcp->seq_number)) {\n#if 0\n\t\tint _debug __attribute__((unused)) = 1;\n\t\tprintf(\"tcp: suspicious of their seq number?\\n\");\n\t\tprintf(\"tcp: their seq = %u our ack = %u\\n\",\n\t\t\tntohl(tcp->seq_number), sock->priv32[1]);\n#endif\n\t\t//window_size = 300;\n\t\t\n\t\tretval = 0;\n\t\tsend_thrice = 1;\n\t} else {\n\t\tsock->priv32[0] = isSynAck ? 1 : sock->priv32[0];\n\t\tsock->priv32[1] = (ntohl(tcp->seq_number) + payload_len) & 0xFFFFFFFF;\n\t\tsock->priv[1] = 2;\n\t}\n\n\tsock->priv[2]++;\n\n#if 0\n\tprintf(\"tcp: their ack = %u our seq = %u\\n\",\n\t\tntohl(tcp->ack_number), sock->priv32[0]);\n\tprintf(\"tcp: their seq = %u our ack = %u\\n\",\n\t\tntohl(tcp->seq_number), sock->priv32[1]);\n#endif\n\n\n\tsize_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header);\n\n\tstruct ipv4_packet * response = malloc(total_length);\n\tresponse->length = htons(total_length);\n\tresponse->destination = packet->source;\n\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\tresponse->ttl = 64;\n\tresponse->protocol = IPV4_PROT_TCP;\n\tresponse->ident = htons(sock->priv[2]);\n\tresponse->flags_fragment = htons(0x0);\n\tresponse->version_ihl = 0x45;\n\tresponse->dscp_ecn = 0;\n\tresponse->checksum = 0;\n\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\tint flags = TCP_FLAGS_ACK;\n\tif (ntohs(tcp->flags) & TCP_FLAGS_FIN) {\n\t\t/* Other side is closed now */\n\t\tsock->priv32[1]++;\n\t\tsock->priv[1] = 3;\n\t}\n\n\t/* Stick TCP header into payload */\n\tstruct tcp_header * tcp_header = (struct tcp_header*)&response->payload;\n\ttcp_header->source_port = htons(sock->priv[0]);\n\ttcp_header->destination_port = tcp->source_port;\n\ttcp_header->seq_number = htonl(sock->priv32[0]);\n\ttcp_header->ack_number = htonl(sock->priv32[1]);\n\ttcp_header->flags = htons(flags | 0x5000);\n\ttcp_header->window_size = htons(window_size);\n\ttcp_header->checksum = 0;\n\ttcp_header->urgent = 0;\n\n\t/* Calculate checksum */\n\tstruct tcp_check_header check_hd = {\n\t\t.source = response->source,\n\t\t.destination = response->destination,\n\t\t.zeros = 0,\n\t\t.protocol = IPV4_PROT_TCP,\n\t\t.tcp_len = htons(sizeof(struct tcp_header)),\n\t};\n\n\ttcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, NULL, 0));\n\tnet_ipv4_send(response,nic);\n\tif (send_thrice) {\n\t\tnet_ipv4_send(response,nic);\n\t\tnet_ipv4_send(response,nic);\n\t}\n\tfree(response);\n\treturn retval;\n}\n\nvoid net_ipv4_handle(struct ipv4_packet * packet, fs_node_t * nic, size_t size) {\n\n\tif (size < sizeof(struct ipv4_packet)) {\n\t\tdprintf(\"ipv4: Incoming packet is too small.\\n\");\n\t}\n\n\tchar dest[16];\n\tchar src[16];\n\n\tip_ntoa(ntohl(packet->destination), dest);\n\tip_ntoa(ntohl(packet->source), src);\n\n\tswitch (packet->protocol) {\n\t\tcase 1:\n\t\t\ticmp_handle(packet, src, dest, nic);\n\t\t\tbreak;\n\t\tcase IPV4_PROT_UDP: {\n\t\t\tuint16_t dest_port = ntohs(((uint16_t*)&packet->payload)[1]);\n\t\t\tprintf(\"net: ipv4: %s: %s -> %s udp %d to %d\\n\", nic->name, src, dest, ntohs(((uint16_t*)&packet->payload)[0]), dest_port);\n\t\t\tif (hashmap_has(udp_sockets, (void*)(uintptr_t)dest_port)) {\n\t\t\t\tprintf(\"net: udp: received and have a waiting endpoint!\\n\");\n\t\t\t\tsock_t * sock = hashmap_get(udp_sockets, (void*)(uintptr_t)dest_port);\n\t\t\t\tnet_sock_add(sock, packet, ntohs(packet->length));\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase IPV4_PROT_TCP: {\n\t\t\tuint16_t dest_port = ntohs(((uint16_t*)&packet->payload)[1]);\n\t\t\tprintf(\"net: ipv4: %s: %s -> %s tcp %d to %d\\n\", nic->name, src, dest, ntohs(((uint16_t*)&packet->payload)[0]), dest_port);\n\t\t\tsock_t * sock = hashmap_get(tcp_sockets, (void*)(uintptr_t)dest_port);\n\t\t\tif (sock) {\n\t\t\t\tprintf(\"net: tcp: received and have a waiting endpoint!\\n\");\n\t\t\t\t/* What kind of packet is this? Is it something we were expecting? */\n\t\t\t\tstruct tcp_header * tcp = (struct tcp_header*)&packet->payload;\n\n\t\t\t\tif (sock->priv[1] == 1) {\n\t\t\t\t\t/* Awaiting SYN ACK, is this one? */\n\t\t\t\t\tif ((ntohs(tcp->flags) & (TCP_FLAGS_SYN | TCP_FLAGS_ACK)) == (TCP_FLAGS_SYN | TCP_FLAGS_ACK)) {\n\t\t\t\t\t\tprintf(\"tcp: synack\\n\");\n\t\t\t\t\t\tif (tcp_ack(nic, sock, packet, 1, 1)) {\n\t\t\t\t\t\t\tnet_sock_add(sock, packet, ntohs(packet->length));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if ((ntohs(tcp->flags) & (TCP_FLAGS_RST))) {\n\t\t\t\t\t\tsock->priv[1] = 0;\n\t\t\t\t\t\tnet_sock_alert(sock);\n\t\t\t\t\t}\n\t\t\t\t} else if (sock->priv[1] == 2) {\n\t\t\t\t\tsize_t packet_len = ntohs(packet->length) - sizeof(struct ipv4_packet);\n\t\t\t\t\tsize_t hlen = ((ntohs(tcp->flags) & 0xF000) >> 12) * 4;\n\t\t\t\t\tsize_t payload_len = packet_len - hlen;\n\t\t\t\t\tif (payload_len) {\n\t\t\t\t\t\tprintf(\"tcp: acking because payload_len = %zu (hlen=%zu, packet_len=%zu)\\n\", payload_len, hlen, packet_len);\n\t\t\t\t\t\tif (tcp_ack(nic, sock, packet, 0, payload_len)) {\n\t\t\t\t\t\t\tnet_sock_add(sock, packet, ntohs(packet->length));\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (ntohs(tcp->flags) & TCP_FLAGS_FIN) {\n\t\t\t\t\t\ttcp_ack(nic, sock, packet, 0, 0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic spin_lock_t udp_port_lock = {0};\n\nstatic int next_port = 12345;\nstatic int udp_get_port(sock_t * sock) {\n\tspin_lock(udp_port_lock);\n\tint out = next_port++;\n\thashmap_set(udp_sockets, (void*)(uintptr_t)out, sock);\n\tsock->priv[0] = out;\n\tspin_unlock(udp_port_lock);\n\treturn out;\n}\n\nlong sock_udp_getsockname(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) {\n\tif (!sock->priv[0]) return -EINVAL;\n\t/* TODO do we even record the \"bound\" address? */\n\tstruct sockaddr_in out = {\n\t\tAF_INET, htons(sock->priv[0]), { 0 }, {0},\n\t};\n\n\tmemcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in));\n\tif (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in);\n\treturn 0;\n}\n\nstatic long sock_udp_send(sock_t * sock, const struct msghdr *msg, int flags) {\n\tprintf(\"udp: send called\\n\");\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't send multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\tif (msg->msg_namelen != sizeof(struct sockaddr_in)) {\n\t\tprintf(\"udp: invalid destination address size %ld\\n\", msg->msg_namelen);\n\t\treturn -EINVAL;\n\t}\n\n\tstruct sockaddr_in * name = msg->msg_name;\n\n\tif (!name->sin_port) return -EADDRNOTAVAIL; /* 0 is still 0 in both endians */\n\n\tif (sock->priv[0] == 0) {\n\t\tudp_get_port(sock);\n\t\tprintf(\"udp: assigning port %d to socket\\n\", sock->priv[0]);\n\t}\n\n\n\tchar dest[16];\n\tip_ntoa(ntohl(name->sin_addr.s_addr), dest);\n\tprintf(\"udp: want to send to %s\\n\", dest);\n\n\t/* Routing: We need a device to send this on... */\n\tfs_node_t * nic = net_if_route(name->sin_addr.s_addr);\n\tif (!nic) return 0;\n\n\tsize_t total_length = sizeof(struct ipv4_packet) + msg->msg_iov[0].iov_len + sizeof(struct udp_packet);\n\n\tstruct ipv4_packet * response = malloc(total_length);\n\tresponse->length = htons(total_length);\n\tresponse->destination = name->sin_addr.s_addr;\n\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\tresponse->ttl = 64;\n\tresponse->protocol = IPV4_PROT_UDP;\n\tresponse->ident = 0;\n\tresponse->flags_fragment = htons(0x4000);\n\tresponse->version_ihl = 0x45;\n\tresponse->dscp_ecn = 0;\n\tresponse->checksum = 0;\n\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\t/* Stick UDP header into payload */\n\tstruct udp_packet * udp_packet = (struct udp_packet*)&response->payload;\n\tudp_packet->source_port = htons(sock->priv[0]);\n\tudp_packet->destination_port = name->sin_port;\n\tudp_packet->length = htons(sizeof(struct udp_packet) + msg->msg_iov[0].iov_len);\n\tudp_packet->checksum = 0;\n\n\tmemcpy(response->payload + sizeof(struct udp_packet), msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len);\n\tnet_ipv4_send(response,nic);\n\tfree(response);\n\n\treturn msg->msg_iov[0].iov_len;\n}\n\nstatic long sock_udp_recv(sock_t * sock, struct msghdr * msg, int flags) {\n\tprintf(\"udp: recv called\\n\");\n\tif (!sock->priv[0]) {\n\t\tprintf(\"udp: recv() but socket has no port\\n\");\n\t\treturn -EINVAL;\n\t}\n\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't recv multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\n\tif (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN;\n\n\tchar * packet = net_sock_get(sock);\n\tif (!packet) return -EINTR;\n\tstruct ipv4_packet * data = (struct ipv4_packet*)(packet + sizeof(size_t));\n\tstruct udp_packet * udp_packet = (struct udp_packet*)&data->payload;\n\n\tprintf(\"udp: got response, size is %u - sizeof(ipv4) - sizeof(udp) = %lu\\n\",\n\t\tntohs(data->length), ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet));\n\tmemcpy(msg->msg_iov[0].iov_base, udp_packet->payload, ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet));\n\n\tif (msg->msg_namelen == sizeof(struct sockaddr_in)) {\n\t\tif (msg->msg_name) {\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_family = AF_INET;\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_port = udp_packet->source_port;\n\t\t\t((struct sockaddr_in*)msg->msg_name)->sin_addr.s_addr = data->source;\n\t\t}\n\t}\n\n\tsock_ipv4_control_common(sock,msg,data,IPPROTO_UDP);\n\n\tprintf(\"udp: data copied to iov 0, return length?\\n\");\n\n\tlong resp = ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet);\n\tfree(packet);\n\treturn resp;\n}\n\nstatic void sock_udp_close(sock_t * sock) {\n\tif (sock->priv[0]) {\n\t\tprintf(\"udp: removing port %d from bound map\\n\", sock->priv[0]);\n\t\tspin_lock(udp_port_lock);\n\t\thashmap_remove(udp_sockets, (void*)(uintptr_t)sock->priv[0]);\n\t\tspin_unlock(udp_port_lock);\n\t}\n}\n\n\nstatic long sock_udp_bind(sock_t * sock, const struct sockaddr *addr, socklen_t addrlen) {\n\tif (sock->priv[0]) return -EINVAL; /* Already bound */\n\n\t/* Get port */\n\tconst struct sockaddr_in * addr_in = (const struct sockaddr_in *)addr;\n\tint port = ntohs(addr_in->sin_port);\n\n\tif (port == 0) {\n\t\t/* Pick one */\n\t\tspin_lock(udp_port_lock);\n\t\tport = next_port++;\n\t\tspin_unlock(udp_port_lock);\n\t} else if (port < 1024 && this_core->current_process->user != 0) {\n\t\t/* Only superuser can bind to lower ports */\n\t\treturn -EACCES;\n\t}\n\n\tspin_lock(udp_port_lock);\n\tif (hashmap_has(udp_sockets, (void*)(uintptr_t)port)) {\n\t\tspin_unlock(udp_port_lock);\n\t\treturn -EADDRINUSE;\n\t}\n\thashmap_set(udp_sockets, (void*)(uintptr_t)port, sock);\n\tsock->priv[0] = port;\n\tspin_unlock(udp_port_lock);\n\n\t/* Totally ignore the NIC stuff */\n\n\treturn 0;\n}\n\nstatic int udp_socket(void) {\n\tprintf(\"udp socket...\\n\");\n\tsock_t * sock = net_sock_create();\n\tsock->sock_recv = sock_udp_recv;\n\tsock->sock_send = sock_udp_send;\n\tsock->sock_close = sock_udp_close;\n\tsock->sock_bind = sock_udp_bind;\n\tsock->sock_getsockname = sock_udp_getsockname;\n\treturn process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock);\n}\n\nstatic spin_lock_t tcp_port_lock = {0};\nstatic void sock_tcp_close(sock_t * sock) {\n\tif (sock->priv[0]) {\n\t\tprintf(\"tcp: removing port %d from bound map\\n\", sock->priv[0]);\n\t\tspin_lock(tcp_port_lock);\n\t\thashmap_remove(tcp_sockets, (void*)(uintptr_t)sock->priv[0]);\n\t\tspin_unlock(tcp_port_lock);\n\n\t\tsize_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header);\n\t\tfs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr);\n\t\tif (!nic) return;\n\n\t\tstruct ipv4_packet * response = malloc(total_length);\n\t\tresponse->length = htons(total_length);\n\t\tresponse->destination = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr;\n\t\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\t\tresponse->ttl = 64;\n\t\tresponse->protocol = IPV4_PROT_TCP;\n\t\tsock->priv[2]++;\n\t\tresponse->ident = htons(sock->priv[2]);\n\t\tresponse->flags_fragment = htons(0x0);\n\t\tresponse->version_ihl = 0x45;\n\t\tresponse->dscp_ecn = 0;\n\t\tresponse->checksum = 0;\n\t\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\t\t/* Stick TCP header into payload */\n\t\tstruct tcp_header * tcp_header = (struct tcp_header*)&response->payload;\n\t\ttcp_header->source_port = htons(sock->priv[0]);\n\t\ttcp_header->destination_port = ((struct sockaddr_in*)&sock->dest)->sin_port;\n\t\ttcp_header->seq_number = htonl(sock->priv32[0]);\n\t\ttcp_header->ack_number = htonl(sock->priv32[1]);\n\t\ttcp_header->flags = htons(TCP_FLAGS_FIN | TCP_FLAGS_ACK | 0x5000);\n\t\ttcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE);\n\t\ttcp_header->checksum = 0;\n\t\ttcp_header->urgent = 0;\n\n\t\t/* Calculate checksum */\n\t\tstruct tcp_check_header check_hd = {\n\t\t\t.source = response->source,\n\t\t\t.destination = response->destination,\n\t\t\t.zeros = 0,\n\t\t\t.protocol = IPV4_PROT_TCP,\n\t\t\t.tcp_len = htons(sizeof(struct tcp_header)),\n\t\t};\n\n\t\ttcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, tcp_header->payload, 0));\n\t\tnet_ipv4_send(response,nic);\n\t\tfree(response);\n\t}\n}\n\nstatic int next_tcp_port = 49152;\nstatic int tcp_get_port(sock_t * sock) {\n\tspin_lock(tcp_port_lock);\n\tint out = next_tcp_port++;\n\thashmap_set(tcp_sockets, (void*)(uintptr_t)out, sock);\n\tsock->priv[0] = out;\n\tspin_unlock(tcp_port_lock);\n\treturn out;\n}\n\nstatic long sock_tcp_recv(sock_t * sock, struct msghdr * msg, int flags) {\n\tif (!sock->priv[0]) {\n\t\tprintf(\"tcp: recv() but socket has no port\\n\");\n\t\treturn -EINVAL;\n\t}\n\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't recv multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\n\tif (sock->unread) {\n\t\tif (sock->unread > msg->msg_iov[0].iov_len) {\n\t\t\tunsigned long out = msg->msg_iov[0].iov_len;\n\t\t\tsock->unread -= out;\n\t\t\tmemcpy(msg->msg_iov[0].iov_base, sock->buf, out);\n\t\t\tchar * x = malloc(sock->unread);\n\t\t\tmemcpy(x, sock->buf + out, sock->unread);\n\t\t\tfree(sock->buf);\n\t\t\tsock->buf = x;\n\t\t\treturn out;\n\t\t} else {\n\t\t\tunsigned long out = sock->unread;\n\t\t\tsock->unread = 0;\n\t\t\tmemcpy(msg->msg_iov[0].iov_base, sock->buf, out);\n\t\t\tfree(sock->buf);\n\t\t\tsock->buf = NULL;\n\t\t\treturn out;\n\t\t}\n\t}\n\n\tif (!sock->rx_queue->length && sock->priv[1] == 3) {\n\t\treturn 0; /* EOF */\n\t}\n\n\tif (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN;\n\n\twhile (!sock->rx_queue->length) {\n\t\tint r = process_wait_nodes((process_t *)this_core->current_process, (fs_node_t*[]){(fs_node_t*)sock,NULL}, 200);\n\t\tif (r == -EINTR) return -ERESTARTSYS;\n\t\tif (!sock->rx_queue->length) {\n\t\t\tif (sock->priv[1] == 3) {\n\t\t\t\t/* Socket was closed while waiting */\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t}\n\n\tchar * packet = net_sock_get(sock);\n\tif (!packet) return -EINTR;\n\tstruct ipv4_packet * data = (struct ipv4_packet*)(packet + sizeof(size_t));\n\tsize_t packet_size = *(size_t*)packet;\n\tunsigned long resp = ntohs(data->length);\n\n\tif (resp != packet_size) {\n\t\tdprintf(\"packet size does not match: %zu %zu\\n\", resp, packet_size);\n\t\tresp = packet_size;\n\t}\n\n\tif (resp < sizeof(struct ipv4_packet) + sizeof(struct tcp_header)) {\n\t\tdprintf(\"Invalid receive data?\\n\");\n\t\tassert(0);\n\t}\n\n\tresp -=  sizeof(struct ipv4_packet) + sizeof(struct tcp_header);\n\n\tif (resp > (unsigned long)msg->msg_iov[0].iov_len) {\n\t\tmemcpy(msg->msg_iov[0].iov_base, data->payload + sizeof(struct tcp_header),msg->msg_iov[0].iov_len);\n\t\tresp -= msg->msg_iov[0].iov_len;\n\t\tif (resp == 0xFFFFffffFFFFffff) printf(\"what\\n\");\n\t\tsock->unread = resp;\n\t\tsock->buf = malloc(resp);\n\t\tmemcpy(sock->buf, data->payload + sizeof(struct tcp_header) + msg->msg_iov[0].iov_len, resp);\n\t\tfree(packet);\n\t\treturn msg->msg_iov[0].iov_len;\n\t}\n\n\tmemcpy(msg->msg_iov[0].iov_base, data->payload + sizeof(struct tcp_header), resp);\n\tfree(packet);\n\treturn resp;\n}\n\nextern uint32_t rand(void);\n\nstatic long sock_tcp_connect(sock_t * sock, const struct sockaddr *addr, socklen_t addrlen) {\n\tconst struct sockaddr_in * dest = (const struct sockaddr_in *)addr;\n\tchar deststr[16];\n\tip_ntoa(ntohl(dest->sin_addr.s_addr), deststr);\n\tprintf(\"tcp: connect requested to %s port %d\\n\", deststr, ntohs(dest->sin_port));\n\n\tif (sock->priv[1] != 0) {\n\t\tprintf(\"tcp: socket is already connected?\\n\");\n\t\treturn -EINVAL;\n\t}\n\n\tif (!dest->sin_port) return -EADDRNOTAVAIL; /* 0 is still 0 in both endians */\n\n\t/* Get a port */\n\ttcp_get_port(sock);\n\tprintf(\"tcp: connecting from ephemeral port %d\\n\", (int)sock->priv[0]);\n\n\t/* Mark as awaiting connection, send initial SYN */\n\tsock->priv[1] = 1;\n\n\tmemcpy(&sock->dest, addr, addrlen);\n\n\tfs_node_t * nic = net_if_route(dest->sin_addr.s_addr);\n\tif (!nic) return -ENONET;\n\n\tsize_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header);\n\n\tstruct ipv4_packet * response = malloc(total_length);\n\tresponse->length = htons(total_length);\n\tresponse->destination = dest->sin_addr.s_addr;\n\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\tresponse->ttl = 64;\n\tresponse->protocol = IPV4_PROT_TCP;\n\tsock->priv[2] = rand();\n\tresponse->ident = htons(sock->priv[2]);\n\tresponse->flags_fragment = htons(0x0);\n\tresponse->version_ihl = 0x45;\n\tresponse->dscp_ecn = 0;\n\tresponse->checksum = 0;\n\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\t/* Stick TCP header into payload */\n\tstruct tcp_header * tcp_header = (struct tcp_header*)&response->payload;\n\ttcp_header->source_port = htons(sock->priv[0]);\n\ttcp_header->destination_port = dest->sin_port;\n\ttcp_header->seq_number = 0;\n\ttcp_header->ack_number = 0;\n\ttcp_header->flags = htons((1 << 1) | 0x5000);\n\ttcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE);\n\ttcp_header->checksum = 0;\n\ttcp_header->urgent = 0;\n\n\t/* Calculate checksum */\n\tstruct tcp_check_header check_hd = {\n\t\t.source = response->source,\n\t\t.destination = response->destination,\n\t\t.zeros = 0,\n\t\t.protocol = IPV4_PROT_TCP,\n\t\t.tcp_len = htons(sizeof(struct tcp_header)),\n\t};\n\n\ttcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, NULL, 0));\n\n\tnet_ipv4_send(response,nic);\n\n\t//int _debug __attribute__((unused)) = 1;\n\tprintf(\"tcp: waiting for connect to finish; queue = %ld\\n\", sock->rx_queue->length);\n\n\tunsigned long s, ss;\n\tunsigned long ns, nss;\n\trelative_time(1,0,&s,&ss);\n\tint attempts = 0;\n\n\twhile (!sock->rx_queue->length) {\n\t\tint result = process_wait_nodes((process_t *)this_core->current_process, (fs_node_t*[]){(fs_node_t*)sock,NULL}, 200);\n\t\tif (result == -EINTR) {\n\t\t\tfree(response);\n\t\t\treturn -EINTR;\n\t\t}\n\t\trelative_time(0,0,&ns,&nss);\n\t\tif (sock->priv[1] == 0) {\n\t\t\tfree(response);\n\t\t\treturn -ECONNREFUSED;\n\t\t}\n\t\tif (result != 0 && (ns > s || (ns == s && nss > ss))) {\n\t\t\tif (attempts++ == 3) {\n\t\t\t\tprintf(\"tcp: connect timed out\\n\");\n\t\t\t\tfree(response);\n\t\t\t\treturn -ETIMEDOUT;\n\t\t\t}\n\t\t\tprintf(\"tcp: retrying...\\n\");\n\t\t\tnet_ipv4_send(response,nic);\n\t\t\trelative_time(1,0,&s,&ss);\n\t\t}\n\t}\n\n\tfree(response);\n\n\tprintf(\"tcp: queue should have data now (len = %lu), trying to read\\n\", sock->rx_queue->length);\n\n\t/* wait for signal that we connected or timed out */\n\tchar * packet = net_sock_get(sock);\n\tif (!packet) return -EINTR;\n\t//struct ipv4_packet * data = packet + sizeof(size_t);\n\tprintf(\"tcp: connect complete\\n\");\n\tfree(packet);\n\n\treturn 0;\n}\n\nssize_t sock_tcp_read(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tprintf(\"tcp: read into buffer of %zu bytes\\n\", size);\n\tstruct iovec _iovec = {\n\t\tbuffer, size\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = NULL,\n\t\t.msg_namelen = 0,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\treturn sock_tcp_recv((sock_t*)node, &_header, 0);\n}\n\nstatic void delay_yield(size_t subticks) {\n\tunsigned long s, ss;\n\trelative_time(0, subticks, &s, &ss);\n\tsleep_until((process_t *)this_core->current_process, s, ss);\n\tswitch_task(0);\n}\n\nstatic long sock_tcp_send(sock_t * sock, const struct msghdr *msg, int flags) {\n\tprintf(\"tcp: send called\\n\");\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't send multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\n\tsize_t size_into = 0;\n\tsize_t size_remaining = msg->msg_iov[0].iov_len;\n\n\tsize_t last = arch_perf_timer();\n\twhile (size_remaining) {\n\t\tsize_t size_to_send = size_remaining > 1024 ? 1024 : size_remaining;\n\t\tsize_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header) + size_to_send;\n\n\t\tfs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr);\n\t\tif (!nic) return -ENONET;\n\n\t\tstruct ipv4_packet * response = malloc(total_length);\n\t\tresponse->length = htons(total_length);\n\t\tresponse->destination = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr;\n\t\tresponse->source = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\t\tresponse->ttl = 64;\n\t\tresponse->protocol = IPV4_PROT_TCP;\n\t\tsock->priv[2]++;\n\t\tresponse->ident = htons(sock->priv[2]);\n\t\tresponse->flags_fragment = htons(0x0);\n\t\tresponse->version_ihl = 0x45;\n\t\tresponse->dscp_ecn = 0;\n\t\tresponse->checksum = 0;\n\t\tresponse->checksum = htons(calculate_ipv4_checksum(response));\n\n\t\t/* Stick TCP header into payload */\n\t\tstruct tcp_header * tcp_header = (struct tcp_header*)&response->payload;\n\t\ttcp_header->source_port = htons(sock->priv[0]);\n\t\ttcp_header->destination_port = ((struct sockaddr_in*)&sock->dest)->sin_port;\n\t\ttcp_header->seq_number = htonl(sock->priv32[0]);\n\t\ttcp_header->ack_number = htonl(sock->priv32[1]);\n\t\ttcp_header->flags = htons(TCP_FLAGS_PSH | TCP_FLAGS_ACK | 0x5000);\n\t\ttcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE);\n\t\ttcp_header->checksum = 0;\n\t\ttcp_header->urgent = 0;\n\n\t\tsock->priv32[0] += size_to_send;\n\n\t\t/* Calculate checksum */\n\t\tstruct tcp_check_header check_hd = {\n\t\t\t.source = response->source,\n\t\t\t.destination = response->destination,\n\t\t\t.zeros = 0,\n\t\t\t.protocol = IPV4_PROT_TCP,\n\t\t\t.tcp_len = htons(sizeof(struct tcp_header) + size_to_send),\n\t\t};\n\n\t\tmemcpy(tcp_header->payload, (char*)msg->msg_iov[0].iov_base + size_into, size_to_send);\n\t\ttcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, tcp_header->payload, size_to_send));\n\t\tnet_ipv4_send(response,nic);\n\t\tfree(response);\n\n\t\tsize_remaining -= size_to_send;\n\t\tsize_into += size_to_send;\n\n\t\tif (size_remaining) {\n\t\t\t/* Keep us away from the BSP... */\n\t\t\tif (processor_count > 1) {\n\t\t\t\tif (this_core->cpu_id == 0) {\n\t\t\t\t\tdelay_yield(0);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (arch_perf_timer() - last > 10000UL * arch_cpu_mhz()) {\n\t\t\t\t\tdelay_yield(0);\n\t\t\t\t\tlast = arch_perf_timer();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn size_into;\n}\n\nssize_t sock_tcp_write(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tprintf(\"tcp: write of %zu bytes\\n\", size);\n\tstruct iovec _iovec = {\n\t\t(void*)buffer, size\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = NULL,\n\t\t.msg_namelen = 0,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\treturn sock_tcp_send((sock_t*)node, &_header, 0);\n}\n\nlong sock_tcp_getsockname(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) {\n\tin_addr_t ip4_addr = 0;\n\tfs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr);\n\tif (nic) {\n\t\tip4_addr = ((struct EthernetDevice*)nic->device)->ipv4_addr;\n\t}\n\n\tstruct sockaddr_in out = {\n\t\tAF_INET, htons(sock->priv[0]), { ip4_addr }, {0},\n\t};\n\n\tmemcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in));\n\tif (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in);\n\treturn 0;\n}\n\nlong sock_tcp_getpeername(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) {\n\tin_addr_t ip4_addr = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr;\n\tstruct sockaddr_in out = {\n\t\tAF_INET, ((struct sockaddr_in*)&sock->dest)->sin_port, { ip4_addr }, {0},\n\t};\n\tmemcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in));\n\tif (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in);\n\treturn 0;\n}\n\nstatic int tcp_socket(void) {\n\tprintf(\"tcp socket...\\n\");\n\tsock_t * sock = net_sock_create();\n\tsock->sock_recv = sock_tcp_recv;\n\tsock->sock_send = sock_tcp_send;\n\tsock->sock_close = sock_tcp_close;\n\tsock->sock_connect = sock_tcp_connect;\n\tsock->sock_getsockname = sock_tcp_getsockname;\n\tsock->sock_getpeername = sock_tcp_getpeername;\n\tsock->_fnode.read = sock_tcp_read;\n\tsock->_fnode.write = sock_tcp_write;\n\tint fd = process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock);\n\tFD_MODE(fd) = 03;\n\treturn fd;\n}\n\nlong net_ipv4_socket(int type, int protocol) {\n\t/* Ignore protocol, make socket for 'type' only... */\n\tswitch (type) {\n\t\tcase SOCK_DGRAM:\n\t\t\tif (!protocol || protocol == IPPROTO_UDP)\n\t\t\t\treturn udp_socket();\n\t\t\tif (protocol == IPPROTO_ICMP)\n\t\t\t\treturn icmp_socket();\n\t\t\treturn -EINVAL;\n\t\tcase SOCK_STREAM:\n\t\t\treturn tcp_socket();\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nlong net_so_ipv4_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen) {\n\tswitch (optname) {\n\t\tcase IP_RECVTTL:\n\t\t\tif (optlen != sizeof(int)) return -EINVAL;\n\t\t\t/* TODO ugh bad */\n\t\t\tsock->priv32[2] = *(int*)optval;\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\treturn -ENOPROTOOPT;\n\t}\n}\n"
  },
  {
    "path": "kernel/net/loop.c",
    "content": "/**\n * @file kernel/net/loop.c\n * @brief Loopback interface\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/vfs.h>\n#include <kernel/spinlock.h>\n#include <kernel/list.h>\n#include <kernel/net/netif.h>\n#include <kernel/net/eth.h>\n#include <errno.h>\n\n#include <sys/socket.h>\n#include <net/if.h>\n\nstruct loop_nic {\n\tstruct EthernetDevice eth;\n\tnetif_counters_t counts;\n};\n\nstatic int ioctl_loop(fs_node_t * node, unsigned long request, void * argp) {\n\tstruct loop_nic * nic = node->device;\n\n\tswitch (request) {\n\t\tcase SIOCGIFHWADDR:\n\t\t\treturn 1;\n\t\tcase SIOCGIFADDR:\n\t\t\tif (nic->eth.ipv4_addr == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_addr, sizeof(nic->eth.ipv4_addr));\n\t\t\treturn 0;\n\t\tcase SIOCSIFADDR:\n\t\t\tmemcpy(&nic->eth.ipv4_addr, argp, sizeof(nic->eth.ipv4_addr));\n\t\t\treturn 0;\n\t\tcase SIOCGIFNETMASK:\n\t\t\tif (nic->eth.ipv4_subnet == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_subnet, sizeof(nic->eth.ipv4_subnet));\n\t\t\treturn 0;\n\t\tcase SIOCSIFNETMASK:\n\t\t\tmemcpy(&nic->eth.ipv4_subnet, argp, sizeof(nic->eth.ipv4_subnet));\n\t\t\treturn 0;\n\t\tcase SIOCGIFGATEWAY:\n\t\t\tif (nic->eth.ipv4_subnet == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_gateway, sizeof(nic->eth.ipv4_gateway));\n\t\t\treturn 0;\n\t\tcase SIOCSIFGATEWAY:\n\t\t\tmemcpy(&nic->eth.ipv4_gateway, argp, sizeof(nic->eth.ipv4_gateway));\n\t\t\tnet_arp_ask(nic->eth.ipv4_gateway, node);\n\t\t\treturn 0;\n\n\t\tcase SIOCGIFADDR6:\n\t\t\treturn -ENOENT;\n\t\tcase SIOCSIFADDR6:\n\t\t\tmemcpy(&nic->eth.ipv6_addr, argp, sizeof(nic->eth.ipv6_addr));\n\t\t\treturn 0;\n\n\t\tcase SIOCGIFFLAGS: {\n\t\t\tuint32_t * flags = argp;\n\t\t\t*flags = IFF_RUNNING;\n\t\t\t*flags |= IFF_UP;\n\t\t\t*flags |= IFF_LOOPBACK;\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase SIOCGIFMTU: {\n\t\t\tuint32_t * mtu = argp;\n\t\t\t*mtu = nic->eth.mtu;\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase SIOCGIFCOUNTS: {\n\t\t\tmemcpy(argp, &nic->counts, sizeof(netif_counters_t));\n\t\t\treturn 0;\n\t\t}\n\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic ssize_t write_loop(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct loop_nic * nic = node->device;\n\tnic->counts.rx_count++;\n\tnic->counts.tx_count++;\n\tnic->counts.rx_bytes += size;\n\tnic->counts.tx_bytes += size;\n\n\tnet_eth_handle((void*)buffer, node, size);\n\treturn size;\n}\n\nstatic void loop_init(struct loop_nic * nic) {\n\tnic->eth.device_node = calloc(sizeof(fs_node_t),1);\n\tsnprintf(nic->eth.device_node->name, 100, \"%s\", nic->eth.if_name);\n\tnic->eth.device_node->flags = FS_BLOCKDEVICE;\n\tnic->eth.device_node->mask  = 0666;\n\tnic->eth.device_node->ioctl = ioctl_loop;\n\tnic->eth.device_node->write = write_loop;\n\tnic->eth.device_node->device = nic;\n\tnic->eth.mtu = 65536; /* guess */\n\n\tnic->eth.ipv4_addr   = 0x0100007F;\n\tnic->eth.ipv4_subnet = 0x000000FF;\n\n\tnet_add_interface(nic->eth.if_name, nic->eth.device_node);\n}\n\nfs_node_t * loopbook_install(void) {\n\tstruct loop_nic * nic = calloc(1,sizeof(struct loop_nic));\n\tsnprintf(nic->eth.if_name, 31, \"lo\");\n\tloop_init(nic);\n\treturn nic->eth.device_node;\n\treturn 0;\n}\n\n"
  },
  {
    "path": "kernel/net/netif.c",
    "content": "/**\n * @file  kernel/net/netif.c\n * @brief Network interface manager.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n#include <kernel/list.h>\n#include <kernel/printf.h>\n#include <kernel/spinlock.h>\n#include <kernel/hashmap.h>\n#include <kernel/net/netif.h>\n\n#include <errno.h>\n\nstatic hashmap_t * interfaces = NULL;\nextern list_t * net_raw_sockets_list;\nstatic fs_node_t * _if_first = NULL;\nstatic fs_node_t * _if_loop = NULL;\n\nextern void ipv4_install(void);\nextern hashmap_t * net_arp_cache;\n\nextern fs_node_t * loopbook_install(void);\n\nvoid net_install(void) {\n\t/* Set up virtual devices */\n\tmap_vfs_directory(\"/dev/net\");\n\tinterfaces = hashmap_create(10);\n\tnet_raw_sockets_list = list_create(\"raw sockets\", NULL);\n\tnet_arp_cache = hashmap_create_int(10);\n\tipv4_install();\n\t_if_loop = loopbook_install();\n\t_if_first = NULL;\n}\n\n/* kinda temporary for now */\nint net_add_interface(const char * name, fs_node_t * deviceNode) {\n\thashmap_set(interfaces, name, deviceNode);\n\n\tchar tmp[100];\n\tsnprintf(tmp,100,\"/dev/net/%s\", name);\n\tvfs_mount(tmp, deviceNode, \"netif\", \"\");\n\n\tif (!_if_first) _if_first = deviceNode;\n\n\treturn 0;\n}\n\nfs_node_t * net_if_lookup(const char * name) {\n\treturn hashmap_get(interfaces, name);\n}\n\nfs_node_t * net_if_any(void) {\n\treturn _if_first;\n}\n\nfs_node_t * net_if_route(uint32_t addr) {\n\t/* First off, let's do stupid stuff. */\n\tif (addr == 0x0100007F) return _if_loop;\n\treturn _if_first;\n}\n"
  },
  {
    "path": "kernel/net/socket.c",
    "content": "/**\n * @file  kernel/net/socket.c\n * @brief Top-level socket manager.\n *\n * Provides the standard socket interface.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/list.h>\n#include <kernel/syscall.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n\n#include <kernel/net/netif.h>\n\n#include <sys/socket.h>\n#include <sys/ioctl.h>\n\n#ifndef MISAKA_DEBUG_NET\n#define printf(...)\n#endif\n\n/**\n * TODO: Should we have an interface for modules to install protocol handlers?\n *       Thinking this should work like the VFS, with method tables for different\n *       protocol handlers, but a lot of this stuff is also just generic...\n */\nextern long net_ipv4_socket(int,int);\n\nvoid net_sock_alert(sock_t * sock) {\n\tspin_lock(sock->alert_lock);\n\twhile (sock->alert_wait->head) {\n\t\tnode_t * node = list_dequeue(sock->alert_wait);\n\t\tprocess_t * p = node->value;\n\t\tfree(node);\n\t\tspin_unlock(sock->alert_lock);\n\t\tprocess_alert_node(p, (fs_node_t*)sock);\n\t\tspin_lock(sock->alert_lock);\n\t}\n\tspin_unlock(sock->alert_lock);\n}\n\nvoid net_sock_add(sock_t * sock, void * frame, size_t size) {\n\tspin_lock(sock->rx_lock);\n\tchar * bleh = malloc(size + sizeof(size_t));\n\t*(size_t*)bleh = size;\n\tmemcpy(bleh + sizeof(size_t), frame, size);\n\tlist_insert(sock->rx_queue, bleh);\n\twakeup_queue(sock->rx_wait);\n\tnet_sock_alert(sock);\n\tspin_unlock(sock->rx_lock);\n}\n\nvoid * net_sock_get(sock_t * sock) {\n\twhile (!sock->rx_queue->length) {\n\t\tif (sleep_on(sock->rx_wait)) {\n\t\t\tif (!sock->rx_queue->length)\n\t\t\t\treturn NULL;\n\t\t}\n\t}\n\n\tspin_lock(sock->rx_lock);\n\tnode_t * n = list_dequeue(sock->rx_queue);\n\tvoid* value = n->value;\n\tfree(n);\n\tspin_unlock(sock->rx_lock);\n\n\treturn value;\n}\n\nint sock_generic_check(fs_node_t *node) {\n\tsock_t * sock = (sock_t*)node;\n\tif (sock->rx_queue->length) return 0;\n\tif (sock->unread) return 0;\n\treturn 1;\n}\n\nint sock_generic_wait(fs_node_t *node, void * process) {\n\tsock_t * sock = (sock_t*)node;\n\n\tspin_lock(sock->alert_lock);\n\tif (!list_find(sock->alert_wait, process)) {\n\t\tlist_insert(sock->alert_wait, process);\n\t}\n\tlist_insert(((process_t *)process)->node_waits, sock);\n\tspin_unlock(sock->alert_lock);\n\treturn 0;\n}\n\nvoid sock_generic_close(fs_node_t *node) {\n\tsock_t * sock = (sock_t*)node;\n\tsock->sock_close(sock);\n\twhile (sock->rx_queue->length) {\n\t\tnode_t * n = list_dequeue(sock->rx_queue);\n\t\tfree(n->value);\n\t\tfree(n);\n\t}\n\tprintf(\"net: socket closed\\n\");\n}\n\nint sock_generic_ioctl(fs_node_t * node, unsigned long request, void * argp) {\n\tsock_t * sock = (sock_t*)node;\n\tswitch (request) {\n\t\tcase FIONBIO: {\n\t\t\tif (!mmu_validate_user_pointer(argp, sizeof(int), 0)) return -EFAULT;\n\t\t\tsock->nonblocking = (!!*(int*)argp);\n\t\t\treturn 0;\n\t\t}\n\t}\n\treturn -EINVAL;\n}\n\nsock_t * net_sock_create(void) {\n\tsock_t * sock = calloc(sizeof(struct SockData),1);\n\tsock->_fnode.flags = FS_SOCKET; /* uh, FS_SOCKET? */\n\tsock->_fnode.mask = 0600;\n\tsock->_fnode.device = NULL;\n\tsock->_fnode.selectcheck = sock_generic_check;\n\tsock->_fnode.selectwait = sock_generic_wait;\n\tsock->_fnode.close = sock_generic_close;\n\tsock->_fnode.ioctl = sock_generic_ioctl;\n\tsock->alert_wait = list_create(\"socket alert wait\", sock);\n\tsock->rx_wait    = list_create(\"socket rx wait\", sock);\n\tsock->rx_queue   = list_create(\"socket rx queue\", sock);\n\topen_fs((fs_node_t*)sock,0);\n\treturn sock;\n}\n\nspin_lock_t net_raw_sockets_lock = {0};\nlist_t * net_raw_sockets_list = NULL;\n\nstatic long sock_raw_recv(sock_t * sock, struct msghdr * msg, int flags) {\n\tif (!sock->_fnode.device) return -EINVAL;\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't recv multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\tchar * data = net_sock_get(sock);\n\tif (!data) return -EINTR;\n\tsize_t packet_size = *(size_t*)data;\n\tif (msg->msg_iov[0].iov_len < packet_size) {\n\t\tfree(data);\n\t\treturn -EINVAL;\n\t}\n\tmemcpy(msg->msg_iov[0].iov_base, data + sizeof(size_t), packet_size);\n\tfree(data);\n\treturn 4096;\n}\n\nstatic long sock_raw_send(sock_t * sock, const struct msghdr *msg, int flags) {\n\tif (!sock->_fnode.device) return -EINVAL;\n\tif (msg->msg_iovlen > 1) {\n\t\tprintf(\"net: todo: can't send multiple iovs\\n\");\n\t\treturn -ENOTSUP;\n\t}\n\tif (msg->msg_iovlen == 0) return 0;\n\treturn write_fs(sock->_fnode.device, 0, msg->msg_iov[0].iov_len, msg->msg_iov[0].iov_base);\n}\n\nstatic void sock_raw_close(sock_t * sock) {\n\tspin_lock(net_raw_sockets_lock);\n\tlist_delete(net_raw_sockets_list, list_find(net_raw_sockets_list, sock));\n\tspin_unlock(net_raw_sockets_lock);\n\n\t/* free stuff ? */\n}\n\n/**\n * Raw sockets\n */\nlong net_raw_socket(int type, int protocol) {\n\tif (type != SOCK_RAW) return -ESOCKTNOSUPPORT;\n\tif (this_core->current_process->user != 0) return -EACCES;\n\n\t/* Make a new raw socket? */\n\tsock_t * sock = net_sock_create();\n\tsock->sock_recv = sock_raw_recv;\n\tsock->sock_send = sock_raw_send;\n\tsock->sock_close = sock_raw_close;\n\n\tspin_lock(net_raw_sockets_lock);\n\tlist_insert(net_raw_sockets_list, sock);\n\tspin_unlock(net_raw_sockets_lock);\n\n\tint fd = process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock);\n\treturn fd;\n}\n\nlong net_socket(int domain, int type, int protocol) {\n\tswitch (domain) {\n\t\tcase AF_INET:\n\t\t\treturn net_ipv4_socket(type, protocol);\n\t\tcase AF_RAW:\n\t\t\treturn net_raw_socket(type, protocol);\n\t\tdefault:\n\t\t\treturn -EAFNOSUPPORT;\n\t}\n}\n\nlong net_so_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen) {\n\tswitch (optname) {\n\t\tcase SO_BINDTODEVICE: {\n\t\t\tif (optlen < 1 || optlen > 32 || ((const char*)optval)[optlen-1] != 0) return -EINVAL;\n\t\t\tfs_node_t * netif = net_if_lookup((const char*)optval);\n\t\t\tif (!netif) return -ENOENT;\n\t\t\tsock->_fnode.device = netif;\n\t\t\treturn 0;\n\t\t}\n\t\tdefault:\n\t\t\treturn -ENOPROTOOPT;\n\t}\n}\n\nextern long net_so_ipv4_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen);\n\nstatic inline int is_socket(int sockfd) {\n\tif (!FD_CHECK(sockfd)) return -EBADF;\n\tfs_node_t * node = FD_ENTRY(sockfd);\n\tif (!(node->flags & FS_SOCKET)) return -ENOTSOCK;\n\treturn 0;\n}\n\n#define CHECK_SOCK(sockfd) do { int x = is_socket(sockfd); if (x) return x; } while (0)\n\n#define ADDR_WR_ADDR 1\n#define ADDR_WR_LEN  2\nstatic inline int validate_addr_ptr(const struct sockaddr *addr, socklen_t * addrlen, int flags) {\n\tif (!mmu_validate_user_pointer(addrlen, sizeof(socklen_t), (flags & ADDR_WR_LEN) ? MMU_PTR_WRITE : 0)) return -EFAULT;\n\tif (!mmu_validate_user_pointer((void*)addr, *addrlen, (flags & ADDR_WR_ADDR) ? MMU_PTR_WRITE : 0)) return -EFAULT;\n\treturn 0;\n}\n\n#define CHECK_ADDR_ADDRLEN(addr,addrlen,flags) do { int x = validate_addr_ptr(addr,addrlen,flags); if (x) return x; } while (0)\n\nlong net_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {\n\tCHECK_SOCK(sockfd);\n\tPTR_VALIDATE(optval);\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\tswitch (level) {\n\t\tcase SOL_SOCKET:\n\t\t\treturn net_so_socket(node,optname,optval,optlen);\n\t\tcase IPPROTO_IP:\n\t\t\treturn net_so_ipv4_socket(node,optname,optval,optlen);\n\t\tdefault:\n\t\t\treturn -ENOPROTOOPT;\n\t}\n\treturn -EINVAL;\n}\n\nlong net_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {\n\tCHECK_SOCK(sockfd);\n\treturn -EINVAL;\n}\n\nlong net_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n\tCHECK_SOCK(sockfd);\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\tif (!node->sock_bind) return -EINVAL;\n\treturn node->sock_bind(node, addr, addrlen);\n}\n\nlong net_accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen) {\n\tCHECK_SOCK(sockfd);\n\treturn -EINVAL;\n}\n\nlong net_listen(int sockfd, int backlog) {\n\tCHECK_SOCK(sockfd);\n\treturn -EINVAL;\n}\n\nlong net_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n\tCHECK_SOCK(sockfd);\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\tif (!node->sock_connect) return -EINVAL;\n\treturn node->sock_connect(node,addr,addrlen);\n}\n\nstatic int validate_msg(const struct msghdr * msg, int readonly) {\n\tint flags = readonly ? 0 : MMU_PTR_WRITE;\n\tif (!mmu_validate_user_pointer(msg,sizeof(struct msghdr),flags)) return 1;\n\tif (msg->msg_iovlen) {\n\t\t/* Check msg_name pointer */\n\t\tif (msg->msg_name && !mmu_validate_user_pointer(msg->msg_name, (size_t)(msg->msg_namelen), flags)) return 1;\n\t\t/* Check iovec structures */\n\t\tif (!mmu_validate_user_pointer(msg->msg_iov, (size_t)(msg->msg_iovlen * sizeof(struct iovec)),flags)) return 1;\n\t\t/* Check all the buffers in there */\n\t\tfor (size_t i = 0; i < msg->msg_iovlen; ++i) {\n\t\t\tif (!mmu_validate_user_pointer(msg->msg_iov[i].iov_base, (size_t)(msg->msg_iov[i].iov_len), flags)) return 1;\n\t\t}\n\t}\n\n\t/* Check control message space */\n\tif (msg->msg_controllen && !mmu_validate_user_pointer(msg->msg_control, (size_t)(msg->msg_controllen), flags)) return 1;\n\treturn 0;\n}\n\nlong net_recv(int sockfd, struct msghdr * msg, int flags) {\n\tCHECK_SOCK(sockfd);\n\tPTR_VALIDATE(msg);\n\tif (validate_msg(msg,0)) return -EFAULT;\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\treturn node->sock_recv(node,msg,flags);\n}\n\nlong net_send(int sockfd, const struct msghdr * msg, int flags) {\n\tCHECK_SOCK(sockfd);\n\tPTR_VALIDATE(msg);\n\tif (validate_msg(msg,1)) return -EFAULT;\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\treturn node->sock_send(node,msg,flags);\n}\n\nlong net_shutdown(int sockfd, int how) {\n\treturn -EINVAL;\n}\n\nlong net_getsockname(int sockfd, struct sockaddr *addr, socklen_t * addrlen) {\n\tCHECK_SOCK(sockfd);\n\tCHECK_ADDR_ADDRLEN(addr,addrlen,ADDR_WR_ADDR|ADDR_WR_LEN);\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\tif (!node->sock_getsockname) return -EINVAL;\n\treturn node->sock_getsockname(node, addr, addrlen);\n}\n\nlong net_getpeername(int sockfd, struct sockaddr *addr, socklen_t * addrlen) {\n\tCHECK_SOCK(sockfd);\n\tCHECK_ADDR_ADDRLEN(addr,addrlen,ADDR_WR_ADDR|ADDR_WR_LEN);\n\tsock_t * node = (sock_t*)FD_ENTRY(sockfd);\n\tif (!node->sock_getpeername) return -EINVAL;\n\treturn node->sock_getpeername(node, addr, addrlen);\n}\n"
  },
  {
    "path": "kernel/sys/mutex.c",
    "content": "/**\n * Mutex that sleeps... and can be owned across sleeping...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange <klange@toaruos.org>\n */\n#include <kernel/types.h>\n#include <kernel/printf.h>\n#include <kernel/time.h>\n#include <kernel/string.h>\n#include <kernel/spinlock.h>\n#include <kernel/assert.h>\n#include <kernel/process.h>\n#include <kernel/list.h>\n#include <kernel/mutex.h>\n\nextern int wakeup_queue_one(list_t * queue);\n\nsched_mutex_t * mutex_init(const char * name) {\n\tsched_mutex_t * out = malloc(sizeof(sched_mutex_t));\n\tspin_init(out->inner_lock);\n\tout->status = 0;\n\tout->owner = NULL;\n\tout->waiters = list_create(name, out);\n\n\treturn out;\n}\n\nint mutex_acquire(sched_mutex_t * mutex) {\n\tspin_lock(mutex->inner_lock);\n\twhile (mutex->status) {\n\t\tsleep_on_unlocking(mutex->waiters, &mutex->inner_lock);\n\t\tspin_lock(mutex->inner_lock);\n\t}\n\tmutex->status = 1;\n\tmutex->owner  = (process_t*)this_core->current_process;\n\tspin_unlock(mutex->inner_lock);\n\treturn 0;\n}\n\nint mutex_release(sched_mutex_t * mutex) {\n\tassert(mutex->owner == this_core->current_process);\n\tspin_lock(mutex->inner_lock);\n\tmutex->owner  = NULL;\n\tmutex->status = 0;\n\twakeup_queue_one(mutex->waiters);\n\tspin_unlock(mutex->inner_lock);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "kernel/sys/process.c",
    "content": "/**\n * @file   kernel/sys/process.c\n * @brief  Task switch and thread scheduling.\n *\n * Implements the primary scheduling primitives for the kernel.\n *\n * Generally, what the kernel refers to as a \"process\" is an individual thread.\n * The POSIX concept of a \"process\" is represented in Misaka as a collection of\n * threads and their shared paging, signal, and file descriptor tables.\n *\n * Kernel threads are also \"processes\", referred to as \"tasklets\".\n *\n * Misaka allows nested kernel preemption, and task switching involves saving\n * kernel state in a manner similar to setjmp/longjmp, as well as saving the\n * outer context in the case of a nested task switch.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n * Copyright (C) 2012 Markus Schober\n * Copyright (C) 2015 Dale Weiler\n */\n#include <errno.h>\n#include <kernel/assert.h>\n#include <kernel/process.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/vfs.h>\n#include <kernel/spinlock.h>\n#include <kernel/tree.h>\n#include <kernel/list.h>\n#include <kernel/mmu.h>\n#include <kernel/shm.h>\n#include <kernel/signal.h>\n#include <kernel/time.h>\n#include <kernel/misc.h>\n#include <kernel/syscall.h>\n#include <sys/wait.h>\n#include <sys/signal_defs.h>\n\n/* FIXME: This only needs the size of the regs struct... */\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/regs.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/regs.h>\n#else\n#error \"no regs\"\n#endif\n\ntree_t * process_tree;  /* Stores the parent-child process relationships; the root of this graph is 'init'. */\nlist_t * process_list;  /* Stores all existing processes. Mostly used for sanity checking or for places where iterating over all processes is useful. */\nlist_t * process_queue; /* Scheduler ready queue. This the round-robin source. The head is the next process to run. */\nlist_t * sleep_queue;   /* Ordered list of processes waiting to be awoken by timeouts. The head is the earliest thread to awaken. */\nlist_t * reap_queue;    /* Processes that could not be cleaned up and need to be deleted. */\n\nstruct ProcessorLocal processor_local_data[32] = {0};\nint processor_count = 1;\n\n/* The following locks protect access to the process tree, scheduler queue,\n * sleeping, and the very special wait queue... */\nstatic spin_lock_t tree_lock = { 0 };\nstatic spin_lock_t process_queue_lock = { 0 };\nstatic spin_lock_t wait_lock_tmp = { 0 };\nstatic spin_lock_t sleep_lock = { 0 };\nstatic spin_lock_t reap_lock = { 0 };\n\n/**\n * Update both the total time and the system time when switching to a new thread\n * or exiting the current thread.\n */\nvoid update_process_times(void) {\n\tuint64_t pTime = arch_perf_timer();\n\tif (this_core->current_process->time_in && this_core->current_process->time_in < pTime) {\n\t\tthis_core->current_process->time_total +=  pTime - this_core->current_process->time_in;\n\t}\n\tthis_core->current_process->time_in = 0;\n\n\tif (this_core->current_process->time_switch && this_core->current_process->time_switch < pTime) {\n\t\tthis_core->current_process->time_sys += pTime - this_core->current_process->time_switch;\n\t}\n\tthis_core->current_process->time_switch = 0;\n}\n\n/**\n * Add time spent in kernel to time_sys when returning to userspace, such as through\n * an interrupt return or entry into a signal handler.\n */\nvoid update_process_times_on_exit(void) {\n\tuint64_t pTime = arch_perf_timer();\n\tif (this_core->current_process->time_switch && this_core->current_process->time_switch < pTime) {\n\t\tthis_core->current_process->time_sys += pTime - this_core->current_process->time_switch;\n\t}\n\tthis_core->current_process->time_switch = 0;\n}\n\n#define must_have_lock(lck) if (lck.owner != this_core->cpu_id+1) { arch_fatal_prepare(); printf(\"Failed lock check.\\n\"); arch_dump_traceback(); arch_fatal(); }\n\n/**\n * @brief Restore the context of the next available process's kernel thread.\n *\n * Loads the next ready process from the scheduler queue and resumes it.\n *\n * If no processes are available, the local idle task will be run from the beginning\n * of its function entry.\n *\n * If the next process in the queue has been marked as finished, it will be discard\n * until a non-finished process is found.\n *\n * If the next process is new, it will be marked as started, and its entry point\n * jumped to.\n *\n * For all other cases, the process's stored kernel thread state will be restored\n * and execution will contain in @ref switch_task with a return value of 1.\n *\n * Note that switch_next does not return and should be called only when the current\n * process has been properly added to a scheduling queue, or marked as awaiting cleanup,\n * otherwise its return state if resumed is undefined and generally whatever the state\n * was when that process last entered switch_task.\n *\n * @returns never.\n */\nvoid switch_next(void) {\n\tthis_core->previous_process = this_core->current_process;\n\tupdate_process_times();\n\n\t/* Get the next available process, discarded anything in the queue\n\t * marked as finished. */\n\tdo {\n\t\tthis_core->current_process = next_ready_process();\n\t} while (this_core->current_process->flags & PROC_FLAG_FINISHED);\n\n\tthis_core->current_process->time_in = arch_perf_timer();\n\tthis_core->current_process->time_switch = this_core->current_process->time_in;\n\n\t/* Restore paging and task switch context. */\n\tmmu_set_directory(this_core->current_process->thread.page_directory->directory);\n\tarch_set_kernel_stack(this_core->current_process->image.stack);\n\n\tif (this_core->current_process->flags & PROC_FLAG_FINISHED) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"Should not have this process...\\n\");\n\t\tif (this_core->current_process->flags & PROC_FLAG_FINISHED) printf(\"It is marked finished.\\n\");\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t\t__builtin_unreachable();\n\t}\n\n\t/* Mark the process as running and started. */\n\t__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_STARTED);\n\n\tasm volatile (\"\" ::: \"memory\");\n\n\t/* Jump to next */\n\tarch_restore_context(&this_core->current_process->thread);\n\t__builtin_unreachable();\n}\n\n/**\n * @brief Yield the processor to the next available task.\n *\n * Yields the current process, allowing the next to run. Can be called both as\n * part of general preemption or from blocking tasks; in the latter case,\n * the process should be added to a scheduler queue to be awakoen later when the\n * blocking operation is completed and @p reschedule should be set to 0.\n *\n * @param reschedule Non-zero if this process should be added to the ready queue.\n */\nvoid switch_task(uint8_t reschedule) {\n\n\t/* switch_task() called but the scheduler isn't enabled? Resume... this is probably a bug. */\n\tif (!this_core->current_process) return;\n\n\tif (this_core->current_process == this_core->kernel_idle_task) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"Context switch from kernel_idle_task triggered from somewhere other than pre-emption source. Halting.\\n\");\n\t\tprintf(\"This generally means that a driver responding to interrupts has attempted to yield in its interrupt context.\\n\");\n\t\tprintf(\"Ensure that all device drivers which respond to interrupts do so with non-blocking data structures.\\n\");\n\t\tprintf(\"   Return address of switch_task: %p\\n\", __builtin_return_address(0));\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\n\t/* If a process got to switch_task but was not marked as running, it must be exiting and we don't\n\t * want to waste time saving context for it. Also, kidle is always resumed from the top of its\n\t * loop function, so we don't save any context for it either. */\n\tif (!(this_core->current_process->flags & PROC_FLAG_RUNNING) || (this_core->current_process == this_core->kernel_idle_task)) {\n\t\tswitch_next();\n\t\treturn;\n\t}\n\n\tarch_save_floating((process_t*)this_core->current_process);\n\n\t/* 'setjmp' - save the execution context. When this call returns '1' we are back\n\t * from a task switch and have been awoken if we were sleeping. */\n\tif (arch_save_context(&this_core->current_process->thread) == 1) {\n\t\tarch_restore_floating((process_t*)this_core->current_process);\n\t\treturn;\n\t}\n\n\t/* If this is a normal yield, we reschedule.\n\t * XXX: Is this going to work okay with SMP? I think this whole thing\n\t *      needs to be wrapped in a lock, but also what if we put the\n\t *      thread into a schedule queue previously but a different core\n\t *      picks it up before we saved the thread context or the FPU state... */\n\tif (reschedule) {\n\t\tmake_process_ready((process_t*)this_core->current_process);\n\t}\n\n\t/* @ref switch_next() does not return. */\n\tswitch_next();\n}\n\n/**\n * @brief Initial scheduler datastructures.\n *\n * Called by early system startup to allocate trees and lists\n * the schedule uses to track processes.\n */\nvoid initialize_process_tree(void) {\n\tprocess_tree = tree_create();\n\tprocess_list = list_create(\"global process list\",NULL);\n\tprocess_queue = list_create(\"global scheduler queue\",NULL);\n\tsleep_queue = list_create(\"global timed sleep queue\",NULL);\n\treap_queue = list_create(\"processes awaiting later cleanup\",NULL);\n\n\t/* TODO: PID bitset? */\n}\n\n/**\n * @brief Determines if a process is alive and valid.\n *\n * Scans @ref process_list to see if @p process is a valid\n * process object or not.\n *\n * XXX This is horribly inefficient, and its very existence\n *     is likely indicative of bugs whereever it needed to\n *     be called...\n *\n * @param process Process object to check.\n * @returns 1 if the process is valid, 0 if it is not.\n */\nint is_valid_process(process_t * process) {\n\tforeach(lnode, process_list) {\n\t\tif (lnode->value == process) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * @brief Grow the FD table for a process by doubling its capacity.\n */\nstatic void process_fds_grow(process_t * proc) {\n\tproc->fds->capacity *= 2;\n\tproc->fds->entries = realloc(proc->fds->entries, sizeof(fs_node_t *) * proc->fds->capacity);\n\tproc->fds->modes   = realloc(proc->fds->modes,   sizeof(int) * proc->fds->capacity);\n\tproc->fds->offsets = realloc(proc->fds->offsets, sizeof(uint64_t) * proc->fds->capacity);\n}\n\n/**\n * @brief Duplicate a file descriptor to a new table entry.\n */\nstatic void process_fds_copy(process_t * proc, long src, long dest) {\n\tproc->fds->entries[dest] = proc->fds->entries[src];\n\tproc->fds->modes[dest] = proc->fds->modes[src];\n\tproc->fds->offsets[dest] = proc->fds->offsets[src];\n\topen_fs(proc->fds->entries[dest], 0);\n}\n\n/**\n * @brief Allocate a new file descriptor.\n *\n * Adds a new entry to the file descriptor table for @p proc\n * pointing to the file @p node. The file descriptor's offset\n * and file modes must be set by the caller afterwards.\n *\n * @param proc Process whose file descriptor should be modified.\n * @param node VFS object to add a reference to.\n * @returns the new file descriptor index\n */\nunsigned long process_append_fd(process_t * proc, fs_node_t * node) {\n\tspin_lock(proc->fds->lock);\n\t/* Fill gaps */\n\tfor (unsigned long i = 0; i < proc->fds->length; ++i) {\n\t\tif (!proc->fds->entries[i]) {\n\t\t\tproc->fds->entries[i] = node;\n\t\t\t/* modes, offsets must be set by caller */\n\t\t\tproc->fds->modes[i] = 0;\n\t\t\tproc->fds->offsets[i] = 0;\n\t\t\tspin_unlock(proc->fds->lock);\n\t\t\treturn i;\n\t\t}\n\t}\n\t/* No gaps, expand */\n\tif (proc->fds->length == proc->fds->capacity) {\n\t\tprocess_fds_grow(proc);\n\t}\n\tproc->fds->entries[proc->fds->length] = node;\n\t/* modes, offsets must be set by caller */\n\tproc->fds->modes[proc->fds->length] = 0;\n\tproc->fds->offsets[proc->fds->length] = 0;\n\tproc->fds->length++;\n\tspin_unlock(proc->fds->lock);\n\treturn proc->fds->length-1;\n}\n\n/**\n * @brief Duplicate a file descriptor to a minimum value.\n *\n * Duplicate the file descriptor at @c oldfd to a new file descriptor\n * with a number of a least @c newfd - always allocating a new\n * file descriptor in the process.\n *\n * If the current table can not hold @c newfd then the table is\n * expanded and any unused entries are left empty and will\n * be filled by @c process_append_fd normally.\n *\n * @param proc Process to modify.\n * @param oldfd File descriptor to copy from.\n * @param newfd Minimum value of new file descriptor.\n * @returns The actual newly allocated file descriptor.\n */\nlong process_fd_dup_least(process_t * proc, long oldfd, long newfd) {\n\tspin_lock(proc->fds->lock);\n\n\t/* Ensure there is at least enough space for for newfd itself */\n\twhile (proc->fds->capacity <= (unsigned long)newfd) {\n\t\tprocess_fds_grow(proc);\n\t}\n\n\t/* Then ensure length is at least newfds */\n\twhile (proc->fds->length <= (unsigned long)newfd) {\n\t\tproc->fds->entries[proc->fds->length] = NULL;\n\t\tproc->fds->length++;\n\t}\n\n\t/* Then check if anything is available already */\n\tfor (size_t i = newfd; i < proc->fds->length; ++i) {\n\t\tif (proc->fds->entries[i] == NULL) {\n\t\t\t/* Found a hit, copy */\n\t\t\tprocess_fds_copy(proc, oldfd, i);\n\t\t\tspin_unlock(proc->fds->lock);\n\t\t\treturn i;\n\t\t}\n\t}\n\n\t/* Otherwise we need to keep expanding */\n\tif (proc->fds->length == proc->fds->capacity) {\n\t\tprocess_fds_grow(proc);\n\t}\n\n\tlong i = proc->fds->length;\n\tprocess_fds_copy(proc, oldfd, i);\n\tproc->fds->length++;\n\tspin_unlock(proc->fds->lock);\n\n\treturn i;\n}\n\n/**\n * @brief Allocate a process identifier.\n *\n * Obtains the next available process identifier.\n *\n * FIXME This used to use a bitset in Toaru32 so it could\n *       handle overflow of the pid counter. We need to\n *       bring that back.\n */\npid_t get_next_pid(void) {\n\tstatic pid_t _next_pid = 2;\n\treturn __sync_fetch_and_add(&_next_pid,1);\n}\n\n/**\n * @brief The idle task.\n *\n * Sits in a loop forever. Scheduled whenever there is nothing\n * else to do. Actually always enters from the top of the function\n * whenever scheduled, as we don't both to save its state.\n */\nstatic void _kidle(void) {\n\twhile (1) {\n\t\tarch_pause();\n\t\tswitch_next();\n\t}\n}\n\n/**\n * @brief Release a process's paging data.\n *\n * If this is a thread in a POSIX process with other\n * living threads, the directory is not actually released\n * but the reference count for it is decremented.\n *\n * XXX There's probably no reason for this to take an argument;\n *     we only ever free directories in two places: on exec, or\n *     when a thread exits, and that's always the current thread.\n */\nvoid process_release_directory(page_directory_t * dir) {\n\tspin_lock(dir->lock);\n\tdir->refcount--;\n\tif (dir->refcount < 1) {\n\t\tmmu_free(dir->directory);\n\t\tfree(dir);\n\t} else {\n\t\tspin_unlock(dir->lock);\n\t}\n}\n\nprocess_t * spawn_kidle(int bsp) {\n\tprocess_t * idle = calloc(1,sizeof(process_t));\n\tidle->id = -1;\n\tidle->name = strdup(\"[kidle]\");\n\tidle->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED | PROC_FLAG_RUNNING;\n\tidle->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE)+ KERNEL_STACK_SIZE;\n\tmmu_frame_allocate(\n\t\tmmu_get_page(idle->image.stack - KERNEL_STACK_SIZE, 0),\n\t\tMMU_FLAG_KERNEL);\n\n\t/* TODO arch_initialize_context(uintptr_t) ? */\n\tidle->thread.context.ip = (uintptr_t)&_kidle;\n\tidle->thread.context.sp = idle->image.stack;\n\tidle->thread.context.bp = idle->image.stack;\n\n\t/* FIXME Why does the idle thread have wait queues and shm mappings?\n\t *       Can we make sure these are never referenced and not allocate them? */\n\tidle->wait_queue = list_create(\"process wait queue (kidle)\",idle);\n\tidle->shm_mappings = list_create(\"process shm mappings (kidle)\",idle);\n\tgettimeofday(&idle->start, NULL);\n\tidle->thread.page_directory = malloc(sizeof(page_directory_t));\n\tidle->thread.page_directory->refcount = 1;\n\tidle->thread.page_directory->directory = mmu_clone(this_core->current_pml);\n\tspin_init(idle->thread.page_directory->lock);\n\treturn idle;\n}\n\nprocess_t * spawn_init(void) {\n\tprocess_t * init = calloc(1,sizeof(process_t));\n\ttree_set_root(process_tree, (void*)init);\n\n\tinit->tree_entry = process_tree->root;\n\tinit->id         = 1;\n\tinit->group      = 0;\n\tinit->job        = 1;\n\tinit->session    = 1;\n\tinit->name       = strdup(\"init\");\n\tinit->cmdline    = NULL;\n\tinit->user       = USER_ROOT_UID;\n\tinit->real_user  = USER_ROOT_UID;\n\tinit->user_group = USER_ROOT_UID;\n\tinit->real_user_group = USER_ROOT_UID;\n\tinit->mask       = 022;\n\tinit->status     = 0;\n\n\tinit->fds           = malloc(sizeof(fd_table_t));\n\tinit->fds->refs     = 1;\n\tinit->fds->length   = 0;\n\tinit->fds->capacity = 4;\n\tinit->fds->entries  = malloc(init->fds->capacity * sizeof(fs_node_t *));\n\tinit->fds->modes    = malloc(init->fds->capacity * sizeof(int));\n\tinit->fds->offsets  = malloc(init->fds->capacity * sizeof(uint64_t));\n\tspin_init(init->fds->lock);\n\n\tinit->wd_node = clone_fs(fs_root);\n\tinit->wd_name = strdup(\"/\");\n\n\tinit->image.entry    = 0;\n\tinit->image.heap     = 0;\n\tinit->image.stack    = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;\n\tmmu_frame_allocate(\n\t\tmmu_get_page(init->image.stack - KERNEL_STACK_SIZE, 0),\n\t\tMMU_FLAG_KERNEL);\n\tinit->image.shm_heap = USER_SHM_LOW;\n\n\tinit->flags         = PROC_FLAG_STARTED | PROC_FLAG_RUNNING;\n\tinit->wait_queue    = list_create(\"process wait queue (init)\", init);\n\tinit->shm_mappings  = list_create(\"process shm mapping (init)\", init);\n\n\tinit->sched_node.prev = NULL;\n\tinit->sched_node.next = NULL;\n\tinit->sched_node.value = init;\n\n\tinit->sleep_node.prev = NULL;\n\tinit->sleep_node.next = NULL;\n\tinit->sleep_node.value = init;\n\n\tinit->timed_sleep_node = NULL;\n\n\tinit->thread.page_directory = malloc(sizeof(page_directory_t));\n\tinit->thread.page_directory->refcount = 1;\n\tinit->thread.page_directory->directory = this_core->current_pml;\n\tspin_init(init->thread.page_directory->lock);\n\tinit->description = strdup(\"[init]\");\n\tlist_insert(process_list, (void*)init);\n\n\treturn init;\n}\n\nprocess_t * spawn_process(volatile process_t * parent, int flags) {\n\tprocess_t * proc = calloc(1,sizeof(process_t));\n\n\tproc->id          = get_next_pid();\n\tproc->group       = proc->id;\n\tproc->name        = strdup(parent->name);\n\tproc->description = NULL;\n\tproc->cmdline     = parent->cmdline; /* FIXME dup it? */\n\n\tproc->user        = parent->user;\n\tproc->real_user   = parent->real_user;\n\tproc->user_group  = parent->user_group;\n\tproc->real_user_group = parent->real_user_group;\n\tproc->mask        = parent->mask;\n\tproc->job         = parent->job;\n\tproc->session     = parent->session;\n\n\tif (parent->supplementary_group_count) {\n\t\tproc->supplementary_group_count = parent->supplementary_group_count;\n\t\tproc->supplementary_group_list = malloc(sizeof(gid_t) * proc->supplementary_group_count);\n\t\tfor (int i = 0; i < proc->supplementary_group_count; ++i) {\n\t\t\tproc->supplementary_group_list[i] = parent->supplementary_group_list[i];\n\t\t}\n\t}\n\n\tproc->thread.context.sp = 0;\n\tproc->thread.context.bp = 0;\n\tproc->thread.context.ip = 0;\n\tmemcpy((void*)proc->thread.fp_regs, (void*)parent->thread.fp_regs, 512);\n\n\t/* Entry is only stored for reference. */\n\tproc->image.entry       = parent->image.entry;\n\tproc->image.heap        = parent->image.heap;\n\tproc->image.stack       = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;\n\tmmu_frame_allocate(\n\t\tmmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0),\n\t\tMMU_FLAG_KERNEL);\n\tproc->image.shm_heap    = USER_SHM_LOW;\n\n\tif (flags & PROC_REUSE_FDS) {\n\t\tspin_lock(parent->fds->lock);\n\t\tproc->fds = parent->fds;\n\t\tproc->fds->refs++;\n\t\tspin_unlock(parent->fds->lock);\n\t} else {\n\t\tproc->fds = malloc(sizeof(fd_table_t));\n\t\tspin_init(proc->fds->lock);\n\t\tproc->fds->refs = 1;\n\t\tspin_lock(parent->fds->lock);\n\t\tproc->fds->length = parent->fds->length;\n\t\tproc->fds->capacity = parent->fds->capacity;\n\t\tproc->fds->entries = malloc(proc->fds->capacity * sizeof(fs_node_t *));\n\t\tproc->fds->modes   = malloc(proc->fds->capacity * sizeof(int));\n\t\tproc->fds->offsets = malloc(proc->fds->capacity * sizeof(uint64_t));\n\t\tfor (uint32_t i = 0; i < parent->fds->length; ++i) {\n\t\t\tproc->fds->entries[i] = clone_fs(parent->fds->entries[i]);\n\t\t\tproc->fds->modes[i]   = parent->fds->modes[i];\n\t\t\tproc->fds->offsets[i] = parent->fds->offsets[i];\n\t\t}\n\t\tspin_unlock(parent->fds->lock);\n\t}\n\n\tproc->wd_node = clone_fs(parent->wd_node);\n\tproc->wd_name = strdup(parent->wd_name);\n\n\tproc->wait_queue   = list_create(\"process wait queue\",proc);\n\tproc->shm_mappings = list_create(\"process shm mappings\",proc);\n\n\tproc->sched_node.value = proc;\n\tproc->sleep_node.value = proc;\n\n\tgettimeofday(&proc->start, NULL);\n\ttree_node_t * entry = tree_node_create(proc);\n\tproc->tree_entry = entry;\n\n\tspin_lock(tree_lock);\n\ttree_node_insert_child_node(process_tree, parent->tree_entry, entry);\n\tlist_insert(process_list, (void*)proc);\n\tspin_unlock(tree_lock);\n\treturn proc;\n}\n\nextern void tree_remove_reparent_root(tree_t * tree, tree_node_t * node);\n\nvoid process_reap(process_t * proc) {\n\tif (proc->tracees) {\n\t\twhile (proc->tracees->length) {\n\t\t\tfree(list_pop(proc->tracees));\n\t\t}\n\t\tfree(proc->tracees);\n\t}\n\n\t/* Unmark the stack bottom's fault detector */\n\tmmu_frame_allocate(\n\t\tmmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0),\n\t\tMMU_FLAG_KERNEL | MMU_FLAG_WRITABLE);\n\n\tfree((void *)(proc->image.stack - KERNEL_STACK_SIZE));\n\tprocess_release_directory(proc->thread.page_directory);\n\n\tfree(proc->name);\n\tfree(proc);\n}\n\nstatic int process_is_owned(process_t * proc) {\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tif (processor_local_data[i].previous_process == proc ||\n\t\t    processor_local_data[i].current_process == proc) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid process_reap_later(process_t * proc) {\n\tspin_lock(reap_lock);\n\t/* See if we can delete anything */\n\twhile (reap_queue->head) {\n\t\tprocess_t * proc = reap_queue->head->value;\n\t\tif (!process_is_owned(proc)) {\n\t\t\tfree(list_dequeue(reap_queue));\n\t\t\tprocess_reap(proc);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\t/* And delete this thing later */\n\tlist_insert(reap_queue, proc);\n\tspin_unlock(reap_lock);\n}\n\n/**\n * @brief Remove a process from the valid process list.\n *\n * Deletes a process from both the valid list and the process tree.\n * Any the process has any children, they become orphaned and are\n * moved under 'init', which is awoken if it was blocked on 'waitpid'.\n *\n * Finally, the process is freed.\n */\nvoid process_delete(process_t * proc) {\n\tassert(proc != this_core->current_process);\n\n\ttree_node_t * entry = proc->tree_entry;\n\tif (!entry) {\n\t\tprintf(\"Tried to delete process with no tree entry?\\n\");\n\t\treturn;\n\t}\n\tif (process_tree->root == entry) {\n\t\tprintf(\"Tried to delete process init...\\n\");\n\t\treturn;\n\t}\n\n\tspin_lock(tree_lock);\n\tint has_children = entry->children->length;\n\ttree_remove_reparent_root(process_tree, entry);\n\tlist_delete(process_list, list_find(process_list, proc));\n\tspin_unlock(tree_lock);\n\n\tif (has_children) {\n\t\t/* Wake up init */\n\t\tprocess_t * init = process_tree->root->value;\n\t\twakeup_queue(init->wait_queue);\n\t}\n\n\t// FIXME bitset_clear(&pid_set, proc->id);\n\tproc->tree_entry = NULL;\n\n\tshm_release_all(proc);\n\tfree(proc->shm_mappings);\n\n\tif (proc->supplementary_group_list) {\n\t\tproc->supplementary_group_count = 0;\n\t\tfree(proc->supplementary_group_list);\n\t}\n\n\t/* Is someone using this process? */\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tif (i == this_core->cpu_id) continue;\n\t\tif (processor_local_data[i].previous_process == proc ||\n\t\t    processor_local_data[i].current_process == proc) {\n\t\t\tprocess_reap_later(proc);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tprocess_reap(proc);\n}\n\n/**\n * @brief Place an available process in the ready queue.\n *\n * Marks a process as available for general scheduling.\n * If the process was currently in a sleep queue, it is\n * marked as having been interrupted and removed from its\n * owning queue before being moved.\n *\n * The process must not otherwise have been in a scheduling\n * queue before it is placed in the ready queue.\n */\nvoid make_process_ready(volatile process_t * proc) {\n\tint sleep_lock_is_mine = sleep_lock.owner == (this_core->cpu_id + 1);\n\tif (!sleep_lock_is_mine) spin_lock(sleep_lock);\n\tif (proc->sleep_node.owner != NULL) {\n\t\tif (proc->sleep_node.owner == sleep_queue) {\n\t\t\t/* The sleep queue is slightly special... */\n\t\t\tif (proc->timed_sleep_node) {\n\t\t\t\tlist_delete(sleep_queue, proc->timed_sleep_node);\n\t\t\t\tproc->sleep_node.owner = NULL;\n\t\t\t\tfree(proc->timed_sleep_node->value);\n\t\t\t}\n\t\t} else {\n\t\t\t/* This was blocked on a semaphore we can interrupt. */\n\t\t\t__sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT);\n\t\t\tlist_delete((list_t*)proc->sleep_node.owner, (node_t*)&proc->sleep_node);\n\t\t}\n\t}\n\tif (!sleep_lock_is_mine) spin_unlock(sleep_lock);\n\n\tspin_lock(process_queue_lock);\n\tif (proc->sched_node.owner) {\n\t\t/* There's only one ready queue, so this means the process was already ready, which\n\t\t * is indicative of a bug somewhere as we shouldn't be added processes to the ready\n\t\t * queue multiple times. */\n\t\tspin_unlock(process_queue_lock);\n\t\treturn;\n\t}\n\n\tlist_append(process_queue, (node_t*)&proc->sched_node);\n\tspin_unlock(process_queue_lock);\n\n\tarch_wakeup_others();\n}\n\n/**\n * @brief Pop the next available process from the queue.\n *\n * Gets the next available process from the round-robin scheduling\n * queue. If there is no process to run, the idle task is returned.\n *\n * TODO This needs more locking for SMP...\n */\nvolatile process_t * next_ready_process(void) {\n\tspin_lock(process_queue_lock);\n\n\tif (!process_queue->head) {\n\t\tif (process_queue->length) {\n\t\t\tarch_fatal_prepare();\n\t\t\tprintf(\"Queue has a length but head is NULL\\n\");\n\t\t\tarch_dump_traceback();\n\t\t\tarch_fatal();\n\t\t}\n\t\tspin_unlock(process_queue_lock);\n\t\treturn this_core->kernel_idle_task;\n\t}\n\n\tnode_t * np = list_dequeue(process_queue);\n\n\tif ((uintptr_t)np < 0xFFFFff0000000000UL || (uintptr_t)np > 0xFFFFfff000000000UL) {\n\t\tarch_fatal_prepare();\n\t\tprintf(\"Suspicious pointer in queue: %#zx\\n\", (uintptr_t)np);\n\t\tarch_dump_traceback();\n\t\tarch_fatal();\n\t}\n\tvolatile process_t * next = np->value;\n\n\tif ((next->flags & PROC_FLAG_RUNNING) && (next->owner != this_core->cpu_id)) {\n\t\t/* We pulled a process too soon, switch to idle for a bit so the\n\t\t * core that marked this process as ready can finish switching away from it. */\n\t\tlist_append(process_queue, (node_t*)&next->sched_node);\n\t\tspin_unlock(process_queue_lock);\n\t\treturn this_core->kernel_idle_task;\n\t}\n\n\tspin_unlock(process_queue_lock);\n\n\tif (!(next->flags & PROC_FLAG_FINISHED)) {\n\t\t__sync_or_and_fetch(&next->flags, PROC_FLAG_RUNNING);\n\t}\n\n\tnext->owner = this_core->cpu_id;\n\n\treturn next;\n}\n\n/**\n * @brief Signal a semaphore.\n *\n * Okay, so toaru32 used these general-purpose lists of processes\n * as a sort of sempahore system, so often when you see 'queue' it\n * can be read as 'semaphore' and be equally valid (outside of the\n * 'ready queue', I guess). This will awaken all processes currently\n * in the semaphore @p queue, unless they were marked as finished in\n * which case they will be discarded.\n *\n * Note that these \"semaphore queues\" are binary semaphores - simple\n * locks, but with smarter logic than the \"spin_lock\" primitive also\n * used throughout the kernel, as that just blindly switches tasks\n * until its atomic swap succeeds.\n *\n * @param queue The semaphore to signal\n * @returns the number of processes successfully awoken\n */\nint wakeup_queue(list_t * queue) {\n\tint awoken_processes = 0;\n\tspin_lock(wait_lock_tmp);\n\twhile (queue->length > 0) {\n\t\tnode_t * node = list_pop(queue);\n\t\tspin_unlock(wait_lock_tmp);\n\t\tif (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) {\n\t\t\tmake_process_ready(node->value);\n\t\t}\n\t\tspin_lock(wait_lock_tmp);\n\t\tawoken_processes++;\n\t}\n\tspin_unlock(wait_lock_tmp);\n\treturn awoken_processes;\n}\n\n/**\n * @brief Signal a semaphore, exceptionally.\n *\n * Wake up everything in the semaphore @p queue but mark every\n * waiter as having been interrupted, rather than gracefully awoken.\n * Generally that means the event they were waiting for did not\n * happen and may never happen.\n *\n * Otherwise, same semantics as @ref wakeup_queue.\n */\nint wakeup_queue_interrupted(list_t * queue) {\n\tint awoken_processes = 0;\n\tspin_lock(wait_lock_tmp);\n\twhile (queue->length > 0) {\n\t\tnode_t * node = list_pop(queue);\n\t\tspin_unlock(wait_lock_tmp);\n\t\tif (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) {\n\t\t\tprocess_t * proc = node->value;\n\t\t\t__sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT);\n\t\t\tmake_process_ready(proc);\n\t\t}\n\t\tspin_lock(wait_lock_tmp);\n\t\tawoken_processes++;\n\t}\n\tspin_unlock(wait_lock_tmp);\n\treturn awoken_processes;\n}\n\nint wakeup_queue_one(list_t * queue) {\n\tint awoken_processes = 0;\n\tspin_lock(wait_lock_tmp);\n\tif (queue->length > 0) {\n\t\tnode_t * node = list_pop(queue);\n\t\tspin_unlock(wait_lock_tmp);\n\t\tif (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) {\n\t\t\tmake_process_ready(node->value);\n\t\t}\n\t\tspin_lock(wait_lock_tmp);\n\t\tawoken_processes++;\n\t}\n\tspin_unlock(wait_lock_tmp);\n\treturn awoken_processes;\n}\n\n/**\n * @brief Wait for a binary semaphore.\n *\n * Wait for an event with everyone else in @p queue.\n *\n * @returns 1 if the wait was interrupted (eg. the event did not occur); 0 otherwise.\n */\nint sleep_on(list_t * queue) {\n\tif (this_core->current_process->sleep_node.owner) {\n\t\tswitch_task(0);\n\t\treturn 0;\n\t}\n\t__sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT));\n\tspin_lock(wait_lock_tmp);\n\tlist_append(queue, (node_t*)&this_core->current_process->sleep_node);\n\tspin_unlock(wait_lock_tmp);\n\tswitch_task(0);\n\treturn !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT);\n}\n\nint sleep_on_unlocking(list_t * queue, spin_lock_t * release) {\n\t__sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT));\n\tspin_lock(wait_lock_tmp);\n\tlist_append(queue, (node_t*)&this_core->current_process->sleep_node);\n\tspin_unlock(wait_lock_tmp);\n\n\tspin_unlock(*release);\n\n\tswitch_task(0);\n\treturn !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT);\n}\n\n/**\n * @brief Indicates whether a process is ready to be run but not currently running.\n */\nint process_is_ready(process_t * proc) {\n\treturn (proc->sched_node.owner != NULL && !(proc->flags & PROC_FLAG_RUNNING));\n}\n\nint process_alert_node_locked(process_t * process, void * value);\n\n/**\n * @brief Wake up processes that were sleeping on timers.\n *\n * Reschedule all processes whose timed waits have expired as of\n * the time indicated by @p seconds and @p subseconds. If the sleep\n * was part of an fswait system call timing out, the call is marked\n * as timed out before the process is rescheduled.\n */\nvoid wakeup_sleepers(unsigned long seconds, unsigned long subseconds) {\n\tspin_lock(sleep_lock);\n\tif (sleep_queue->length) {\n\t\tsleeper_t * proc = ((sleeper_t *)sleep_queue->head->value);\n\t\twhile (proc && (proc->end_tick < seconds || (proc->end_tick == seconds && proc->end_subtick <= subseconds))) {\n\n\t\t\tif (proc->is_fswait) {\n\t\t\t\tproc->is_fswait = -1;\n\t\t\t\tprocess_alert_node_locked(proc->process,proc);\n\t\t\t} else {\n\t\t\t\tprocess_t * process = proc->process;\n\t\t\t\tprocess->sleep_node.owner = NULL;\n\t\t\t\tprocess->timed_sleep_node = NULL;\n\t\t\t\tif (!process_is_ready(process)) {\n\t\t\t\t\tmake_process_ready(process);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(proc);\n\t\t\tfree(list_dequeue(sleep_queue));\n\t\t\tif (sleep_queue->length) {\n\t\t\t\tproc = ((sleeper_t *)sleep_queue->head->value);\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\tspin_unlock(sleep_lock);\n}\n\n/**\n * @brief Wait until a given time.\n *\n * Suspends the current process until the given time. The process may\n * still be resumed by a signal or other mechanism, in which case the\n * sleep will not be resumed by the kernel.\n */\nvoid sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds) {\n\tspin_lock(sleep_lock);\n\tif (this_core->current_process->sleep_node.owner) {\n\t\tspin_unlock(sleep_lock);\n\t\t/* Can't sleep, sleeping already */\n\t\treturn;\n\t}\n\tprocess->sleep_node.owner = sleep_queue;\n\n\tnode_t * before = NULL;\n\tforeach(node, sleep_queue) {\n\t\tsleeper_t * candidate = ((sleeper_t *)node->value);\n\t\tif (!candidate) {\n\t\t\tprintf(\"null candidate?\\n\");\n\t\t\tcontinue;\n\t\t}\n\t\tif (candidate->end_tick > seconds || (candidate->end_tick == seconds && candidate->end_subtick > subseconds)) {\n\t\t\tbreak;\n\t\t}\n\t\tbefore = node;\n\t}\n\tsleeper_t * proc = malloc(sizeof(sleeper_t));\n\tproc->process     = process;\n\tproc->end_tick    = seconds;\n\tproc->end_subtick = subseconds;\n\tproc->is_fswait = 0;\n\tprocess->timed_sleep_node = list_insert_after(sleep_queue, before, proc);\n\tspin_unlock(sleep_lock);\n}\n\nuint8_t process_compare(void * proc_v, void * pid_v) {\n\tpid_t pid = (*(pid_t *)pid_v);\n\tprocess_t * proc = (process_t *)proc_v;\n\n\treturn (uint8_t)(proc->id == pid);\n}\n\nprocess_t * process_from_pid(pid_t pid) {\n\tif (pid < 0) return NULL;\n\n\tspin_lock(tree_lock);\n\ttree_node_t * entry = tree_find(process_tree,&pid,process_compare);\n\tspin_unlock(tree_lock);\n\tif (entry) {\n\t\treturn (process_t *)entry->value;\n\t}\n\treturn NULL;\n}\n\n\nlong process_move_fd(process_t * proc, long src, long dest) {\n\tif ((size_t)src >= proc->fds->length || (dest != -1 && (size_t)dest >= proc->fds->length)) {\n\t\treturn -1;\n\t}\n\tif (dest == -1) {\n\t\tdest = process_append_fd(proc, NULL);\n\t}\n\tif (proc->fds->entries[dest] != proc->fds->entries[src]) {\n\t\tclose_fs(proc->fds->entries[dest]);\n\t\tprocess_fds_copy(proc, src, dest);\n\t}\n\treturn dest;\n}\n\nvoid tasking_start(void) {\n\tthis_core->current_process = spawn_init();\n\tthis_core->kernel_idle_task = spawn_kidle(1);\n}\n\nstatic int wait_candidate(volatile process_t * parent, int pid, int options, volatile process_t * proc) {\n\tif (!proc) return 0;\n\n\tif (options & WNOKERN) {\n\t\t/* Skip kernel processes */\n\t\tif (proc->flags & PROC_FLAG_IS_TASKLET) return 0;\n\t}\n\n\tif (pid < -1) {\n\t\tif (proc->job == -pid || proc->id == -pid) return 1;\n\t} else if (pid == 0) {\n\t\t/* Matches our group ID */\n\t\tif (proc->job == parent->id) return 1;\n\t} else if (pid > 0) {\n\t\t/* Specific pid */\n\t\tif (proc->id == pid) return 1;\n\t} else {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nint waitpid(int pid, int * status, int options) {\n\tvolatile process_t * volatile proc = (process_t*)this_core->current_process;\n\t#if 0\n\tif (proc->group) {\n\t\tproc = process_from_pid(proc->group);\n\t}\n\t#endif\n\n\tdo {\n\t\tvolatile process_t * candidate = NULL;\n\t\tint has_children = 0;\n\t\tint is_parent = 0;\n\n\t\tspin_lock(proc->wait_lock);\n\n\t\t/* First, find out if there is anyone to reap */\n\t\tforeach(node, proc->tree_entry->children) {\n\t\t\tif (!node->value) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tvolatile process_t * volatile child = ((tree_node_t *)node->value)->value;\n\n\t\t\tif (wait_candidate(proc, pid, options, child)) {\n\t\t\t\thas_children = 1;\n\t\t\t\tis_parent = 1;\n\t\t\t\tif (child->flags & PROC_FLAG_FINISHED) {\n\t\t\t\t\tcandidate = child;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ((child->flags & PROC_FLAG_SUSPENDED) && ((child->status & 0xFF) == 0x7F)) {\n\t\t\t\t\tint reason = (child->status >> 16) & 0xFF;\n\t\t\t\t\tif ((options & WSTOPPED) || (reason == 0xFF && (options & WUNTRACED))) {\n\t\t\t\t\t\tcandidate = child;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!candidate && proc->tracees) {\n\t\t\tforeach(node, proc->tracees) {\n\t\t\t\tprocess_t * child = node->value;\n\t\t\t\tif (wait_candidate(proc,pid,options,child)) {\n\t\t\t\t\thas_children = 1;\n\t\t\t\t\tif (child->flags & (PROC_FLAG_SUSPENDED | PROC_FLAG_FINISHED)) {\n\t\t\t\t\t\tcandidate = child;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!has_children) {\n\t\t\t/* No valid children matching this description */\n\t\t\tspin_unlock(proc->wait_lock);\n\t\t\treturn -ECHILD;\n\t\t}\n\n\t\tif (candidate) {\n\t\t\tspin_unlock(proc->wait_lock);\n\t\t\tif (status) {\n\t\t\t\t*status = candidate->status;\n\t\t\t}\n\t\t\tcandidate->status &= ~0xFF;\n\t\t\tint pid = candidate->id;\n\t\t\tif (is_parent && (candidate->flags & PROC_FLAG_FINISHED)) {\n\t\t\t\twhile (*((volatile int *)&candidate->flags) & PROC_FLAG_RUNNING);\n\t\t\t\tproc->time_children += candidate->time_children + candidate->time_total;\n\t\t\t\tproc->time_sys_children += candidate->time_sys_children + candidate->time_sys;\n\t\t\t\tprocess_delete((process_t*)candidate);\n\t\t\t}\n\t\t\treturn pid;\n\t\t} else {\n\t\t\tif (options & WNOHANG) {\n\t\t\t\tspin_unlock(proc->wait_lock);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\t/* Wait */\n\t\t\tif (sleep_on_unlocking(proc->wait_queue, &proc->wait_lock) != 0) {\n\t\t\t\treturn -EINTR;\n\t\t\t}\n\t\t}\n\t} while (1);\n}\n\nint process_timeout_sleep(process_t * process, int timeout) {\n\tunsigned long s, ss;\n\trelative_time(0, timeout * 1000, &s, &ss);\n\n\tnode_t * before = NULL;\n\tforeach(node, sleep_queue) {\n\t\tsleeper_t * candidate = ((sleeper_t *)node->value);\n\t\tif (candidate->end_tick > s || (candidate->end_tick == s && candidate->end_subtick > ss)) {\n\t\t\tbreak;\n\t\t}\n\t\tbefore = node;\n\t}\n\tsleeper_t * proc = malloc(sizeof(sleeper_t));\n\tproc->process     = process;\n\tproc->end_tick    = s;\n\tproc->end_subtick = ss;\n\tproc->is_fswait = 1;\n\tlist_insert(((process_t *)process)->node_waits, proc);\n\tprocess->timeout_node = list_insert_after(sleep_queue, before, proc);\n\n\treturn 0;\n}\n\nint process_wait_nodes(process_t * process,fs_node_t * nodes[], int timeout) {\n\tfs_node_t ** n = nodes;\n\tint index = 0;\n\tif (*n) {\n\t\tdo {\n\t\t\tint result = selectcheck_fs(*n);\n\t\t\tif (result < 0) {\n\t\t\t\treturn -EBADF;\n\t\t\t}\n\t\t\tif (result == 0) {\n\t\t\t\treturn index;\n\t\t\t}\n\t\t\tn++;\n\t\t\tindex++;\n\t\t} while (*n);\n\t}\n\n\tif (timeout == 0) {\n\t\treturn index;\n\t}\n\n\tn = nodes;\n\n\tspin_lock(sleep_lock);\n\tspin_lock(process->sched_lock);\n\tprocess->node_waits = list_create(\"process fswaiters\",process);\n\tif (*n) {\n\t\tdo {\n\t\t\tif (selectwait_fs(*n, process) < 0) {\n\t\t\t\tprintf(\"bad selectwait?\\n\");\n\t\t\t}\n\t\t\tn++;\n\t\t} while (*n);\n\t}\n\n\tif (timeout > 0) {\n\t\tprocess_timeout_sleep(process, timeout);\n\t} else {\n\t\tprocess->timeout_node = NULL;\n\t}\n\n\tprocess->awoken_index = -1;\n\tspin_unlock(process->sched_lock);\n\tspin_unlock(sleep_lock);\n\n\t/* Wait. */\n\tswitch_task(0);\n\n\treturn process->awoken_index;\n}\n\nint process_awaken_from_fswait(process_t * process, int index) {\n\tmust_have_lock(sleep_lock);\n\n\tprocess->awoken_index = index;\n\tlist_free(process->node_waits);\n\tfree(process->node_waits);\n\tprocess->node_waits = NULL;\n\n\tif (process->timeout_node && process->timeout_node->owner == sleep_queue) {\n\t\tsleeper_t * proc = process->timeout_node->value;\n\t\tif (proc->is_fswait != -1) {\n\t\t\tlist_delete(sleep_queue, process->timeout_node);\n\t\t\tfree(process->timeout_node->value);\n\t\t\tfree(process->timeout_node);\n\t\t}\n\t}\n\tprocess->timeout_node = NULL;\n\n\tmake_process_ready(process);\n\tspin_unlock(process->sched_lock);\n\treturn 0;\n}\n\nvoid process_awaken_signal(process_t * process) {\n\tspin_lock(sleep_lock);\n\tspin_lock(process->sched_lock);\n\tif (process->node_waits) {\n\t\tprocess_awaken_from_fswait(process, -EINTR);\n\t} else {\n\t\tspin_unlock(process->sched_lock);\n\t}\n\tspin_unlock(sleep_lock);\n}\n\nint process_alert_node_locked(process_t * process, void * value) {\n\tmust_have_lock(sleep_lock);\n\n\tif (!is_valid_process(process)) {\n\t\tdprintf(\"core %d (pid=%d %s) attempted to alert invalid process %#zx\\n\",\n\t\t\tthis_core->cpu_id, this_core->current_process->id, this_core->current_process->name,\n\t\t\t(uintptr_t)process);\n\t\treturn 0;\n\t}\n\n\tspin_lock(process->sched_lock);\n\n\tif (!process->node_waits) {\n\t\tspin_unlock(process->sched_lock);\n\t\treturn 0; /* Possibly already returned. Wait for another call. */\n\t}\n\n\tint index = 0;\n\tforeach(node, process->node_waits) {\n\t\tif (value == node->value) {\n\t\t\treturn process_awaken_from_fswait(process, index);\n\t\t}\n\t\tindex++;\n\t}\n\n\tspin_unlock(process->sched_lock);\n\treturn -1;\n}\n\nint process_alert_node(process_t * process, void * value) {\n\tspin_lock(sleep_lock);\n\tint result = process_alert_node_locked(process, value);\n\tspin_unlock(sleep_lock);\n\treturn result;\n}\n\nprocess_t * process_get_parent(process_t * process) {\n\tprocess_t * result = NULL;\n\tspin_lock(tree_lock);\n\n\ttree_node_t * entry = process->tree_entry;\n\n\tif (entry->parent) {\n\t\tresult = entry->parent->value;\n\t}\n\n\tspin_unlock(tree_lock);\n\treturn result;\n}\n\nvoid task_exit(int retval) {\n\tthis_core->current_process->status = retval;\n\n\t/* free whatever we can */\n\tlist_free(this_core->current_process->wait_queue);\n\tfree(this_core->current_process->wait_queue);\n\tfree(this_core->current_process->wd_name);\n\tif (this_core->current_process->node_waits) {\n\t\tlist_free(this_core->current_process->node_waits);\n\t\tfree(this_core->current_process->node_waits);\n\t\tthis_core->current_process->node_waits = NULL;\n\t}\n\n\tif (this_core->current_process->fds) {\n\t\tspin_lock(this_core->current_process->fds->lock);\n\t\tthis_core->current_process->fds->refs--;\n\t\tif (this_core->current_process->fds->refs == 0) {\n\t\t\tfor (uint32_t i = 0; i < this_core->current_process->fds->length; ++i) {\n\t\t\t\tif (this_core->current_process->fds->entries[i]) {\n\t\t\t\t\tclose_fs(this_core->current_process->fds->entries[i]);\n\t\t\t\t\tthis_core->current_process->fds->entries[i] = NULL;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfree(this_core->current_process->fds->entries);\n\t\t\tfree(this_core->current_process->fds->offsets);\n\t\t\tfree(this_core->current_process->fds->modes);\n\t\t\tfree(this_core->current_process->fds);\n\t\t\tthis_core->current_process->fds = NULL;\n\t\t} else {\n\t\t\tspin_unlock(this_core->current_process->fds->lock);\n\t\t}\n\t}\n\n\tif (this_core->current_process->tracees) {\n\t\tspin_lock(this_core->current_process->wait_lock);\n\t\twhile (this_core->current_process->tracees->length) {\n\t\t\tnode_t * n = list_pop(this_core->current_process->tracees);\n\t\t\tprocess_t * tracee = n->value;\n\t\t\tfree(n);\n\t\t\tif (is_valid_process(tracee)) {\n\t\t\t\ttracee->tracer = 0;\n\t\t\t\t__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_TRACE_SIGNALS | PROC_FLAG_TRACE_SYSCALLS));\n\t\t\t\tif (tracee->flags & PROC_FLAG_SUSPENDED) {\n\t\t\t\t\ttracee->status = 0;\n\t\t\t\t\t__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));\n\t\t\t\t\tmake_process_ready(tracee);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tspin_unlock(this_core->current_process->wait_lock);\n\t}\n\n\tupdate_process_times();\n\n\tprocess_t * parent = process_get_parent((process_t *)this_core->current_process);\n\t__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_FINISHED);\n\n\tif (this_core->current_process->tracer) {\n\t\tprocess_t * tracer = process_from_pid(this_core->current_process->tracer);\n\t\tif (tracer && tracer != parent) {\n\t\t\tspin_lock(tracer->wait_lock);\n\t\t\twakeup_queue(tracer->wait_queue);\n\t\t\tspin_unlock(tracer->wait_lock);\n\t\t}\n\t}\n\n\tif (parent && !(parent->flags & PROC_FLAG_FINISHED)) {\n\t\tspin_lock(parent->wait_lock);\n\t\tsend_signal(parent->group, SIGCHLD, 1);\n\t\twakeup_queue(parent->wait_queue);\n\t\tspin_unlock(parent->wait_lock);\n\t}\n\n\tswitch_next();\n}\n\n#define PUSH(stack, type, item) stack -= sizeof(type); \\\n\t\t\t\t\t\t\t*((volatile type *) stack) = item\n\npid_t fork(void) {\n\tuintptr_t sp, bp;\n\tprocess_t * parent = (process_t*)this_core->current_process;\n\tunion PML * directory = mmu_clone(parent->thread.page_directory->directory);\n\tprocess_t * new_proc = spawn_process(parent, 0);\n\tnew_proc->thread.page_directory = malloc(sizeof(page_directory_t));\n\tnew_proc->thread.page_directory->refcount = 1;\n\tnew_proc->thread.page_directory->directory = directory;\n\tspin_init(new_proc->thread.page_directory->lock);\n\n\tmemcpy(new_proc->signals, parent->signals, sizeof(struct signal_config) * (NUMSIGNALS+1));\n\tnew_proc->blocked_signals = parent->blocked_signals;\n\n\tstruct regs r;\n\tmemcpy(&r, parent->syscall_registers, sizeof(struct regs));\n\tsp = new_proc->image.stack;\n\tbp = sp;\n\n\tarch_syscall_return(&r, 0);\n\tPUSH(sp, struct regs, r);\n\n\tnew_proc->syscall_registers = (void*)sp;\n\tnew_proc->thread.context.sp = sp;\n\tnew_proc->thread.context.bp = bp;\n\tnew_proc->thread.context.tls_base = parent->thread.context.tls_base;\n\tnew_proc->thread.context.ip = (uintptr_t)&arch_resume_user;\n\tarch_save_context(&parent->thread);\n\tmemcpy(new_proc->thread.context.saved, parent->thread.context.saved, sizeof(parent->thread.context.saved));\n\n\t#if 0\n\tprintf(\"fork(): resuming with register context\\n\");\n\textern void aarch64_regs(struct regs *);\n\taarch64_regs(&r);\n\tprintf(\"fork(): and arch context:\\n\");\n\textern void aarch64_context(process_t * proc);\n\taarch64_context(new_proc);\n\t#endif\n\n\tif (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET;\n\tmake_process_ready(new_proc);\n\treturn new_proc->id;\n}\n\npid_t clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) {\n\tuintptr_t sp, bp;\n\tprocess_t * parent = (process_t *)this_core->current_process;\n\tprocess_t * new_proc = spawn_process(this_core->current_process, 1);\n\tnew_proc->thread.page_directory = this_core->current_process->thread.page_directory;\n\tspin_lock(new_proc->thread.page_directory->lock);\n\tnew_proc->thread.page_directory->refcount++;\n\tspin_unlock(new_proc->thread.page_directory->lock);\n\tmemcpy(new_proc->signals, parent->signals, sizeof(struct signal_config) * (NUMSIGNALS+1));\n\tnew_proc->blocked_signals = parent->blocked_signals;\n\n\tstruct regs r;\n\tmemcpy(&r, parent->syscall_registers, sizeof(struct regs));\n\tsp = new_proc->image.stack;\n\tbp = sp;\n\n\t/* Set the gid */\n\tif (this_core->current_process->group) {\n\t\tnew_proc->group = this_core->current_process->group;\n\t} else {\n\t\t/* We are the session leader */\n\t\tnew_proc->group = this_core->current_process->id;\n\t}\n\n\t/* different calling convention */\n\t#if defined(__x86_64__)\n\tr.rdi = arg;\n\tPUSH(new_stack, uintptr_t, (uintptr_t)0);\n\t#elif defined(__aarch64__)\n\tr.x0 = arg;\n\tr.x30 = 0;\n\t#endif\n\tPUSH(sp, struct regs, r);\n\tnew_proc->syscall_registers = (void*)sp;\n\t#if defined(__x86_64__)\n\tnew_proc->syscall_registers->rsp = new_stack;\n\tnew_proc->syscall_registers->rbp = new_stack;\n\tnew_proc->syscall_registers->rip = thread_func;\n\t#elif defined(__aarch64__)\n\tnew_proc->syscall_registers->user_sp = new_stack;\n\tnew_proc->syscall_registers->x29 = new_stack;\n\tnew_proc->thread.context.saved[10] = thread_func;\n\t#endif\n\tnew_proc->thread.context.sp = sp;\n\tnew_proc->thread.context.bp = bp;\n\tnew_proc->thread.context.tls_base = this_core->current_process->thread.context.tls_base;\n\tnew_proc->thread.context.ip = (uintptr_t)&arch_resume_user;\n\tif (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET;\n\tmake_process_ready(new_proc);\n\treturn new_proc->id;\n}\n\nprocess_t * spawn_worker_thread(void (*entrypoint)(void * argp), const char * name, void * argp) {\n\tprocess_t * proc = calloc(1,sizeof(process_t));\n\n\tproc->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED;\n\n\tproc->id          = get_next_pid();\n\tproc->group       = proc->id;\n\tproc->name        = strdup(name);\n\tproc->description = NULL;\n\tproc->cmdline     = NULL;\n\n\t/* Are these necessary for tasklets? Should probably all be zero. */\n\tproc->user        = 0;\n\tproc->real_user   = 0;\n\tproc->user_group  = 0;\n\tproc->real_user_group = 0;\n\tproc->mask        = 0;\n\tproc->job         = proc->id;\n\tproc->session     = proc->id;\n\n\tproc->thread.page_directory = malloc(sizeof(page_directory_t));\n\tproc->thread.page_directory->refcount = 1;\n\tproc->thread.page_directory->directory = mmu_clone(mmu_get_kernel_directory());\n\tspin_init(proc->thread.page_directory->lock);\n\n\tproc->image.stack       = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE;\n\tPUSH(proc->image.stack, uintptr_t, (uintptr_t)entrypoint);\n\tPUSH(proc->image.stack, void*, argp);\n\n\tproc->thread.context.sp = proc->image.stack;\n\tproc->thread.context.bp = proc->image.stack;\n\tproc->thread.context.ip = (uintptr_t)&arch_enter_tasklet;\n\n\n\tproc->wait_queue   = list_create(\"worker thread wait queue\",proc);\n\tproc->shm_mappings = list_create(\"worker thread shm mappings\",proc);\n\n\tproc->sched_node.value = proc;\n\tproc->sleep_node.value = proc;\n\n\tgettimeofday(&proc->start, NULL);\n\ttree_node_t * entry = tree_node_create(proc);\n\tproc->tree_entry = entry;\n\n\tspin_lock(tree_lock);\n\ttree_node_insert_child_node(process_tree, this_core->current_process->tree_entry, entry);\n\tlist_insert(process_list, (void*)proc);\n\tspin_unlock(tree_lock);\n\n\tmake_process_ready(proc);\n\n\treturn proc;\n}\n\nstatic void update_one_process(uint64_t clock_ticks, uint64_t perf_scale, process_t * proc) {\n\tproc->usage[3] = proc->usage[2];\n\tproc->usage[2] = proc->usage[1];\n\tproc->usage[1] = proc->usage[0];\n\tproc->usage[0] = (1000 * (proc->time_total - proc->time_prev)) / (clock_ticks * perf_scale);\n\tproc->time_prev = proc->time_total;\n}\n\nvoid update_process_usage(uint64_t clock_ticks, uint64_t perf_scale) {\n\tspin_lock(tree_lock);\n\tforeach(lnode, process_list) {\n\t\tprocess_t * proc = lnode->value;\n\t\tupdate_one_process(clock_ticks, perf_scale, proc);\n\t}\n\tspin_unlock(tree_lock);\n\t/* Now use idle tasks to calculator processor activity? */\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tprocess_t * proc = processor_local_data[i].kernel_idle_task;\n\t\tupdate_one_process(clock_ticks, perf_scale, proc);\n\t}\n}\n"
  },
  {
    "path": "kernel/sys/ptrace.c",
    "content": "/**\n * @file kernel/sys/ptrace.c\n * @brief Process tracing functions\n *\n * Provides single stepping, cross-process memory inspection,\n * regiser inspection, poking, and syscall trace events.\n *\n * @warning This ptrace implementation is incomplete.\n *\n * We are missing a lot of @c ptrace functionality found in other\n * operating systems, and even some of the functionality we have is\n * only partially implemented or may not work as it should.\n *\n * This implementation was intended primarily to support having a\n * @c strace command in userspace, and also provides some limited\n * support for a debugger.\n *\n * @see apps/dbg.c\n * @see apps/strace.c\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021-2022 K. Lange\n */\n#include <stdint.h>\n#include <errno.h>\n#include <sys/ptrace.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/signal.h>\n#include <kernel/syscall.h>\n#include <kernel/ptrace.h>\n#include <kernel/args.h>\n#include <kernel/mmu.h>\n\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/regs.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/regs.h>\n#else\n#error \"no regs\"\n#endif\n\n/**\n * @brief Internally set the tracer of a tracee process.\n *\n * Sets up @p tracer to trace @p tracee and sets @p tracee as\n * tracing the default events (syscalls and signals).\n *\n * A tracer can trace multiple tracees, but a tracee can only be\n * traced by one tracer.\n *\n * @param tracer Process that is the doing the tracing\n * @param tracee Process that is breing traced\n */\nstatic void _ptrace_trace(process_t * tracer, process_t * tracee) {\n\tspin_lock(tracer->wait_lock);\n\t__sync_or_and_fetch(&tracee->flags, (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS));\n\n\tif (!tracer->tracees) {\n\t\ttracer->tracees = list_create(\"debug tracees\", tracer);\n\t}\n\n\tlist_insert(tracer->tracees, tracee);\n\n\ttracee->tracer = tracer->id;\n\n\tspin_unlock(tracer->wait_lock);\n}\n\n/**\n * @brief Start tracing a process.\n *\n * @ref PTRACE_ATTACH\n *\n * Sets the current process to be the tracer for the target tracee.\n * Both the tracer and tracee will resume normally, until the next\n * ptrace event stops the tracee.\n *\n * TODO What happens if the process is already being traced?\n *\n * @param pid Tracee ID\n * @returns 0 on success, -ESRCH if the tracee is invalid, -EPERM if the tracee\n *          is not owned by the same user as the tracer and the tracer is not root.\n */\nlong ptrace_attach(pid_t pid) {\n\tprocess_t * tracer = (process_t *)this_core->current_process;\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee) return -ESRCH;\n\tif (tracer->user != 0 && tracer->user != tracee->user) return -EPERM;\n\n\t_ptrace_trace(tracer, tracee);\n\n\treturn 0;\n}\n\n/**\n * @brief Set the current process to be traced by its parent.\n *\n * @ref PTRACE_TRACEME\n *\n * Generally, this is used through the @c ptrace system call by\n * the debugger or @c strace implementation after forking a child\n * process and before calling @c exec.\n *\n * The calling process will resume immediately.\n *\n * TODO What happens if we are already being traced?\n *\n * @returns 0 on success, -EINVAL if the parent was not found.\n */\nlong ptrace_self(void) {\n\tprocess_t * tracee = (process_t*)this_core->current_process;\n\tprocess_t * tracer = process_get_parent(tracee);\n\tif (!tracer) return -EINVAL;\n\n\t_ptrace_trace(tracer, tracee);\n\n\treturn 0;\n}\n\n/**\n * @brief Trigger a ptrace event on the currently executing thread.\n *\n * @ref PTRACE_EVENT_SINGLESTEP\n * @ref PTRACE_EVENT_SYSCALL_ENTER\n * @ref PTRACE_EVENT_SYSCALL_EXIT\n *\n * Called elsewhere in the kernel when a trace event happens that is\n * not currently being ignored, such as upon entry into a syscall handler,\n * or exit from a syscall handler, or before a signal would be delivered.\n *\n * Runs in the kernel context of the tracee, causes the tracee to be suspended\n * and awakens the tracer to return from its @c ptrace call.\n *\n * When the kernel context for this process is resumed, the signal number\n * will be checked from the tracee's status and returned to caller that\n * initiated the ptrace event.\n *\n * When resuming from a signal event, the new signal number will replace the\n * old signal number. In this case, if the new signal number is 0 it will\n * be discarded and the tracee will continue as if it had ignored it.\n *\n * When resuming from other events, signals are generally sent directly\n * and the process will act on the signal when it has an opportunity to\n * return to userspace.\n *\n * @param signal Signal number if @p reason is 0.\n * @param reason PTRACE_EVENT value describing the event; 0 for signal delivery.\n * @returns Signal number from tracee status upon resumption.\n */\nlong ptrace_signal(int signal, int reason) {\n\tthis_core->current_process->status = 0x7F | (signal << 8) | (reason << 16);\n\t__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_SUSPENDED);\n\n\tprocess_t * parent = process_from_pid(this_core->current_process->tracer);\n\tif (parent && !(parent->flags & PROC_FLAG_FINISHED)) {\n\t\tspin_lock(parent->wait_lock);\n\t\twakeup_queue(parent->wait_queue);\n\t\tspin_unlock(parent->wait_lock);\n\t}\n\tswitch_task(0);\n\n\tint signum = (this_core->current_process->status >> 8);\n\tthis_core->current_process->status = 0;\n\treturn signum;\n}\n\n/**\n * @brief Resume a traced process.\n *\n * Unsuspends the traced process, sending an appropriate signal if one\n * was currently pending or if one was sent by the tracer through either\n * of @ref ptrace_continue or @ref ptrace_detach.\n *\n * @param pid Tracee ID\n * @param tracee Tracee process object\n * @param sig Signal number to send, or 0 if none.\n */\nstatic void signal_and_continue(pid_t pid, process_t * tracee, int sig) {\n\t/* Unsuspend */\n\t__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));\n\n\t/* Does the process have a pending signal? */\n\tif ((tracee->status >> 8) & 0xFF && (!(tracee->status >> 16) || ((tracee->status >> 16) == 0xFF))) {\n\t\ttracee->status = (sig << 8);\n\t\tmake_process_ready(tracee);\n\t} else if (sig) {\n\t\tsend_signal(pid, sig,1);\n\t} else {\n\t\tmake_process_ready(tracee);\n\t}\n}\n\n/**\n * @brief Resume the tracee until the next event.\n *\n * @ref PTRACE_CONT\n *\n * Allows the tracee to resume execution, while optionally sending\n * a signal. This signal may be the one that triggered the ptrace\n * event from which the process is being resumed, or a new signal,\n * or no signal at all.\n *\n * @param pid Tracee ID\n * @param sig Signal to send to tracee on resume, or 0 for none.\n * @returns 0 on success, -ESRCH if tracee is invalid.\n */\nlong ptrace_continue(pid_t pid, int sig) {\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\tsignal_and_continue(pid,tracee,sig);\n\n\treturn 0;\n}\n\n/**\n * @brief Stop tracing a tracee.\n *\n * @ref PTRACE_DETACH\n *\n * Marks the tracee as no longer being traced and resumes it.\n *\n * @param pid Tracee ID\n * @param sig Signal to send to tracee on resume, or 0 for none.\n * @returns 0 on success, -ESRCH if tracee is invalid.\n */\nlong ptrace_detach(pid_t pid, int sig) {\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\t/* Mark us not the tracer. */\n\ttracee->tracer = 0;\n\n\tsignal_and_continue(pid,tracee,sig);\n\n\treturn 0;\n}\n\n/**\n * @brief Obtain the register context of the tracee.\n *\n * @ref PTRACE_GETREGS\n *\n * Copies the interrupt register context of the tracee into a tracer-provided\n * address. The size, meaning, and layout of the data copied is architecture-dependent.\n *\n * On AArch64 we also add ELR, which isn't in the interrupt or syscall register contexts,\n * but pushed somewhere else...\n *\n * TODO We should support reading FPU regs as well.\n *\n * @param pid Tracee ID\n * @param data Address in tracer to write data into.\n * @returns 0 on success, -ESRCH if tracee is invalid.\n */\nlong ptrace_getregs(pid_t pid, void * data) {\n\tif (!data || ptr_validate(data, \"ptrace\")) return -EFAULT;\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\t/* Copy registers */\n\tmemcpy(data, tracee->syscall_registers, sizeof(struct regs));\n#ifdef __aarch64__\n\tmemcpy((char*)data + sizeof(struct regs), &tracee->thread.context.saved[10], sizeof(uintptr_t));\n#endif\n\n\treturn 0;\n}\n\n/**\n * @brief Modify the registers of the tracee.\n *\n * @ref PTRACE_SETREGS\n *\n * @param pid Tracee ID\n * @param data Address in tracer to read data from.\n * @returns 0 on success, -ESRCH if tracee is invalid.\n */\nlong ptrace_setregs(pid_t pid, void * data) {\n\tif (!data || ptr_validate(data, \"ptrace\")) return -EFAULT;\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\t/* Copy registers */\n\tmemcpy(tracee->syscall_registers, data, sizeof(struct regs));\n#ifdef __aarch64__\n\tmemcpy(&tracee->thread.context.saved[10], (char*)data + sizeof(struct regs), sizeof(uintptr_t));\n#endif\n\n\treturn 0;\n}\n\n/**\n * @brief Read one byte from the tracee's memory.\n *\n * @ref PTRACE_PEEKDATA\n *\n * Reads one byte of data from the tracee process's memory space.\n * Other implementations of @c PTRACE_PEEKDATA may write other sizes of data,\n * but to make this as straightforward as possible, we only support single\n * bytes. Maybe in the future we'll support other sizes...\n *\n * @param pid Tracee ID\n * @param addr Virtual address in the tracee context to write to.\n * @param data Address in the tracer to store the read byte into.\n * @returns 0 on success, -EFAULT if the requested address is not mapped and readable in the tracee, -ESRCH if tracee is invalid.\n */\nlong ptrace_peek(pid_t pid, void * addr, void * data) {\n\tif (!data || ptr_validate(data, \"ptrace\")) return -EFAULT;\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\tunion PML * page_entry = mmu_get_page_other(tracee->thread.page_directory->directory, (uintptr_t)addr);\n\n\tif (!page_entry) return -EFAULT;\n\tif (!mmu_page_is_user_readable(page_entry)) return -EFAULT;\n\n\tuintptr_t mapped_address = mmu_map_to_physical(tracee->thread.page_directory->directory, (uintptr_t)addr);\n\n\tif ((intptr_t)mapped_address < 0 && (intptr_t)mapped_address > -10) return -EFAULT;\n\n\tuintptr_t blarg = (uintptr_t)mmu_map_from_physical(mapped_address);\n\n\t/* Yeah, uh, one byte. That works. */\n\t*(char*)data = *(char*)blarg;\n\n\treturn 0;\n}\n\n/**\n * @brief Place a byte of data into the tracee's memory.\n *\n * @ref PTRACE_POKEDATA\n *\n * Writes one byte of data into the tracee process's memory space.\n * Other implementations of @c PTRACE_POKEDATA may write other sizes of data,\n * but to make this as straightforward as possible, we only support single\n * bytes. Maybe in the future we'll support other sizes...\n *\n * TODO This uses mmu_map_from_physical and doesn't do any cache maintenance?\n *      It will probably break when, eg., poking instructions on ARM...\n *\n * @param pid Tracee ID\n * @param addr Virtual address in the tracee context to write to.\n * @param data Address in the tracer context to read one byte from.\n * @returns 0 on success, -ESRCH if tracee is invalid, -EFAULT if the tracee address is not mapped or not writable.\n */\nlong ptrace_poke(pid_t pid, void * addr, void * data) {\n\tif (!data || ptr_validate(data, \"ptrace\")) return -EFAULT;\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\tunion PML * page_entry = mmu_get_page_other(tracee->thread.page_directory->directory, (uintptr_t)addr);\n\n\tif (!page_entry) return -EFAULT;\n\tif (!mmu_page_is_user_writable(page_entry)) return -EFAULT;\n\n\tuintptr_t mapped_address = mmu_map_to_physical(tracee->thread.page_directory->directory, (uintptr_t)addr);\n\n\tif ((intptr_t)mapped_address < 0 && (intptr_t)mapped_address > -10) return -EFAULT;\n\n\tuintptr_t blarg = (uintptr_t)mmu_map_from_physical(mapped_address);\n\n\t/* Yeah, uh, one byte. That works. */\n\t*(char*)blarg = *(char*)data;\n\n\treturn 0;\n}\n\n/**\n * @brief Disable tracing of syscalls in the tracee.\n *\n * @ref PTRACE_SIGNALS_ONLY_PLZ\n *\n * Turns off tracing of syscalls in the tracee. Only signals will be\n * traced. To turn syscall tracing back on, restart tracing by detaching\n * and re-attaching to the tracee.\n *\n * TODO We need a better interface to configure tracing, so we can offer\n *      more complex options than just signals and syscalls...\n *\n * @param pid Tracee ID\n * @returns 0 on success, -ESRCH if the tracee was not found or the current process is not its tracer.\n */\nlong ptrace_signals_only(pid_t pid) {\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\t__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_TRACE_SYSCALLS));\n\treturn 0;\n}\n\n/**\n * @brief Enable single-stepping for a process.\n *\n * @ref PTRACE_SINGLESTEP\n *\n * Enables an architecture-specific mechanism for single step debugging\n * in the requested process. When the process resumes, it will execute\n * one instruction and then fault back to the kernel, and the tracer\n * will be alerted.\n *\n * Single stepping will be disabled again when the process returns from\n * the fault, and must be re-enabled by another call to @c ptrace_singlstep.\n *\n * @param pid ID of the process to enable single-step for\n * @param sig Signal number to hand to the process when it resumes, or 0.\n * @returns 0 on success, -ESRCH if the process could not be found or is not a tracee of the current process.\n */\nlong ptrace_singlestep(pid_t pid, int sig) {\n\tprocess_t * tracee = process_from_pid(pid);\n\tif (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH;\n\n\t/* arch_set_singlestep? */\n\t#if defined(__x86_64__)\n\tstruct regs * target = tracee->syscall_registers;\n\ttarget->rflags |= (1 << 8);\n\t#elif defined(__aarch64__)\n\ttracee->thread.context.saved[11] |= (1 << 21);\n\t#endif\n\n\t__sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED));\n\ttracee->status = (sig << 8);\n\tmake_process_ready(tracee);\n\n\treturn 0;\n}\n\n/**\n * @brief Handle ptrace system call requests.\n *\n * Internal interface for dispatching @c ptrace system calls. Maps\n * arguments from the system call to the various ptrace functions.\n *\n * @note This is the direct system call implementation. Data coming\n *       in here is directly from the arguments of the system call.\n *\n * @param request Request type\n * @param pid Tracee ID\n * @param addr Address to peek or poke\n * @param data Place to put or read data, depending on the function\n * @returns Generally, status codes. -EINVAL for an invalid request.\n */\nlong ptrace_handle(long request, pid_t pid, void * addr, void * data) {\n\tswitch (request) {\n\t\tcase PTRACE_ATTACH:\n\t\t\treturn ptrace_attach(pid);\n\t\tcase PTRACE_TRACEME:\n\t\t\treturn ptrace_self();\n\t\tcase PTRACE_GETREGS:\n\t\t\treturn ptrace_getregs(pid,data);\n\t\tcase PTRACE_CONT:\n\t\t\treturn ptrace_continue(pid,(uintptr_t)data);\n\t\tcase PTRACE_PEEKDATA:\n\t\t\treturn ptrace_peek(pid,addr,data);\n\t\tcase PTRACE_POKEDATA:\n\t\t\treturn ptrace_poke(pid,addr,data);\n\t\tcase PTRACE_SIGNALS_ONLY_PLZ:\n\t\t\treturn ptrace_signals_only(pid);\n\t\tcase PTRACE_SINGLESTEP:\n\t\t\treturn ptrace_singlestep(pid,(uintptr_t)data);\n\t\tcase PTRACE_DETACH:\n\t\t\treturn ptrace_detach(pid,(uintptr_t)data);\n\t\tcase PTRACE_SETREGS:\n\t\t\treturn ptrace_setregs(pid,data);\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\n"
  },
  {
    "path": "kernel/sys/shm.c",
    "content": "/**\n * @file  kernel/sys/shm.c\n * @brief Shared memory subsystem\n *\n * Provides shared memory mappings for userspace processes and\n * manages their allocation/deallocation for process cleanup.\n * Used primarily to implement text buffers for the compositor.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2021 K. Lange\n * Copyright (C) 2012 Markus Schober\n */\n#include <stdint.h>\n#include <stdlib.h>\n#include <kernel/types.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n#include <kernel/shm.h>\n#include <kernel/spinlock.h>\n#include <kernel/string.h>\n\n#include <kernel/tree.h>\n#include <kernel/list.h>\n\n\n//static volatile uint8_t bsl; // big shm lock\nstatic spin_lock_t bsl; // big shm lock\ntree_t * shm_tree = NULL;\n\n\nvoid shm_install(void) {\n\tshm_tree = tree_create();\n\ttree_set_root(shm_tree, NULL);\n}\n\n\n/* Accessors */\n\n\nstatic shm_node_t * _get_node(char * shm_path, int create, tree_node_t * from) {\n\n\tchar *pch, *save;\n\tpch = strtok_r(shm_path, SHM_PATH_SEPARATOR, &save);\n\n\ttree_node_t * tnode = from;\n\tforeach(node, tnode->children) {\n\t\ttree_node_t * _node = (tree_node_t *)node->value;\n\t\tshm_node_t *  snode = (shm_node_t *)_node->value;\n\n\t\tif (!strcmp(snode->name, pch)) {\n\t\t\tif (*save == '\\0') {\n\t\t\t\treturn snode;\n\t\t\t}\n\t\t\treturn _get_node(save, create, _node);\n\t\t}\n\t}\n\n\t/* The next node in sequence was not found */\n\tif (create) {\n\t\tshm_node_t * nsnode = malloc(sizeof(shm_node_t));\n\t\tmemcpy(nsnode->name, pch, strlen(pch) + 1);\n\t\tnsnode->chunk = NULL;\n\n\t\ttree_node_t * nnode = tree_node_insert_child(shm_tree, from, nsnode);\n\n\t\tif (*save == '\\0') {\n\t\t\treturn nsnode;\n\t\t}\n\n\t\treturn _get_node(save, create, nnode);\n\t} else {\n\t\treturn NULL;\n\t}\n}\n\nstatic shm_node_t * get_node (char * shm_path, int create) {\n\tchar * _path = malloc(strlen(shm_path)+1);\n\tmemcpy(_path, shm_path, strlen(shm_path)+1);\n\n\tshm_node_t * node = _get_node(_path, create, shm_tree->root);\n\n\tfree(_path);\n\treturn node;\n}\n\n\n/* Create and Release */\n\n\nstatic shm_chunk_t * create_chunk (shm_node_t * parent, size_t size) {\n\tif (!size) return NULL;\n\n\tshm_chunk_t *chunk = malloc(sizeof(shm_chunk_t));\n\tif (chunk == NULL) {\n\t\treturn NULL;\n\t}\n\n\tchunk->parent = parent;\n\tchunk->lock = 0;\n\tchunk->ref_count = 1;\n\n\tchunk->num_frames = (size / 0x1000) + ((size % 0x1000) ? 1 : 0);\n\tchunk->frames = malloc(sizeof(uintptr_t) * chunk->num_frames);\n\tif (chunk->frames == NULL) {\n\t\tfree(chunk);\n\t\treturn NULL;\n\t}\n\n\t/* Now grab some frames for this guy. */\n\tfor (uint32_t i = 0; i < chunk->num_frames; i++) {\n\t\t/* Allocate frame */\n\t\tuintptr_t index = mmu_allocate_a_frame();\n\t\tchunk->frames[i] = index;\n\t}\n\n\treturn chunk;\n}\n\nstatic int release_chunk (shm_chunk_t * chunk) {\n\tif (chunk) {\n\t\tchunk->ref_count--;\n\n\t\t/* Does the chunk need to be freed? */\n\t\tif (chunk->ref_count < 1) {\n#if 0\n\t\t\tdebug_print(INFO, \"Freeing chunk with name %s\", chunk->parent->name);\n#endif\n\n\t\t\t/* First, free the frames used by this chunk */\n\t\t\tfor (uint32_t i = 0; i < chunk->num_frames; i++) {\n\t\t\t\tmmu_frame_release(chunk->frames[i] << 12);\n\t\t\t}\n\n\t\t\t/* Then, get rid of the damn thing */\n\t\t\tchunk->parent->chunk = NULL;\n\t\t\tfree(chunk->frames);\n\t\t\tfree(chunk);\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\treturn -1;\n}\n\n\n/* Mapping and Unmapping */\n\nstatic uintptr_t proc_sbrk(uint32_t num_pages, volatile process_t * volatile proc) {\n\tuintptr_t initial = proc->image.shm_heap;\n\n\tif (initial & 0xFFF) {\n\t\tinitial += 0x1000 - (initial & 0xFFF);\n\t\tproc->image.shm_heap = initial;\n\t}\n\tproc->image.shm_heap += num_pages << 12;\n\n\treturn initial;\n}\n\nstatic void * map_in (shm_chunk_t * chunk, volatile process_t * volatile proc) {\n\tif (!chunk) {\n\t\treturn NULL;\n\t}\n\n\tshm_mapping_t * mapping = malloc(sizeof(shm_mapping_t));\n\tmapping->chunk = chunk;\n\tmapping->num_vaddrs = chunk->num_frames;\n\tmapping->vaddrs = malloc(sizeof(uintptr_t) * mapping->num_vaddrs);\n\n\tuintptr_t last_address = USER_SHM_LOW;\n\tforeach(node, proc->shm_mappings) {\n\t\tshm_mapping_t * m = node->value;\n\t\tif (m->vaddrs[0] > last_address) {\n\t\t\tsize_t gap = (uintptr_t)m->vaddrs[0] - last_address;\n\t\t\tif (gap >= mapping->num_vaddrs * 0x1000) {\n\t\t\t\t/* Map the gap */\n\t\t\t\tfor (unsigned int i = 0; i < chunk->num_frames; ++i) {\n\t\t\t\t\tunion PML * page = mmu_get_page(last_address + (i << 12), MMU_GET_MAKE);\n\t\t\t\t\tpage->bits.page = chunk->frames[i];\n\t\t\t\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t\t\t\t\tmapping->vaddrs[i] = last_address + (i << 12);\n\t\t\t\t}\n\n\t\t\t\t/* Insert us before this node */\n\t\t\t\tlist_insert_before(proc->shm_mappings, node, mapping);\n\n\t\t\t\treturn (void *)mapping->vaddrs[0];\n\t\t\t}\n\t\t}\n\t\tlast_address = m->vaddrs[0] + m->num_vaddrs * 0x1000;\n\t}\n\n\tif (proc->image.shm_heap > last_address) {\n\t\tsize_t gap = proc->image.shm_heap - last_address;\n\t\tif (gap >= mapping->num_vaddrs * 0x1000) {\n\n\t\t\tfor (unsigned int i = 0; i < chunk->num_frames; ++i) {\n\t\t\t\tunion PML * page = mmu_get_page(last_address + (i << 12), MMU_GET_MAKE);\n\t\t\t\tpage->bits.page = chunk->frames[i];\n\t\t\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t\t\t\tmapping->vaddrs[i] = last_address + (i << 12);\n\t\t\t}\n\n\t\t\tlist_insert(proc->shm_mappings, mapping);\n\t\t\treturn (void *)mapping->vaddrs[0];\n\t\t}\n\t}\n\n\n\tfor (uint32_t i = 0; i < chunk->num_frames; i++) {\n\t\tuintptr_t new_vpage = proc_sbrk(1, proc);\n\n\t\tunion PML * page = mmu_get_page(new_vpage, MMU_GET_MAKE);\n\t\tpage->bits.page = chunk->frames[i];\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t\tmapping->vaddrs[i] = new_vpage;\n\t}\n\n\tlist_insert(proc->shm_mappings, mapping);\n\n\treturn (void *)mapping->vaddrs[0];\n}\n\nstatic size_t chunk_size (shm_chunk_t * chunk) {\n\treturn (size_t)(chunk->num_frames * 0x1000);\n}\n\n\n/* Kernel-Facing Functions and Syscalls */\n\n\nvoid * shm_obtain (char * path, size_t * size) {\n\tspin_lock(bsl);\n\tvolatile process_t * volatile proc = this_core->current_process;\n\n\tif (proc->group != 0) {\n\t\tproc = process_from_pid(proc->group);\n\t}\n\n\tshm_node_t * node = get_node(path, 1); // (if it exists, just get it)\n\tshm_chunk_t * chunk = node->chunk;\n\n\tif (chunk == NULL) {\n\t\t/* There's no chunk for that key -- we need to allocate it! */\n\n\t\tif (!size) {\n\t\t\t// The process doesn't want a chunk...?\n\t\t\tspin_unlock(bsl);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchunk = create_chunk(node, *size);\n\t\tif (chunk == NULL) {\n\t\t\tspin_unlock(bsl);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tnode->chunk = chunk;\n\t} else {\n\t\t/* New accessor! */\n\t\tchunk->ref_count++;\n\t}\n\n\tvoid * vshm_start = map_in(chunk, proc);\n\t*size = chunk_size(chunk);\n\n\tspin_unlock(bsl);\n\n\treturn vshm_start;\n}\n\nint shm_release (char * path) {\n\tspin_lock(bsl);\n\tprocess_t * proc = (process_t *)this_core->current_process;\n\n\tif (proc->group != 0) {\n\t\tproc = process_from_pid(proc->group);\n\t}\n\n\t/* First, find the right chunk */\n\tshm_node_t * _node = get_node(path, 0);\n\tif (!_node) {\n\t\tspin_unlock(bsl);\n\t\treturn 1;\n\t}\n\tshm_chunk_t * chunk = _node->chunk;\n\n\t/* Next, find the proc's mapping for that chunk */\n\tnode_t * node = NULL;\n\tforeach (n, proc->shm_mappings) {\n\t\tshm_mapping_t * m = (shm_mapping_t *)n->value;\n\t\tif (m->chunk == chunk) {\n\t\t\tnode = n;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (node == NULL) {\n\t\tspin_unlock(bsl);\n\t\treturn 1;\n\t}\n\n\tshm_mapping_t * mapping = (shm_mapping_t *)node->value;\n\n\t/* Clear the mappings from the process's address space */\n\tfor (uint32_t i = 0; i < mapping->num_vaddrs; i++) {\n\t\tunion PML * page = mmu_get_page(mapping->vaddrs[i], 0);\n\t\tpage->bits.present = 0;\n\t\tmmu_invalidate(mapping->vaddrs[i]);\n\t}\n\n\t/* Clean up */\n\trelease_chunk(chunk);\n\tlist_delete(proc->shm_mappings, node);\n\tfree(node);\n\tfree(mapping);\n\n\tspin_unlock(bsl);\n\treturn 0;\n}\n\n/* This function should only be called if the process's address space\n * is about to be destroyed -- chunks will not be unmounted therefrom ! */\nvoid shm_release_all (process_t * proc) {\n\tspin_lock(bsl);\n\n\tnode_t * node;\n\twhile ((node = list_pop(proc->shm_mappings)) != NULL) {\n\t\tshm_mapping_t * mapping = node->value;\n\t\trelease_chunk(mapping->chunk);\n\t\tfree(mapping);\n\t\tfree(node);\n\t}\n\n\t/* Empty, but don't free, the mappings list */\n\tlist_free(proc->shm_mappings);\n\tproc->shm_mappings->head = proc->shm_mappings->tail = NULL;\n\tproc->shm_mappings->length = 0;\n\n\tspin_unlock(bsl);\n}\n\n\n"
  },
  {
    "path": "kernel/sys/signal.c",
    "content": "/**\n * @file  kernel/sys/signal.c\n * @brief Signal handling.\n *\n * Provides signal entry and delivery; also handles suspending\n * and resuming jobs (SIGTSTP, SIGCONT).\n *\n * As of Misaka 2.1, signal delivery has been largely rewritten:\n * - Signals can only be delivered a times when we would be\n *   normally returning to userspace. This matches behavior in\n *   a number of other kernels.\n * - Signals should cause kernel sleeps to return with an error\n *   state, ending any blocking system calls and allowing them\n *   to either gracefully return or bubble up -ERESTARTSYS to\n *   be restarted.\n * - Userspace signal handlers now push context on the userspace\n *   stack. This is arch-specific behavior.\n * - Signal handler returns work the same as previously, injecting\n *   a special \"magic\" return address that should fault.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2022 K. Lange\n */\n#include <errno.h>\n#include <stdint.h>\n#include <sys/signal.h>\n#include <sys/signal_defs.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/process.h>\n#include <kernel/signal.h>\n#include <kernel/spinlock.h>\n#include <kernel/ptrace.h>\n#include <kernel/syscall.h>\n\nstatic spin_lock_t sig_lock;\n\n#define SIG_DISP_Ign  0\n#define SIG_DISP_Term 1\n#define SIG_DISP_Core 2\n#define SIG_DISP_Stop 3\n#define SIG_DISP_Cont 4\n\nstatic char sig_defaults[] = {\n\t0, /* 0? */\n\t[SIGHUP     ] = SIG_DISP_Term,\n\t[SIGINT     ] = SIG_DISP_Term,\n\t[SIGQUIT    ] = SIG_DISP_Core,\n\t[SIGILL     ] = SIG_DISP_Core,\n\t[SIGTRAP    ] = SIG_DISP_Core,\n\t[SIGABRT    ] = SIG_DISP_Core,\n\t[SIGEMT     ] = SIG_DISP_Core,\n\t[SIGFPE     ] = SIG_DISP_Core,\n\t[SIGKILL    ] = SIG_DISP_Term,\n\t[SIGBUS     ] = SIG_DISP_Core,\n\t[SIGSEGV    ] = SIG_DISP_Core,\n\t[SIGSYS     ] = SIG_DISP_Core,\n\t[SIGPIPE    ] = SIG_DISP_Term,\n\t[SIGALRM    ] = SIG_DISP_Term,\n\t[SIGTERM    ] = SIG_DISP_Term,\n\t[SIGUSR1    ] = SIG_DISP_Term,\n\t[SIGUSR2    ] = SIG_DISP_Term,\n\t[SIGCHLD    ] = SIG_DISP_Ign,\n\t[SIGPWR     ] = SIG_DISP_Ign,\n\t[SIGWINCH   ] = SIG_DISP_Ign,\n\t[SIGURG     ] = SIG_DISP_Ign,\n\t[SIGPOLL    ] = SIG_DISP_Ign,\n\t[SIGSTOP    ] = SIG_DISP_Stop,\n\t[SIGTSTP    ] = SIG_DISP_Stop,\n\t[SIGCONT    ] = SIG_DISP_Cont,\n\t[SIGTTIN    ] = SIG_DISP_Stop,\n\t[SIGTTOUT   ] = SIG_DISP_Stop,\n\t[SIGTTOU    ] = SIG_DISP_Stop,\n\t[SIGVTALRM  ] = SIG_DISP_Term,\n\t[SIGPROF    ] = SIG_DISP_Term,\n\t[SIGXCPU    ] = SIG_DISP_Core,\n\t[SIGXFSZ    ] = SIG_DISP_Core,\n\t[SIGWAITING ] = SIG_DISP_Ign,\n\t[SIGDIAF    ] = SIG_DISP_Term,\n\t[SIGHATE    ] = SIG_DISP_Ign,\n\t[SIGWINEVENT] = SIG_DISP_Ign,\n\t[SIGCAT     ] = SIG_DISP_Ign,\n};\n\n#define shift_signal(signum) (1ULL << signum)\n\n/**\n * @brief If a system call returned -ERESTARTSYS, restart it.\n *\n * Called by both @c handle_signal and @c return_from_signal_handler depending\n * on how the signal was handled.\n *\n * @param r Registers after restoration from signal return.\n */\nstatic void maybe_restart_system_call(struct regs * r, int signum, int no_handler) {\n\tif (signum < 0 || signum >= NUMSIGNALS) return;\n\tif (this_core->current_process->interrupted_system_call && arch_syscall_number(r) == -ERESTARTSYS) {\n\t\tif (sig_defaults[signum] == SIG_DISP_Cont || (this_core->current_process->signals[signum].flags & SA_RESTART)) {\n\t\t\tarch_syscall_return(r, this_core->current_process->interrupted_system_call);\n\t\t\tthis_core->current_process->interrupted_system_call = 0;\n\t\t\tsyscall_handler(r);\n\t\t} else {\n\t\t\tthis_core->current_process->interrupted_system_call = 0;\n\t\t\tarch_syscall_return(r, -EINTR);\n\t\t}\n\t} else if (this_core->current_process->interrupted_system_call && arch_syscall_number(r) == -ERESTARTSIGSUSPEND) {\n\t\tarch_syscall_return(r, this_core->current_process->interrupted_system_call);\n\t\tthis_core->current_process->interrupted_system_call = 0;\n\t\tif (no_handler) {\n\t\t\tsyscall_handler(r);\n\t\t} else {\n\t\t\tarch_syscall_return(r, -EINTR);\n\t\t}\n\t\tif (this_core->current_process->flags & PROC_FLAG_RESTORE_SIGMASK) {\n\t\t\t__sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_RESTORE_SIGMASK));\n\t\t\tthis_core->current_process->blocked_signals = this_core->current_process->restored_signals;\n\t\t}\n\t}\n}\n\n#define PENDING (this_core->current_process->pending_signals & ((~this_core->current_process->blocked_signals) | shift_signal(SIGSTOP) | shift_signal(SIGKILL)))\n\n/**\n * @brief Examine the pending signal and perform an appropriate action.\n *\n * This is called by @c process_check_signals below. It should not be called\n * directly by other parts of the kernel. Previously, it was called through\n * process switching...\n *\n * When a signal handler is called, this does not return. The userspace\n * process is resumed in the signal handler context, and any future calls\n * into the kernel are \"from scratch\".\n *\n * @param proc should be the current active process, which should generally\n *             always be this_core->current_process.\n * @param sig  is the signal node from the pending queue. Currently, this\n *             just contains the signal number and nothing else. It used to\n *             also contain the handler to call, but that led to TOCTOU bugs.\n * @param r    Userspace registers at time of signal entry. This gets passed\n *             forward to @c arch_enter_signal_handler\n * @returns 0 if another signal needs to be handled, 1 otherwise.\n */\nint handle_signal(process_t * proc, int signum, struct regs *r) {\n\tstruct signal_config config = proc->signals[signum];\n\n\t/* Are we being traced? */\n\tif (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) {\n\t\tsignum = ptrace_signal(signum, 0);\n\t}\n\n\tif (proc->flags & PROC_FLAG_FINISHED) {\n\t\treturn 1;\n\t}\n\n\tif (signum == 0 || signum >= NUMSIGNALS) {\n\t\tgoto _ignore_signal;\n\t}\n\n\tif (!config.handler) {\n\t\tchar dowhat = sig_defaults[signum];\n\t\tif (dowhat == SIG_DISP_Term || dowhat == SIG_DISP_Core) {\n\t\t\ttask_exit(((128 + signum) << 8) | signum);\n\t\t\t__builtin_unreachable();\n\t\t} else if (dowhat == SIG_DISP_Stop) {\n\t\t\t__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_SUSPENDED);\n\t\t\tthis_core->current_process->status = 0x7F | (signum << 8) | 0xFF0000;\n\n\t\t\tprocess_t * parent = process_get_parent((process_t *)this_core->current_process);\n\n\t\t\tif (parent && !(parent->flags & PROC_FLAG_FINISHED)) {\n\t\t\t\twakeup_queue(parent->wait_queue);\n\t\t\t}\n\n\t\t\tdo {\n\t\t\t\tswitch_task(0);\n\t\t\t} while (!PENDING);\n\n\t\t\treturn 0; /* Return and handle another */\n\t\t} else if (dowhat == SIG_DISP_Cont) {\n\t\t\t/* Continue doesn't actually do anything different at this stage. */\n\t\t\tgoto _ignore_signal;\n\t\t}\n\t\tgoto _ignore_signal;\n\t}\n\n\t/* If the handler value is 1 we treat it as IGN. */\n\tif (config.handler == 1) goto _ignore_signal;\n\n\tif (config.flags & SA_RESETHAND) {\n\t\tproc->signals[signum].handler = 0;\n\t}\n\n\tarch_enter_signal_handler(config.handler, signum, r);\n\treturn 1; /* Should not be reachable */\n\n_ignore_signal:\n\t/* we still need to check if we need to restart something */\n\n\tmaybe_restart_system_call(r, signum, 1);\n\n\treturn !PENDING;\n}\n\n/**\n * @brief Deliver a signal to another process.\n *\n * Called by both system calls like @c kill as well as by some things\n * that want to trigger SIGSEGV, SIGPIPE, and so on.\n *\n * @param process    PID to deliver to. Must be a single PID, not a group specifier.\n * @param signal     Signal number to deliver.\n * @param force_root If the current process isn't root, it can't send signals to\n *                   processes owned by other users, which means we can't send soft\n *                   signals as part operations like SIGPIPE or SIGCHLD. Kernel callers\n *                   can use this parameter to skip this check.\n * @returns General status, should be suitable for sys_kill return value.\n */\nint send_signal(pid_t process, int signal, int force_root) {\n\tprocess_t * receiver = process_from_pid(process);\n\n\tif (!receiver) return -ESRCH;\n\tif (!force_root && receiver->user != this_core->current_process->user && this_core->current_process->user != USER_ROOT_UID &&\n\t\t!(signal == SIGCONT && receiver->session == this_core->current_process->session)) return -EPERM;\n\tif (receiver->flags & PROC_FLAG_IS_TASKLET) return -EPERM;\n\tif (signal >= NUMSIGNALS || signal < 0) return -EINVAL;\n\tif (receiver->flags & PROC_FLAG_FINISHED) return -ESRCH;\n\tif (signal == 0) return 0;\n\n\tint awaited = receiver->awaited_signals & shift_signal(signal);\n\tint ignored = !receiver->signals[signal].handler && !sig_defaults[signal];\n\tint blocked = (receiver->blocked_signals & shift_signal(signal)) && signal != SIGKILL && signal != SIGSTOP;\n\n\t/* sigcont always unsuspends */\n\tif (sig_defaults[signal] == SIG_DISP_Cont && (receiver->flags & PROC_FLAG_SUSPENDED)) {\n\t\t__sync_and_and_fetch(&receiver->flags, ~(PROC_FLAG_SUSPENDED));\n\t\treceiver->status = 0;\n\t}\n\n\t/* Do nothing if the signal is not being waited for or blocked and the default disposition is to ignore. */\n\tif (!awaited && !blocked && ignored) return 0;\n\n\t/* Mark the signal for delivery. */\n\tspin_lock(sig_lock);\n\treceiver->pending_signals |= shift_signal(signal);\n\tspin_unlock(sig_lock);\n\n\t/* If the signal is blocked and not being awaited, end here. */\n\tif (blocked && !awaited) return 0;\n\n\t/* Informs any blocking events that the process has been interrupted\n\t * by a signal, which should trigger those blocking events to complete\n\t * and potentially return -EINTR or -ERESTARTSYS */\n\tprocess_awaken_signal(receiver);\n\n\t/* Schedule processes awoken by signals to be run. Unless they're us, we'll\n\t * jump to the signal handler as part of returning from this call. */\n\tif (receiver != this_core->current_process && !process_is_ready(receiver)) {\n\t\tmake_process_ready(receiver);\n\t}\n\n\treturn 0;\n}\n\n/**\n * @brief Send a signal to multiple processes.\n *\n * Similar to @c send_signal but for when a negative PID needs to be used.\n *\n * @param group The group process ID. Positive PID, not negative.\n * @param signal Signal number to deliver.\n * @param force_root See explanation in @c send_signal\n * @returns 0 if something was killed, -ESRCH otherwise.\n */\nint group_send_signal(pid_t group, int signal, int force_root) {\n\n\tint kill_self = 0;\n\tint killed_something = 0;\n\n\tif (signal >= NUMSIGNALS || signal < 0) return -EINVAL;\n\n\tforeach(node, process_list) {\n\t\tprocess_t * proc = node->value;\n\t\tif (proc->group == proc->id && proc->job == group) {\n\t\t\t/* Only thread group leaders */\n\t\t\tif (proc->group == this_core->current_process->group) {\n\t\t\t\tkill_self = 1;\n\t\t\t} else {\n\t\t\t\tif (send_signal(proc->group, signal, force_root) == 0) {\n\t\t\t\t\tkilled_something = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (kill_self) {\n\t\tif (send_signal(this_core->current_process->group, signal, force_root) == 0) {\n\t\t\tkilled_something = 1;\n\t\t}\n\t}\n\n\treturn killed_something ? 0 : -ESRCH;\n}\n\n/**\n * @brief Examine the signal delivery queue of the current process, and handle signals.\n *\n * Should be called before a userspace return would happen. If a signal handler is to be\n * run in userspace, then process_check_signals will not return, similar to exec.\n *\n * @param r Userspace registers before signal entry.\n */\nvoid process_check_signals(struct regs * r) {\n_tryagain:\n\tspin_lock(sig_lock);\n\tif (this_core->current_process && !(this_core->current_process->flags & PROC_FLAG_FINISHED)) {\n\t\t/* Set an pending signals that were previously blocked */\n\t\tsigset_t active_signals  = PENDING;\n\n\t\tint signal = 0;\n\t\twhile (active_signals && signal < NUMSIGNALS)  {\n\t\t\tif (active_signals & 1) {\n\t\t\t\tthis_core->current_process->pending_signals &= ~shift_signal(signal);\n\t\t\t\tspin_unlock(sig_lock);\n\t\t\t\tif (handle_signal((process_t*)this_core->current_process, signal, r)) return;\n\t\t\t\tgoto _tryagain;\n\t\t\t}\n\t\t\tactive_signals >>= 1;\n\t\t\tsignal++;\n\t\t}\n\t}\n\tspin_unlock(sig_lock);\n}\n\n/**\n * @brief Restore pre-signal context and possibly restart system calls.\n *\n * To be called by the platform's fault handler when it determines that\n * a signal handler return has been triggered. Calls platform code to restore\n * the previous userspace context (before the signal) from the userspace stack\n * and restarts an interrupted system call if there was one.\n *\n * @param r Registers at fault, passed to platform code for restoration and\n *          then to @c maybe_restart_system_call to handle system call restarts.\n */\nvoid return_from_signal_handler(struct regs *r) {\n\tint signum = arch_return_from_signal_handler(r);\n\tif (PENDING) {\n\t\tprocess_check_signals(r);\n\t}\n\tmaybe_restart_system_call(r,signum,0);\n}\n\n/**\n * @brief Synchronously wait for specified signals to become pending.\n *\n * The signals in @c awaited are set as the current \"awaited set\". Delivery\n * of these signals will ignore the blocked and ignored states and always\n * result in the process be awoken with the signal marked pending if it is\n * sleeping. When the process awakens from @c switch_task the awaiting set\n * will be cleared.\n *\n * If no unblocked signal is pending and an awaited, blocked signal is pending,\n * its signal number will be placed in @p sig and it will be unmarked as\n * pending, returning 0. If a unblocked signal is received, @c -EINTR is\n * returned, and under normal circumstances the caller should raise this\n * return status up and allow normal signal handling to occur.\n *\n * Otherwise, if the process is reawoken by some other means and no unblocked\n * signals or awaited signals are pending, it will apply the awaited set and\n * sleep again. This will repeat until either of these conditions are met.\n *\n * If a signal specified in @p awaited is not currently blocked, but is pending\n * upon entering signal_await, it will be marked as not pending and the call\n * will return immediately; if an unblocked signal is not pending, it will not\n * be awaited: signal_await will return with -EINTR.\n *\n * @param awaited Signals to wait for, should all be blocked by caller.\n * @param sig     Will be set to the awaited signal, if one arrives.\n * @returns 0 if an awaited signal arrives, -EINTR if another signal arrives.\n */\nint signal_await(sigset_t awaited, int * sig) {\n\tdo {\n\t\tsigset_t maybe = awaited & this_core->current_process->pending_signals;\n\t\tif (maybe) {\n\t\t\tint signal = 0;\n\t\t\twhile (maybe && signal < NUMSIGNALS) {\n\t\t\t\tif (maybe & 1) {\n\t\t\t\t\tspin_lock(sig_lock);\n\t\t\t\t\tthis_core->current_process->pending_signals &= ~shift_signal(signal);\n\t\t\t\t\t*sig = signal;\n\t\t\t\t\tspin_unlock(sig_lock);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tmaybe >>= 1;\n\t\t\t\tsignal++;\n\t\t\t}\n\t\t}\n\n\t\t/* Set awaited signals */\n\t\tthis_core->current_process->awaited_signals = awaited;\n\n\t\t/* Sleep */\n\t\tswitch_task(0);\n\n\t\t/* Unset awaited signals. */\n\t\tthis_core->current_process->awaited_signals = 0;\n\t} while (!PENDING);\n\n\treturn -EINTR;\n}\n\n/**\n * @brief Unschedule until a pending signal is delivered.\n */\nvoid signal_wait(void) {\n\twhile (!PENDING) {\n\t\tswitch_task(0);\n\t}\n}\n"
  },
  {
    "path": "kernel/sys/syscall.c",
    "content": "/**\n * @file kernel/sys/syscall.c\n * @brief System call handlers.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n */\n#include <stdint.h>\n#include <errno.h>\n#include <sys/sysfunc.h>\n#include <sys/types.h>\n#include <sys/utsname.h>\n#include <sys/time.h>\n#include <sys/times.h>\n#include <sys/ptrace.h>\n#include <sys/signal.h>\n#include <syscall_nums.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/version.h>\n#include <kernel/pipe.h>\n#include <kernel/shm.h>\n#include <kernel/mmu.h>\n#include <kernel/pty.h>\n#include <kernel/spinlock.h>\n#include <kernel/signal.h>\n#include <kernel/time.h>\n#include <kernel/syscall.h>\n#include <kernel/misc.h>\n#include <kernel/ptrace.h>\n#include <kernel/net/netif.h>\n\nstatic char   hostname[256];\nstatic size_t hostname_len = 0;\n\nint ptr_validate(void * ptr, const char * syscall) {\n\tif (ptr) {\n\t\tif (!PTR_INRANGE(ptr)) {\n\t\t\tsend_signal(this_core->current_process->id, SIGSEGV, 1);\n\t\t\treturn 1;\n\t\t}\n\t\tif (!mmu_validate_user_pointer(ptr,1,0)) return 1;\n\t}\n\treturn 0;\n}\n\n#define PTRCHECK(addr,size,flags) do { if (!mmu_validate_user_pointer(addr,size,flags)) return -EFAULT; } while (0)\n\nlong sys_sbrk(ssize_t size) {\n\tif (size & 0xFFF) return -EINVAL;\n\tvolatile process_t * volatile proc = this_core->current_process;\n\tif (proc->group != 0) {\n\t\tproc = process_from_pid(proc->group);\n\t}\n\tif (!proc) return -EINVAL;\n\tspin_lock(proc->image.lock);\n\tuintptr_t out = proc->image.heap;\n\tfor (uintptr_t i = out; i < out + size; i += 0x1000) {\n\t\tunion PML * page = mmu_get_page(i, MMU_GET_MAKE);\n\t\tif (page->bits.page != 0) {\n\t\t\tprintf(\"odd, %#zx is already allocated?\\n\", i);\n\t\t}\n\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t}\n\tproc->image.heap += size;\n\tspin_unlock(proc->image.lock);\n\treturn (long)out;\n}\n\nextern int elf_module(char ** args);\n\nlong sys_sysfunc(long fn, char ** args) {\n\t/* FIXME: Most of these should be top-level, many are hacks/broken in Misaka */\n\tswitch (fn) {\n\t\tcase TOARU_SYS_FUNC_SYNC:\n\t\t\t/* FIXME: There is no sync ability in the VFS at the moment.\n\t\t\t * XXX: Should this just be an ioctl on individual devices?\n\t\t\t *      Or possibly even an ioctl we can send to arbitrary files? */\n\t\t\tprintf(\"sync: not implemented\\n\");\n\t\t\treturn -EINVAL;\n\n\t\tcase TOARU_SYS_FUNC_LOGHERE:\n\t\t\t/* FIXME: The entire kernel logging system needs to be revamped as\n\t\t\t *        Misaka switched everything to raw printfs, and then also\n\t\t\t *        removed most of them for cleanliness... first task would\n\t\t\t *        be to reintroduce kernel fprintf() to printf to fs_nodes. */\n\t\t\t//if (this_core->current_process->user != 0) return -EACCES;\n\t\t\tprintf(\"\\033[32m%s\\033[0m\", (char*)args);\n\t\t\treturn -EINVAL;\n\n\t\tcase TOARU_SYS_FUNC_KDEBUG:\n\t\t\t/* FIXME: The kernel debugger is completely deprecated and fully removed\n\t\t\t *        in Misaka, and I'm not sure I want to add it back... */\n\t\t\tif (this_core->current_process->user != 0) return -EACCES;\n\t\t\tprintf(\"kdebug: not implemented\\n\");\n\t\t\treturn -EINVAL;\n\n\t\tcase TOARU_SYS_FUNC_CLEARICACHE:\n\t\t\t#ifdef __aarch64__\n\t\t\tPTR_VALIDATE(&args[0]);\n\t\t\tPTR_VALIDATE(&args[1]);\n\t\t\textern void arch_clear_icache(uintptr_t,uintptr_t);\n\t\t\tarch_clear_icache((uintptr_t)args[0], (uintptr_t)args[1]);\n\t\t\t#endif\n\t\t\treturn 0;\n\n\t\tcase TOARU_SYS_FUNC_MUNMAP: {\n\t\t\textern void mmu_unmap_user(uintptr_t addr, size_t size);\n\t\t\tPTR_VALIDATE(&args[0]);\n\t\t\tPTR_VALIDATE(&args[1]);\n\t\t\tvolatile process_t * volatile proc = this_core->current_process;\n\t\t\tif (proc->group != 0) proc = process_from_pid(proc->group);\n\t\t\tif (!proc) return -EFAULT;\n\t\t\tspin_lock(proc->image.lock);\n\t\t\tmmu_unmap_user((uintptr_t)args[0], (size_t)args[1]);\n\t\t\tspin_unlock(proc->image.lock);\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase TOARU_SYS_FUNC_INSMOD:\n\t\t\t/* Linux has init_module as a system call? */\n\t\t\tif (this_core->current_process->user != 0) return -EACCES;\n\t\t\tPTR_VALIDATE(args);\n\t\t\tif (!args) return -EFAULT;\n\t\t\tPTR_VALIDATE(args[0]);\n\t\t\tif (!args[0]) return -EFAULT;\n\t\t\tfor (char ** aa = args; *aa; ++aa) { PTR_VALIDATE(*aa); }\n\t\t\treturn elf_module(args);\n\n\t\tcase TOARU_SYS_FUNC_SETHEAP: {\n\t\t\t/* I'm not really sure how this should be done...\n\t\t\t * traditional brk() would be expected to map everything in-between,\n\t\t\t * but we use this to move the heap in ld.so, and we don't want\n\t\t\t * the stuff in the middle to be mapped necessarily... */\n\t\t\tPTR_VALIDATE(args);\n\t\t\tif (!args) return -EFAULT;\n\t\t\tif (!PTR_INRANGE(args[0])) return -EFAULT;\n\t\t\tif (!args[0]) return -EFAULT;\n\t\t\tvolatile process_t * volatile proc = this_core->current_process;\n\t\t\tif (proc->group != 0) proc = process_from_pid(proc->group);\n\t\t\tif (!proc) return -EFAULT;\n\t\t\tspin_lock(proc->image.lock);\n\t\t\tproc->image.heap = (uintptr_t)args[0];\n\t\t\tspin_unlock(proc->image.lock);\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase TOARU_SYS_FUNC_MMAP: {\n\t\t\t/* FIXME: This whole thing should be removed; we need a proper mmap interface,\n\t\t\t *        preferrably with all of the file mapping options, too. And it should\n\t\t\t *        probably also interact with the SHM subsystem... */\n\t\t\tPTR_VALIDATE(args);\n\t\t\tif (!args) return -EFAULT;\n\t\t\tvolatile process_t * volatile proc = this_core->current_process;\n\t\t\tif (proc->group != 0) proc = process_from_pid(proc->group);\n\t\t\tif (!proc) return -EFAULT;\n\t\t\tspin_lock(proc->image.lock);\n\t\t\t/* Align inputs */\n\t\t\tuintptr_t start = ((uintptr_t)args[0]) & 0xFFFFffffFFFFf000UL;\n\t\t\tuintptr_t end   = ((uintptr_t)args[0] + (size_t)args[1] + 0xFFF) & 0xFFFFffffFFFFf000UL;\n\t\t\tif (!PTR_INRANGE(start)) return -EFAULT;\n\t\t\tif (!PTR_INRANGE(end)) return -EFAULT;\n\t\t\tfor (uintptr_t i = start; i < end; i += 0x1000) {\n\t\t\t\tunion PML * page = mmu_get_page(i, MMU_GET_MAKE);\n\t\t\t\tmmu_frame_allocate(page, MMU_FLAG_WRITABLE);\n\t\t\t}\n\t\t\tspin_unlock(proc->image.lock);\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase TOARU_SYS_FUNC_THREADNAME: {\n\t\t\t/* This should probably be moved to a new system call. */\n\t\t\tint count = 0;\n\t\t\tchar **arg = args;\n\t\t\tPTR_VALIDATE(args);\n\t\t\tif (!args) return -EFAULT;\n\t\t\twhile (*arg) {\n\t\t\t\tPTR_VALIDATE(*arg);\n\t\t\t\tcount++;\n\t\t\t\targ++;\n\t\t\t}\n\t\t\tthis_core->current_process->cmdline = malloc(sizeof(char*)*(count+1));\n\t\t\tint i = 0;\n\t\t\twhile (i < count) {\n\t\t\t\tthis_core->current_process->cmdline[i] = strdup(args[i]);\n\t\t\t\ti++;\n\t\t\t}\n\t\t\tthis_core->current_process->cmdline[i] = NULL;\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase TOARU_SYS_FUNC_SETGSBASE:\n\t\t\t/* This should be a new system call; see what Linux, et al., call it. */\n\t\t\tPTR_VALIDATE(args);\n\t\t\tif (!args) return -EFAULT;\n\t\t\tPTR_VALIDATE(args[0]);\n\t\t\tthis_core->current_process->thread.context.tls_base = (uintptr_t)args[0];\n\t\t\tarch_set_tls_base(this_core->current_process->thread.context.tls_base);\n\t\t\treturn 0;\n\n\t\tcase TOARU_SYS_FUNC_NPROC:\n\t\t\treturn processor_count;\n\n\t\tdefault:\n\t\t\tprintf(\"Bad system function: %ld\\n\", fn);\n\t\t\treturn -EINVAL;\n\t}\n\treturn -EINVAL;\n}\n\n__attribute__((noreturn))\nlong sys_exit(long exitcode) {\n\ttask_exit((exitcode & 0xFF) << 8);\n\t__builtin_unreachable();\n}\n\nlong sys_write(int fd, char * ptr, unsigned long len) {\n#if 0\n\t/* Enable this to force stderr output to always be printed by the kernel. */\n\tif (fd == 2) {\n\t\tprintf_output(len,ptr);\n\t}\n#endif\n\tif (FD_CHECK(fd)) {\n\t\tPTRCHECK(ptr,len,MMU_PTR_NULL);\n\t\tfs_node_t * node = FD_ENTRY(fd);\n\t\tif (!(FD_MODE(fd) & 2)) return -EACCES;\n\t\tif (len && !ptr) {\n\t\t\treturn -EFAULT;\n\t\t}\n\t\tint64_t out = write_fs(node, FD_OFFSET(fd), len, (uint8_t*)ptr);\n\t\tif (out > 0) {\n\t\t\tFD_OFFSET(fd) += out;\n\t\t}\n\t\treturn out;\n\t}\n\treturn -EBADF;\n}\n\nlong sys_pwrite(int fd, void * ptr, size_t count, off_t offset) {\n\tif (FD_CHECK(fd)) {\n\t\tif ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE;\n\t\tPTRCHECK(ptr,count,MMU_PTR_NULL);\n\t\tfs_node_t * node = FD_ENTRY(fd);\n\t\tif (!(FD_MODE(fd) & 2)) return -EACCES;\n\t\tif (count && !ptr) return -EFAULT;\n\t\treturn write_fs(node, offset, count, (uint8_t*)ptr);\n\t}\n\treturn -EBADF;\n}\n\nlong sys_pread(int fd, void * ptr, size_t count, off_t offset) {\n\tif (FD_CHECK(fd)) {\n\t\tif ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE;\n\t\tPTRCHECK(ptr,count,MMU_PTR_NULL|MMU_PTR_WRITE);\n\t\tfs_node_t * node = FD_ENTRY(fd);\n\t\tif (!(FD_MODE(fd) & 01)) return -EACCES;\n\t\tif (count && !ptr) return -EFAULT;\n\t\treturn read_fs(node, offset, count, (uint8_t *)ptr);\n\t}\n\treturn -EBADF;\n}\n\nstatic long stat_node(fs_node_t * fn, uintptr_t st) {\n\tstruct stat * f = (struct stat *)st;\n\n\tif (!fn) {\n\t\t/* XXX: Does this need to zero the stat struct when returning -ENOENT? */\n\t\tmemset(f, 0x00, sizeof(struct stat));\n\t\treturn -ENOENT;\n\t}\n\n\tf->st_dev   = (uint16_t)(((uint64_t)fn->device & 0xFFFF0) >> 8);\n\tf->st_ino   = fn->inode;\n\n\tuint32_t flags = 0;\n\tif (fn->flags & FS_FILE)        { flags |= _IFREG; }\n\tif (fn->flags & FS_DIRECTORY)   { flags |= _IFDIR; }\n\tif (fn->flags & FS_CHARDEVICE)  { flags |= _IFCHR; }\n\tif (fn->flags & FS_BLOCKDEVICE) { flags |= _IFBLK; }\n\tif (fn->flags & FS_PIPE)        { flags |= _IFIFO; }\n\tif (fn->flags & FS_SYMLINK)     { flags |= _IFLNK; }\n\tif (fn->flags & FS_SOCKET)      { flags |= _IFSOCK; }\n\n\tf->st_mode  = fn->mask | flags;\n\tf->st_nlink = fn->nlink;\n\tf->st_uid   = fn->uid;\n\tf->st_gid   = fn->gid;\n\tf->st_rdev  = 0;\n\tf->st_size  = fn->length;\n\n\tf->st_atim.tv_sec = fn->atime;\n\tf->st_atim.tv_nsec = 0;\n\tf->st_mtim.tv_sec = fn->mtime;\n\tf->st_mtim.tv_nsec = 0;\n\tf->st_ctim.tv_sec = fn->ctime;\n\tf->st_ctim.tv_nsec = 0;\n\tf->st_blksize = 512; /* whatever */\n\n\tif (fn->get_size) {\n\t\tf->st_size = fn->get_size(fn);\n\t}\n\n\treturn 0;\n}\n\nlong sys_stat(int fd, uintptr_t st) {\n\tPTR_VALIDATE(st);\n\tif (!st) return -EFAULT;\n\tif (FD_CHECK(fd)) {\n\t\treturn stat_node(FD_ENTRY(fd), st);\n\t}\n\treturn -EBADF;\n}\n\nlong sys_statf(char * file, uintptr_t st) {\n\tint result;\n\tPTR_VALIDATE(file);\n\tPTR_VALIDATE(st);\n\n\tif (!file || !st) return -EFAULT;\n\n\tfs_node_t * fn = kopen(file, 0);\n\tresult = stat_node(fn, st);\n\tif (fn) {\n\t\tclose_fs(fn);\n\t}\n\treturn result;\n}\n\nlong sys_symlink(char * target, char * name) {\n\tPTR_VALIDATE(target);\n\tPTR_VALIDATE(name);\n\tif (!target || !name) return -EFAULT;\n\treturn symlink_fs(target, name);\n}\n\nlong sys_readlink(const char * file, char * ptr, long len) {\n\tPTR_VALIDATE(file);\n\tPTRCHECK(ptr,len,0);\n\tif (!file) return -EFAULT;\n\tfs_node_t * node = kopen((char *) file, O_PATH | O_NOFOLLOW);\n\tif (!node) {\n\t\treturn -ENOENT;\n\t}\n\tlong rv = readlink_fs(node, ptr, len);\n\tclose_fs(node);\n\treturn rv;\n}\n\nlong sys_lstat(char * file, uintptr_t st) {\n\tPTR_VALIDATE(file);\n\tPTR_VALIDATE(st);\n\tif (!file || !st) return -EFAULT;\n\tfs_node_t * fn = kopen(file, O_PATH | O_NOFOLLOW);\n\tlong result = stat_node(fn, st);\n\tif (fn) {\n\t\tclose_fs(fn);\n\t}\n\treturn result;\n}\n\nlong sys_open(const char * file, long flags, long mode) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\tfs_node_t * node = kopen((char *)file, flags);\n\n\tint access_bits = 0;\n\n\tif (node && (flags & O_CREAT) && (flags & O_EXCL)) {\n\t\tclose_fs(node);\n\t\treturn -EEXIST;\n\t}\n\n\tif (!node && (flags & O_CREAT)) {\n\t\tint result = create_file_fs((char *)file, mode);\n\t\t/*\n\t\t * This still potentially has an issue, particularly with\n\t\t * O_EXCL, where another process can replace this file,\n\t\t * or otherwise create a different file, and then the\n\t\t * file we created above is not the one we are opening.\n\t\t *\n\t\t * In the new VFS, creating a file should return an\n\t\t * inode/dirent representing the new file, so we don't\n\t\t * have to go and immediately try to open it.\n\t\t */\n\t\tif (!result) {\n\t\t\tnode = kopen((char *)file, flags);\n\t\t} else {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tif (!(flags & O_WRONLY) || (flags & O_RDWR)) {\n\t\tif (node && !has_permission(node, 04)) {\n\t\t\tclose_fs(node);\n\t\t\treturn -EACCES;\n\t\t} else {\n\t\t\taccess_bits |= 01;\n\t\t}\n\t}\n\n\tif ((flags & O_RDWR) || (flags & O_WRONLY)) {\n\t\tif (node && !has_permission(node, 02)) {\n\t\t\tclose_fs(node);\n\t\t\treturn -EACCES;\n\t\t}\n\t\tif (node && (node->flags & FS_DIRECTORY)) {\n\t\t\treturn -EISDIR;\n\t\t}\n\t\tif ((flags & O_RDWR) || (flags & O_WRONLY)) {\n\t\t\t/* truncate doesn't grant write permissions */\n\t\t\taccess_bits |= 02;\n\t\t}\n\t}\n\n\tif (node && (flags & O_DIRECTORY)) {\n\t\tif (!(node->flags & FS_DIRECTORY)) {\n\t\t\treturn -ENOTDIR;\n\t\t}\n\t}\n\n\tif (node && (flags & O_TRUNC)) {\n\t\tif (!(access_bits & 02)) {\n\t\t\tclose_fs(node);\n\t\t\treturn -EINVAL;\n\t\t}\n\t\ttruncate_fs(node, 0);\n\t}\n\n\tif (!node) {\n\t\treturn -ENOENT;\n\t}\n\tif (node && (flags & O_CREAT) && (node->flags & FS_DIRECTORY)) {\n\t\tclose_fs(node);\n\t\treturn -EISDIR;\n\t}\n\tint fd = process_append_fd((process_t *)this_core->current_process, node);\n\tFD_MODE(fd) = access_bits;\n\tif (flags & O_APPEND) {\n\t\tFD_OFFSET(fd) = node->length;\n\t} else {\n\t\tFD_OFFSET(fd) = 0;\n\t}\n\treturn fd;\n}\n\nlong sys_close(int fd) {\n\tif (FD_CHECK(fd)) {\n\t\tclose_fs(FD_ENTRY(fd));\n\t\tFD_ENTRY(fd) = NULL;\n\t\treturn 0;\n\t}\n\treturn -EBADF;\n}\n\nlong sys_seek(int fd, long offset, long whence) {\n\tif (FD_CHECK(fd)) {\n\t\tif ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE;\n\t\tswitch (whence) {\n\t\t\tcase 0:\n\t\t\t\tFD_OFFSET(fd) = offset;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tFD_OFFSET(fd) += offset;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tFD_OFFSET(fd) = FD_ENTRY(fd)->length + offset;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn -EINVAL;\n\t\t}\n\t\treturn FD_OFFSET(fd);\n\t}\n\treturn -EBADF;\n}\n\nlong sys_read(int fd, char * ptr, unsigned long len) {\n\tif (FD_CHECK(fd)) {\n\t\tPTRCHECK(ptr,len,MMU_PTR_NULL|MMU_PTR_WRITE);\n\t\tif (len && !ptr) {\n\t\t\treturn -EFAULT;\n\t\t}\n\n\t\tfs_node_t * node = FD_ENTRY(fd);\n\t\tif (!(FD_MODE(fd) & 01)) {\n\t\t\treturn -EACCES;\n\t\t}\n\t\tuint64_t out = read_fs(node, FD_OFFSET(fd), len, (uint8_t *)ptr);\n\t\tFD_OFFSET(fd) += out;\n\t\treturn out;\n\t}\n\treturn -EBADF;\n}\n\nlong sys_ioctl(int fd, unsigned long request, void * argp) {\n\tif (FD_CHECK(fd)) {\n\t\tPTR_VALIDATE(argp);\n\t\treturn ioctl_fs(FD_ENTRY(fd), request, argp);\n\t}\n\treturn -EBADF;\n}\n\nlong sys_readdir(int fd, long index, struct dirent * entry) {\n\tif (FD_CHECK(fd)) {\n\t\tPTR_VALIDATE(entry);\n\t\tif (!entry) return -EFAULT;\n\t\tstruct dirent * kentry = readdir_fs(FD_ENTRY(fd), (uint64_t)index);\n\t\tif (kentry) {\n\t\t\tmemcpy(entry, kentry, sizeof *entry);\n\t\t\tfree(kentry);\n\t\t\treturn 1;\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t}\n\treturn -EBADF;\n}\n\nlong sys_mkdir(char * path, uint64_t mode) {\n\tPTR_VALIDATE(path);\n\tif (!path) return -EFAULT;\n\treturn mkdir_fs(path, mode);\n}\n\nlong sys_access(const char * file, long flags) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\tfs_node_t * node = kopen((char *)file, 0);\n\tif (!node) return -ENOENT;\n\tclose_fs(node);\n\treturn 0;\n}\n\nlong sys_chmod(char * file, long mode) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\tfs_node_t * fn = kopen(file, 0);\n\tif (fn) {\n\t\t/* Can group members change bits? I think it's only owners. */\n\t\tif (this_core->current_process->user != 0 && this_core->current_process->user != fn->uid) {\n\t\t\tclose_fs(fn);\n\t\t\treturn -EACCES;\n\t\t}\n\t\tlong result = chmod_fs(fn, mode);\n\t\tclose_fs(fn);\n\t\treturn result;\n\t} else {\n\t\treturn -ENOENT;\n\t}\n}\n\nlong sys_fchmod(int fd, long mode) {\n\tif (!FD_CHECK(fd)) return -EBADF;\n\tif (this_core->current_process->user != 0 && this_core->current_process->user != FD_ENTRY(fd)->uid) return -EACCES;\n\treturn chmod_fs(FD_ENTRY(fd), mode);\n}\n\nlong sys_rename(const char * src, const char * dest) {\n\tPTR_VALIDATE(src);\n\tif (!src) return -EFAULT;\n\tPTR_VALIDATE(dest);\n\tif (!dest) return -EFAULT;\n\n\textern int rename_file_fs(const char * src, const char * dest);\n\treturn rename_file_fs(src, dest);\n}\n\nstatic int current_group_matches(gid_t gid) {\n\tif (gid == this_core->current_process->user_group) return 1;\n\tfor (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) {\n\t\tif (gid == this_core->current_process->supplementary_group_list[i]) return 1;\n\t}\n\treturn 0;\n}\n\nlong sys_chown(char * file, uid_t uid, uid_t gid) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\tfs_node_t * fn = kopen(file, 0);\n\tif (fn) {\n\n\t\t/* Only a privileged user can change the owner of a file. */\n\t\tif (this_core->current_process->user != USER_ROOT_UID && uid != -1) {\n\t\t\tgoto _access;\n\t\t}\n\n\t\tif (this_core->current_process->user != USER_ROOT_UID && gid != -1) {\n\t\t\t/* The owner of a file... */\n\t\t\tif (this_core->current_process->user != fn->uid) {\n\t\t\t\tgoto _access;\n\t\t\t}\n\n\t\t\t/* May change the group of the file to one that the owner is a member of... */\n\t\t\tif (!current_group_matches(gid)) {\n\t\t\t\tgoto _access;\n\t\t\t}\n\t\t}\n\n\t\tif ((uid != -1 || gid != -1) && (fn->mask & 0x800)) {\n\t\t\t/* Whenever the owner or group of a setuid executable is changed, it\n\t\t\t * loses the setuid bit. */\n\t\t\t chmod_fs(fn, fn->mask & (~0x800));\n\t\t}\n\n\t\tlong result = chown_fs(fn, uid, gid);\n\t\tclose_fs(fn);\n\t\treturn result;\n\t} else {\n\t\treturn -ENOENT;\n\t}\n\n_access:\n\tclose_fs(fn);\n\treturn -EACCES;\n}\n\nlong sys_fchown(int fd, uid_t uid, uid_t gid) {\n\tif (!FD_CHECK(fd)) return -EBADF;\n\tif (this_core->current_process->user != USER_ROOT_UID && uid != -1) return -EACCES;\n\tif (this_core->current_process->user != USER_ROOT_UID && gid != -1) {\n\t\tif (this_core->current_process->user != FD_ENTRY(fd)->uid) return -EACCES;\n\t\tif (!current_group_matches(gid)) return -EACCES;\n\t}\n\n\tif ((uid != -1 || gid != -1) && (FD_ENTRY(fd)->mask & 0x800)) {\n\t\t/* Whenever the owner or group of a setuid executable is changed, it\n\t\t * loses the setuid bit. */\n\t\t chmod_fs(FD_ENTRY(fd), FD_ENTRY(fd)->mask & (~0x800));\n\t}\n\n\treturn chown_fs(FD_ENTRY(fd), uid, gid);\n}\n\nlong sys_truncate(char * file, off_t size) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\tif (size < 0) return -EINVAL;\n\n\tfs_node_t * fn = kopen(file, 0);\n\n\tif (!fn) return -ENOENT;\n\n\t/* Need write permission */\n\tif (!has_permission(fn, 04)) {\n\t\tclose_fs(fn);\n\t\treturn -EACCES;\n\t}\n\n\tif (!fn->truncate) {\n\t\tclose_fs(fn);\n\t\treturn -ENOTSUP;\n\t}\n\n\tlong out = fn->truncate(fn, size);\n\tclose_fs(fn);\n\treturn out;\n}\n\nlong sys_ftruncate(int fd, off_t size) {\n\tif (!FD_CHECK(fd)) return -EBADF;\n\tif (!(FD_MODE(fd) & 2)) return -EACCES;\n\tif (size < 0) return -EINVAL;\n\tif (!FD_ENTRY(fd)->truncate) return -ENOTSUP;\n\treturn FD_ENTRY(fd)->truncate(FD_ENTRY(fd), size);\n}\n\nlong sys_gettimeofday(struct timeval * tv, void * tz) {\n\tPTR_VALIDATE(tv);\n\tPTR_VALIDATE(tz);\n\tif (!tv) return -EFAULT;\n\treturn gettimeofday(tv, tz);\n}\n\nlong sys_settimeofday(struct timeval * tv, void * tz) {\n\textern int settimeofday(struct timeval * t, void *z);\n\tif (this_core->current_process->user != USER_ROOT_UID) return -EPERM;\n\tPTR_VALIDATE(tv);\n\tPTR_VALIDATE(tz);\n\treturn settimeofday(tv,tz);\n}\n\nlong sys_getuid(void) {\n\treturn (long)this_core->current_process->real_user;\n}\n\nlong sys_geteuid(void) {\n\treturn (long)this_core->current_process->user;\n}\n\nlong sys_setuid(uid_t new_uid) {\n\tif (this_core->current_process->user == USER_ROOT_UID) {\n\t\tthis_core->current_process->user = new_uid;\n\t\tthis_core->current_process->real_user = new_uid;\n\t\treturn 0;\n\t}\n\treturn -EPERM;\n}\n\nlong sys_getgid(void) {\n\treturn (long)this_core->current_process->real_user_group;\n}\n\nlong sys_getegid(void) {\n\treturn (long)this_core->current_process->user_group;\n}\n\nlong sys_setgid(gid_t new_gid) {\n\tif (this_core->current_process->user == USER_ROOT_UID) {\n\t\tthis_core->current_process->user_group = new_gid;\n\t\tthis_core->current_process->real_user_group = new_gid;\n\t\treturn 0;\n\t}\n\treturn -EPERM;\n}\n\nlong sys_getgroups(int size, gid_t list[]) {\n\tif (size == 0) {\n\t\treturn this_core->current_process->supplementary_group_count;\n\t} else if (size < this_core->current_process->supplementary_group_count) {\n\t\treturn -EINVAL;\n\t} else {\n\t\tPTR_VALIDATE(list);\n\t\tif (!list) return -EFAULT;\n\t\tfor (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) {\n\t\t\tPTR_VALIDATE(list + i);\n\t\t\tlist[i] = this_core->current_process->supplementary_group_list[i];\n\t\t}\n\t\treturn this_core->current_process->supplementary_group_count;\n\t}\n}\n\nlong sys_setgroups(int size, const gid_t list[]) {\n\tif (this_core->current_process->user != USER_ROOT_UID) return -EPERM;\n\tif (size < 0) return -EINVAL;\n\tif (size > 32) return -EINVAL; /* Arbitrary decision */\n\n\t/* Free the current set. */\n\tif (this_core->current_process->supplementary_group_count) {\n\t\tfree(this_core->current_process->supplementary_group_list);\n\t\tthis_core->current_process->supplementary_group_list = NULL;\n\t}\n\n\tthis_core->current_process->supplementary_group_count = size;\n\tif (size == 0) return 0;\n\n\tthis_core->current_process->supplementary_group_list = malloc(sizeof(gid_t) * size);\n\n\tPTR_VALIDATE(list);\n\tif (!list) return -EFAULT;\n\n\tfor (int i = 0; i < size; ++i) {\n\t\tPTR_VALIDATE(list + i);\n\t\tthis_core->current_process->supplementary_group_list[i] = list[i];\n\t}\n\n\treturn 0;\n}\n\n\nlong sys_getpid(void) {\n\t/* The user actually wants the pid of the originating thread (which can be us). */\n\treturn this_core->current_process->group ? (long)this_core->current_process->group : (long)this_core->current_process->id;\n}\n\nlong sys_gettid(void) {\n\treturn (long)this_core->current_process->id;\n}\n\nlong sys_setsid(void) {\n\tif (this_core->current_process->job == this_core->current_process->group) {\n\t\treturn -EPERM;\n\t}\n\tthis_core->current_process->session = this_core->current_process->group;\n\tthis_core->current_process->job = this_core->current_process->group;\n\treturn this_core->current_process->session;\n}\n\nlong sys_setpgid(pid_t pid, pid_t pgid) {\n\tif (pgid < 0) {\n\t\treturn -EINVAL;\n\t}\n\tprocess_t * proc = NULL;\n\tif (pid == 0) {\n\t\tproc = (process_t*)this_core->current_process;\n\t} else {\n\t\tproc = process_from_pid(pid);\n\t}\n\n\tif (!proc) {\n\t\treturn -ESRCH;\n\t}\n\tif (proc->session != this_core->current_process->session || proc->session == proc->group) {\n\t\treturn -EPERM;\n\t}\n\n\tif (pgid == 0) {\n\t\tproc->job = proc->group;\n\t} else {\n\t\tprocess_t * pgroup = process_from_pid(pgid);\n\n\t\tif (!pgroup || pgroup->session != proc->session) {\n\t\t\treturn -EPERM;\n\t\t}\n\n\t\tproc->job = pgid;\n\t}\n\treturn 0;\n}\n\nlong sys_getpgid(pid_t pid) {\n\tprocess_t * proc;\n\tif (pid == 0) {\n\t\tproc = (process_t*)this_core->current_process;\n\t} else {\n\t\tproc = process_from_pid(pid);\n\t}\n\n\tif (!proc) {\n\t\treturn -ESRCH;\n\t}\n\n\treturn proc->job;\n}\n\nlong sys_uname(struct utsname * name) {\n\tPTR_VALIDATE(name);\n\tif (!name) return -EFAULT;\n\tchar version_number[256];\n\tsnprintf(version_number, 255, __kernel_version_format,\n\t\t\t__kernel_version_major,\n\t\t\t__kernel_version_minor,\n\t\t\t__kernel_version_lower,\n\t\t\t__kernel_version_suffix);\n\tchar version_string[256];\n\tsnprintf(version_string, 255, \"%s %s %s\",\n\t\t\t__kernel_version_codename,\n\t\t\t__kernel_build_date,\n\t\t\t__kernel_build_time);\n\tstrcpy(name->sysname,  __kernel_name);\n\tstrcpy(name->nodename, hostname);\n\tstrcpy(name->release,  version_number);\n\tstrcpy(name->version,  version_string);\n\tstrcpy(name->machine,  __kernel_arch);\n\tstrcpy(name->domainname, \"\"); /* TODO */\n\treturn 0;\n}\n\nlong sys_chdir(char * newdir) {\n\tPTR_VALIDATE(newdir);\n\tif (!newdir) return -EFAULT;\n\tchar * path = canonicalize_path(this_core->current_process->wd_name, newdir);\n\tfs_node_t * chd = kopen(path, 0);\n\tif (chd) {\n\t\tif ((chd->flags & FS_DIRECTORY) == 0) {\n\t\t\tclose_fs(chd);\n\t\t\treturn -ENOTDIR;\n\t\t}\n\t\tif (!has_permission(chd, 01)) {\n\t\t\tclose_fs(chd);\n\t\t\treturn -EACCES;\n\t\t}\n\t\tclose_fs(chd);\n\t\tfree(this_core->current_process->wd_name);\n\t\tthis_core->current_process->wd_name = malloc(strlen(path) + 1);\n\t\tmemcpy(this_core->current_process->wd_name, path, strlen(path) + 1);\n\t\treturn 0;\n\t} else {\n\t\treturn -ENOENT;\n\t}\n}\n\nlong sys_getcwd(char * buf, size_t size) {\n\tif (buf) {\n\t\tPTR_VALIDATE(buf);\n\t\tsize_t len = strlen(this_core->current_process->wd_name) + 1;\n\t\treturn (long)memcpy(buf, this_core->current_process->wd_name, size < len ? size : len);\n\t}\n\treturn 0;\n}\n\nlong sys_dup2(int old, int new) {\n\treturn process_move_fd((process_t *)this_core->current_process, old, new);\n}\n\nlong sys_fcntl(int fd, int cmd, long arg) {\n\tif (!FD_CHECK(fd)) return -EBADF;\n\n\tswitch (cmd) {\n\t\tcase F_GETFD:\n\t\t\treturn 0; /* We don't support any flags. CLOEXEC is the only thing in here. */\n\t\tcase F_SETFD:\n\t\t\treturn 0; /* We don't support any flags, so can't set any flags. */\n\t\tcase F_GETFL: {\n\t\t\tint mode = 0;\n\t\t\tif (FD_MODE(fd) & 03) mode = O_RDWR;\n\t\t\telse if (FD_MODE(fd) & 01) mode = O_RDONLY;\n\t\t\telse if (FD_MODE(fd) & 02) mode = O_WRONLY;\n\t\t\t/* TODO we don't persist O_APPEND and there are other flags we don't support */\n\t\t\treturn mode;\n\t\t}\n\t\tcase F_SETFL: {\n\t\t\treturn 0; /* TODO NONBLOCK, APPEND, SYNC... */\n\t\t}\n\t\tcase F_DUPFD: {\n\t\t\tif (arg < 0 || arg > 256) return -EINVAL; /* We expect a value of, like, 10 from dash. */\n\t\t\textern long process_fd_dup_least(process_t *, long, long);\n\t\t\treturn process_fd_dup_least((process_t*)this_core->current_process, fd, arg);\n\t\t}\n\t\tcase F_GETLK:\n\t\tcase F_SETLK:\n\t\tcase F_SETLKW:\n\t\t\t/* No lock support */\n\t\t\treturn -EINVAL;\n\t}\n\n\treturn -EINVAL;\n}\n\nlong sys_sethostname(char * new_hostname) {\n\tif (this_core->current_process->user == USER_ROOT_UID) {\n\t\tPTR_VALIDATE(new_hostname);\n\t\tif (!new_hostname) return -EFAULT;\n\t\tsize_t len = strlen(new_hostname) + 1;\n\t\tif (len > 256) {\n\t\t\treturn -ENAMETOOLONG;\n\t\t}\n\t\thostname_len = len;\n\t\tmemcpy(hostname, new_hostname, hostname_len);\n\t\treturn 0;\n\t} else {\n\t\treturn -EPERM;\n\t}\n}\n\nlong sys_gethostname(char * buffer) {\n\tPTR_VALIDATE(buffer);\n\tif (!buffer) return -EFAULT;\n\tmemcpy(buffer, hostname, hostname_len);\n\treturn hostname_len;\n}\n\nlong sys_mount(char * arg, char * mountpoint, char * type, unsigned long flags, void * data) {\n\t/* TODO: Make use of flags and data from mount command. */\n\t(void)flags;\n\t(void)data;\n\n\tif (this_core->current_process->user != USER_ROOT_UID) {\n\t\treturn -EPERM;\n\t}\n\n\tif (PTR_INRANGE(arg) && PTR_INRANGE(mountpoint) && PTR_INRANGE(type)) {\n\t\treturn vfs_mount_type(type, arg, mountpoint);\n\t}\n\n\treturn -EFAULT;\n}\n\nlong sys_umask(long mode) {\n\tthis_core->current_process->mask = mode & 0777;\n\treturn 0;\n}\n\nlong sys_unlink(char * file) {\n\tPTR_VALIDATE(file);\n\tif (!file) return -EFAULT;\n\treturn unlink_fs(file);\n}\n\nlong sys_execve(const char * filename, char *const argv[], char *const envp[]) {\n\tPTR_VALIDATE(filename);\n\tPTR_VALIDATE(argv);\n\tPTR_VALIDATE(envp);\n\n\tif (!filename || !argv) return -EFAULT;\n\n\tint argc = 0;\n\tint envc = 0;\n\twhile (argv[argc]) {\n\t\tPTR_VALIDATE(argv[argc]);\n\t\t++argc;\n\t}\n\n\tif (envp) {\n\t\twhile (envp[envc]) {\n\t\t\tPTR_VALIDATE(envp[envc]);\n\t\t\t++envc;\n\t\t}\n\t}\n\n\tchar **argv_ = malloc(sizeof(char*) * (argc + 1));\n\tfor (int j = 0; j < argc; ++j) {\n\t\targv_[j] = malloc(strlen(argv[j]) + 1);\n\t\tmemcpy(argv_[j], argv[j], strlen(argv[j]) + 1);\n\t}\n\targv_[argc] = 0;\n\tchar ** envp_;\n\tif (envp && envc) {\n\t\tenvp_ = malloc(sizeof(char*) * (envc + 1));\n\t\tfor (int j = 0; j < envc; ++j) {\n\t\t\tenvp_[j] = malloc(strlen(envp[j]) + 1);\n\t\t\tmemcpy(envp_[j], envp[j], strlen(envp[j]) + 1);\n\t\t}\n\t\tenvp_[envc] = 0;\n\t} else {\n\t\tenvp_ = malloc(sizeof(char*));\n\t\tenvp_[0] = NULL;\n\t}\n\n\t/**\n\t * FIXME: For legacy reasons, we're just going to close everything >2 for now,\n\t *        but we should really implement proper CLOEXEC semantics...\n\t */\n\tfor (unsigned int i = 3; i < this_core->current_process->fds->length; ++i) {\n\t\tif (this_core->current_process->fds->entries[i]) {\n\t\t\tclose_fs(this_core->current_process->fds->entries[i]);\n\t\t\tthis_core->current_process->fds->entries[i] = NULL;\n\t\t}\n\t}\n\n\tshm_release_all((process_t *)this_core->current_process);\n\n\tthis_core->current_process->cmdline = argv_;\n\treturn exec(filename, argc, argv_, envp_, 0);\n}\n\nlong sys_fork(void) {\n\treturn fork();\n}\n\nlong sys_clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) {\n\tif (!new_stack || !PTR_INRANGE(new_stack)) return -EINVAL;\n\tif (!thread_func || !PTR_INRANGE(thread_func)) return -EINVAL;\n\treturn (int)clone(new_stack, thread_func, arg);\n}\n\nlong sys_waitpid(int pid, int * status, int options) {\n\tif (status && !PTR_INRANGE(status)) return -EINVAL;\n\treturn waitpid(pid, status, options);\n}\n\nlong sys_yield(void) {\n\tswitch_task(1);\n\treturn 1;\n}\n\nlong sys_sleepabs(unsigned long seconds, unsigned long subseconds) {\n\t/* Mark us as asleep until <some time period> */\n\tsleep_until((process_t *)this_core->current_process, seconds, subseconds);\n\n\t/* Switch without adding us to the queue */\n\t//printf(\"process %p (pid=%d) entering sleep until %ld.%06ld\\n\", current_process, current_process->id, seconds, subseconds);\n\tswitch_task(0);\n\n\tunsigned long timer_ticks = 0, timer_subticks = 0;\n\trelative_time(0,0,&timer_ticks,&timer_subticks);\n\t//printf(\"process %p (pid=%d) resumed from sleep at %ld.%06ld\\n\", current_process, current_process->id, timer_ticks, timer_subticks);\n\n\tif (seconds > timer_ticks || (seconds == timer_ticks && subseconds >= timer_subticks)) {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\nlong sys_sleep(unsigned long seconds, unsigned long subseconds) {\n\tunsigned long s, ss;\n\trelative_time(seconds, subseconds * 10000, &s, &ss);\n\treturn sys_sleepabs(s, ss);\n}\n\nlong sys_pipe(int pipes[2]) {\n\tif (!pipes || !PTR_INRANGE(pipes)) {\n\t\treturn -EFAULT;\n\t}\n\n\tfs_node_t * outpipes[2];\n\n\tmake_unix_pipe(outpipes);\n\n\topen_fs(outpipes[0], 0);\n\topen_fs(outpipes[1], 0);\n\n\tpipes[0] = process_append_fd((process_t *)this_core->current_process, outpipes[0]);\n\tpipes[1] = process_append_fd((process_t *)this_core->current_process, outpipes[1]);\n\tFD_MODE(pipes[0]) = 03;\n\tFD_MODE(pipes[1]) = 03;\n\treturn 0;\n}\n\nlong sys_signal(long signum, uintptr_t handler) {\n\tif (signum >= NUMSIGNALS || signum < 0) return -EINVAL;\n\tif (signum == SIGKILL || signum == SIGSTOP) return -EINVAL;\n\tuintptr_t old = this_core->current_process->signals[signum].handler;\n\tthis_core->current_process->signals[signum].handler = handler;\n\tthis_core->current_process->signals[signum].flags = SA_RESTART;\n\treturn old;\n}\n\nlong sys_sigaction(int signum, struct sigaction *act, struct sigaction *oldact) {\n\tif (act) PTRCHECK(act,sizeof(struct sigaction),0);\n\tif (oldact) PTRCHECK(oldact,sizeof(struct sigaction),MMU_PTR_WRITE);\n\n\tif (signum >= NUMSIGNALS || signum < 0) return -EINVAL;\n\tif (signum == SIGKILL || signum == SIGSTOP) return -EINVAL;\n\n\tif (oldact) {\n\t\toldact->sa_handler = (_sig_func_ptr)this_core->current_process->signals[signum].handler;\n\t\toldact->sa_mask    = this_core->current_process->signals[signum].mask;\n\t\toldact->sa_flags   = this_core->current_process->signals[signum].flags;\n\t}\n\n\tif (act) {\n\t\tthis_core->current_process->signals[signum].handler = (uintptr_t)act->sa_handler;\n\t\tthis_core->current_process->signals[signum].mask    = act->sa_mask;\n\t\tthis_core->current_process->signals[signum].flags   = act->sa_flags;\n\t}\n\n\treturn 0;\n}\n\nlong sys_sigpending(sigset_t * set) {\n\tPTRCHECK(set,sizeof(sigset_t),MMU_PTR_WRITE);\n\t*set = this_core->current_process->pending_signals;\n\treturn 0;\n}\n\nlong sys_sigprocmask(int how, sigset_t *restrict set, sigset_t * restrict oset) {\n\tif (oset) {\n\t\tPTRCHECK(oset,sizeof(sigset_t),MMU_PTR_WRITE);\n\t\t*oset = this_core->current_process->blocked_signals;\n\t}\n\n\tif (set) {\n\t\tPTRCHECK(set,sizeof(sigset_t),0);\n\t\tswitch (how) {\n\t\t\tcase SIG_SETMASK:\n\t\t\t\tthis_core->current_process->blocked_signals = *set;\n\t\t\t\tbreak;\n\t\t\tcase SIG_BLOCK:\n\t\t\t\tthis_core->current_process->blocked_signals |= *set;\n\t\t\t\tbreak;\n\t\t\tcase SIG_UNBLOCK:\n\t\t\t\tthis_core->current_process->blocked_signals &= ~*set;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn -EINVAL;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nextern void signal_wait(void);\nlong sys_sigsuspend(const sigset_t *set) {\n\tPTRCHECK(set,sizeof(sigset_t),0);\n\n\tthis_core->current_process->restored_signals = this_core->current_process->blocked_signals;\n\tthis_core->current_process->blocked_signals = *set;\n\n\tsignal_wait();\n\n\t__sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_RESTORE_SIGMASK);\n\treturn -ERESTARTSIGSUSPEND;\n}\n\nlong sys_sigwait(sigset_t * set, int * sig) {\n\tPTRCHECK(set,sizeof(sigset_t),0);\n\tPTRCHECK(sig,sizeof(int),MMU_PTR_WRITE);\n\n\t/* Silently ignore attempts to wait on KILL or STOP */\n\tsigset_t awaited = *set & ~((1 << SIGKILL) | (1 << SIGSTOP));\n\n\t/* Don't let processes wait on unblocked signals */\n\tif (awaited & ~this_core->current_process->blocked_signals) return -EINVAL;\n\n\treturn signal_await(awaited, sig);\n}\n\nlong sys_fswait(int c, int fds[]) {\n\tPTR_VALIDATE(fds);\n\tif (!fds || c < 0) return -EFAULT;\n\tfor (int i = 0; i < c; ++i) {\n\t\tif (!FD_CHECK(fds[i])) return -EBADF;\n\t}\n\tfs_node_t ** nodes = malloc(sizeof(fs_node_t *)*(c+1));\n\tfor (int i = 0; i < c; ++i) {\n\t\tnodes[i] = FD_ENTRY(fds[i]);\n\t}\n\tnodes[c] = NULL;\n\n\tint result = process_wait_nodes((process_t *)this_core->current_process, nodes, -1);\n\tfree(nodes);\n\treturn result;\n}\n\nlong sys_fswait_timeout(int c, int fds[], int timeout) {\n\tPTR_VALIDATE(fds);\n\tif (!fds || c < 0) return -EFAULT;\n\tfor (int i = 0; i < c; ++i) {\n\t\tif (!FD_CHECK(fds[i])) return -EBADF;\n\t}\n\tfs_node_t ** nodes = malloc(sizeof(fs_node_t *)*(c+1));\n\tfor (int i = 0; i < c; ++i) {\n\t\tnodes[i] = FD_ENTRY(fds[i]);\n\t}\n\tnodes[c] = NULL;\n\n\tint result = process_wait_nodes((process_t *)this_core->current_process, nodes, timeout);\n\tfree(nodes);\n\treturn result;\n}\n\nlong sys_fswait_multi(int c, int fds[], int timeout, int out[]) {\n\tPTR_VALIDATE(fds);\n\tPTR_VALIDATE(out);\n\tif (!fds || !out || c < 0) return -EFAULT;\n\tint has_match = -1;\n\tfor (int i = 0; i < c; ++i) {\n\t\tif (!FD_CHECK(fds[i])) {\n\t\t\treturn -EBADF;\n\t\t}\n\t\tif (selectcheck_fs(FD_ENTRY(fds[i])) == 0) {\n\t\t\tout[i] = 1;\n\t\t\thas_match = (has_match == -1) ? i : has_match;\n\t\t} else {\n\t\t\tout[i] = 0;\n\t\t}\n\t}\n\n\t/* Already found a match, return immediately with the first match */\n\tif (has_match != -1) return has_match;\n\n\tint result = sys_fswait_timeout(c, fds, timeout);\n\tif (result >= 0 && result < c) out[result] = 1;\n\treturn result;\n}\n\nlong sys_shm_obtain(char * path, size_t * size) {\n\tPTR_VALIDATE(path);\n\tPTR_VALIDATE(size);\n\tif (!path || !size) return -EFAULT;\n\treturn (long)shm_obtain(path, size);\n}\n\nlong sys_shm_release(char * path) {\n\tPTR_VALIDATE(path);\n\tif (!path) return -EFAULT;\n\treturn shm_release(path);\n}\n\nlong sys_openpty(int * master, int * slave, char * name, void * _ign0, void * size) {\n\t/* We require a place to put these when we are done. */\n\tif (!master || !slave) return -EINVAL;\n\tif (master && !PTR_INRANGE(master)) return -EINVAL;\n\tif (slave && !PTR_INRANGE(slave)) return -EINVAL;\n\tif (size && !PTR_INRANGE(size)) return -EINVAL;\n\n\t/* Create a new pseudo terminal */\n\tfs_node_t * fs_master;\n\tfs_node_t * fs_slave;\n\n\tpty_create(size, &fs_master, &fs_slave);\n\n\t/* Append the master and slave to the calling process */\n\t*master = process_append_fd((process_t *)this_core->current_process, fs_master);\n\t*slave  = process_append_fd((process_t *)this_core->current_process, fs_slave);\n\n\tFD_MODE(*master) = 03;\n\tFD_MODE(*slave) = 03;\n\n\topen_fs(fs_master, 0);\n\topen_fs(fs_slave, 0);\n\n\t/* Return success */\n\treturn 0;\n}\n\nlong sys_kill(pid_t process, int signal) {\n\tif (process < -1) {\n\t\treturn group_send_signal(-process, signal, 0);\n\t} else if (process == 0) {\n\t\treturn group_send_signal(this_core->current_process->job, signal, 0);\n\t} else {\n\t\treturn send_signal(process, signal, 0);\n\t}\n}\n\nlong sys_reboot(void) {\n\tif (this_core->current_process->user != USER_ROOT_UID) {\n\t\treturn -EPERM;\n\t}\n\n\treturn arch_reboot();\n}\n\nlong sys_times(struct tms *buf) {\n\tif (buf) {\n\t\tPTR_VALIDATE(buf);\n\n\t\tbuf->tms_utime  = (this_core->current_process->time_total - this_core->current_process->time_sys) / arch_cpu_mhz();\n\t\tbuf->tms_stime  = this_core->current_process->time_sys          / arch_cpu_mhz();\n\t\tbuf->tms_cutime = (this_core->current_process->time_children - this_core->current_process->time_sys_children) / arch_cpu_mhz();\n\t\tbuf->tms_cstime = this_core->current_process->time_sys_children / arch_cpu_mhz();\n\t}\n\n\treturn arch_perf_timer() / arch_cpu_mhz();\n}\n\nextern long ptrace_handle(long,pid_t,void*,void*);\n\ntypedef long (*scall_func)(long,long,long,long,long);\n\nstatic scall_func syscalls[] = {\n\t/* System Call Table */\n\t[SYS_EXT]          = (scall_func)(uintptr_t)sys_exit,\n\t[SYS_GETEUID]      = (scall_func)(uintptr_t)sys_geteuid,\n\t[SYS_OPEN]         = (scall_func)(uintptr_t)sys_open,\n\t[SYS_READ]         = (scall_func)(uintptr_t)sys_read,\n\t[SYS_WRITE]        = (scall_func)(uintptr_t)sys_write,\n\t[SYS_CLOSE]        = (scall_func)(uintptr_t)sys_close,\n\t[SYS_GETTIMEOFDAY] = (scall_func)(uintptr_t)sys_gettimeofday,\n\t[SYS_GETPID]       = (scall_func)(uintptr_t)sys_getpid,\n\t[SYS_SBRK]         = (scall_func)(uintptr_t)sys_sbrk,\n\t[SYS_UNAME]        = (scall_func)(uintptr_t)sys_uname,\n\t[SYS_SEEK]         = (scall_func)(uintptr_t)sys_seek,\n\t[SYS_STAT]         = (scall_func)(uintptr_t)sys_stat,\n\t[SYS_GETUID]       = (scall_func)(uintptr_t)sys_getuid,\n\t[SYS_SETUID]       = (scall_func)(uintptr_t)sys_setuid,\n\t[SYS_READDIR]      = (scall_func)(uintptr_t)sys_readdir,\n\t[SYS_CHDIR]        = (scall_func)(uintptr_t)sys_chdir,\n\t[SYS_GETCWD]       = (scall_func)(uintptr_t)sys_getcwd,\n\t[SYS_SETHOSTNAME]  = (scall_func)(uintptr_t)sys_sethostname,\n\t[SYS_GETHOSTNAME]  = (scall_func)(uintptr_t)sys_gethostname,\n\t[SYS_MKDIR]        = (scall_func)(uintptr_t)sys_mkdir,\n\t[SYS_GETTID]       = (scall_func)(uintptr_t)sys_gettid,\n\t[SYS_SYSFUNC]      = (scall_func)(uintptr_t)sys_sysfunc,\n\t[SYS_IOCTL]        = (scall_func)(uintptr_t)sys_ioctl,\n\t[SYS_ACCESS]       = (scall_func)(uintptr_t)sys_access,\n\t[SYS_STATF]        = (scall_func)(uintptr_t)sys_statf,\n\t[SYS_CHMOD]        = (scall_func)(uintptr_t)sys_chmod,\n\t[SYS_UMASK]        = (scall_func)(uintptr_t)sys_umask,\n\t[SYS_UNLINK]       = (scall_func)(uintptr_t)sys_unlink,\n\t[SYS_MOUNT]        = (scall_func)(uintptr_t)sys_mount,\n\t[SYS_SYMLINK]      = (scall_func)(uintptr_t)sys_symlink,\n\t[SYS_READLINK]     = (scall_func)(uintptr_t)sys_readlink,\n\t[SYS_LSTAT]        = (scall_func)(uintptr_t)sys_lstat,\n\t[SYS_CHOWN]        = (scall_func)(uintptr_t)sys_chown,\n\t[SYS_SETSID]       = (scall_func)(uintptr_t)sys_setsid,\n\t[SYS_SETPGID]      = (scall_func)(uintptr_t)sys_setpgid,\n\t[SYS_GETPGID]      = (scall_func)(uintptr_t)sys_getpgid,\n\t[SYS_DUP2]         = (scall_func)(uintptr_t)sys_dup2,\n\t[SYS_EXECVE]       = (scall_func)(uintptr_t)sys_execve,\n\t[SYS_FORK]         = (scall_func)(uintptr_t)sys_fork,\n\t[SYS_WAITPID]      = (scall_func)(uintptr_t)sys_waitpid,\n\t[SYS_YIELD]        = (scall_func)(uintptr_t)sys_yield,\n\t[SYS_SLEEPABS]     = (scall_func)(uintptr_t)sys_sleepabs,\n\t[SYS_SLEEP]        = (scall_func)(uintptr_t)sys_sleep,\n\t[SYS_PIPE]         = (scall_func)(uintptr_t)sys_pipe,\n\t[SYS_FSWAIT]       = (scall_func)(uintptr_t)sys_fswait,\n\t[SYS_FSWAIT2]      = (scall_func)(uintptr_t)sys_fswait_timeout,\n\t[SYS_FSWAIT3]      = (scall_func)(uintptr_t)sys_fswait_multi,\n\t[SYS_CLONE]        = (scall_func)(uintptr_t)sys_clone,\n\t[SYS_OPENPTY]      = (scall_func)(uintptr_t)sys_openpty,\n\t[SYS_SHM_OBTAIN]   = (scall_func)(uintptr_t)sys_shm_obtain,\n\t[SYS_SHM_RELEASE]  = (scall_func)(uintptr_t)sys_shm_release,\n\t[SYS_SIGNAL]       = (scall_func)(uintptr_t)sys_signal,\n\t[SYS_KILL]         = (scall_func)(uintptr_t)sys_kill,\n\t[SYS_REBOOT]       = (scall_func)(uintptr_t)sys_reboot,\n\t[SYS_GETGID]       = (scall_func)(uintptr_t)sys_getgid,\n\t[SYS_GETEGID]      = (scall_func)(uintptr_t)sys_getegid,\n\t[SYS_SETGID]       = (scall_func)(uintptr_t)sys_setgid,\n\t[SYS_GETGROUPS]    = (scall_func)(uintptr_t)sys_getgroups,\n\t[SYS_SETGROUPS]    = (scall_func)(uintptr_t)sys_setgroups,\n\t[SYS_TIMES]        = (scall_func)(uintptr_t)sys_times,\n\t[SYS_PTRACE]       = (scall_func)(uintptr_t)ptrace_handle,\n\t[SYS_SETTIMEOFDAY] = (scall_func)(uintptr_t)sys_settimeofday,\n\t[SYS_SIGACTION]    = (scall_func)(uintptr_t)sys_sigaction,\n\t[SYS_SIGPENDING]   = (scall_func)(uintptr_t)sys_sigpending,\n\t[SYS_SIGPROCMASK]  = (scall_func)(uintptr_t)sys_sigprocmask,\n\t[SYS_SIGSUSPEND]   = (scall_func)(uintptr_t)sys_sigsuspend,\n\t[SYS_SIGWAIT]      = (scall_func)(uintptr_t)sys_sigwait,\n\t[SYS_PREAD]        = (scall_func)(uintptr_t)sys_pread,\n\t[SYS_PWRITE]       = (scall_func)(uintptr_t)sys_pwrite,\n\t[SYS_RENAME]       = (scall_func)(uintptr_t)sys_rename,\n\t[SYS_FCNTL]        = (scall_func)(uintptr_t)sys_fcntl,\n\t[SYS_FCHMOD]       = (scall_func)(uintptr_t)sys_fchmod,\n\t[SYS_FCHOWN]       = (scall_func)(uintptr_t)sys_fchown,\n\t[SYS_TRUNCATE]     = (scall_func)(uintptr_t)sys_truncate,\n\t[SYS_FTRUNCATE]    = (scall_func)(uintptr_t)sys_ftruncate,\n\n\t[SYS_SOCKET]       = (scall_func)(uintptr_t)net_socket,\n\t[SYS_SETSOCKOPT]   = (scall_func)(uintptr_t)net_setsockopt,\n\t[SYS_BIND]         = (scall_func)(uintptr_t)net_bind,\n\t[SYS_ACCEPT]       = (scall_func)(uintptr_t)net_accept,\n\t[SYS_LISTEN]       = (scall_func)(uintptr_t)net_listen,\n\t[SYS_CONNECT]      = (scall_func)(uintptr_t)net_connect,\n\t[SYS_GETSOCKOPT]   = (scall_func)(uintptr_t)net_getsockopt,\n\t[SYS_RECV]         = (scall_func)(uintptr_t)net_recv,\n\t[SYS_SEND]         = (scall_func)(uintptr_t)net_send,\n\t[SYS_SHUTDOWN]     = (scall_func)(uintptr_t)net_shutdown,\n\t[SYS_GETSOCKNAME]  = (scall_func)(uintptr_t)net_getsockname,\n\t[SYS_GETPEERNAME]  = (scall_func)(uintptr_t)net_getpeername,\n};\n\nstatic long num_syscalls = sizeof(syscalls) / sizeof(*syscalls);\n\nvoid syscall_handler(struct regs * r) {\n\n\tthis_core->current_process->syscall_registers = r;\n\n\tif (this_core->current_process->flags & PROC_FLAG_TRACE_SYSCALLS) {\n\t\tptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_ENTER);\n\t}\n\n\tlong result;\n\n\tif (arch_syscall_number(r) < 0 || arch_syscall_number(r) >= num_syscalls) {\n\t\tresult = -EINVAL;\n\t\tgoto _finish_syscall;\n\t}\n\n\tscall_func func = syscalls[arch_syscall_number(r)];\n\tresult = func(\n\t\tarch_syscall_arg0(r), arch_syscall_arg1(r), arch_syscall_arg2(r),\n\t\tarch_syscall_arg3(r), arch_syscall_arg4(r));\n\n\tif (result == -ERESTARTSYS || result == -ERESTARTSIGSUSPEND) {\n\t\tthis_core->current_process->interrupted_system_call = arch_syscall_number(r);\n\t}\n\n_finish_syscall:\n\tarch_syscall_return(r, result);\n\n\tif (this_core->current_process->flags & PROC_FLAG_TRACE_SYSCALLS) {\n\t\tptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_EXIT);\n\t}\n}\n"
  },
  {
    "path": "kernel/sys/version.c",
    "content": "/**\n * @file kernel/sys/version.c\n * @brief Kernel version macros.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n */\n\n#include <kernel/version.h>\n\n#define STR(x) #x\n#define STRSTR(x) STR(x)\n\n/* Kernel name. If you change this, you're not\n * my friend any more. */\nconst char * __kernel_name = \"Misaka\";\n\n/* This really shouldn't change, and if it does,\n * always ensure it still has the correct arguments\n * when used as a vsprintf() format. */\nconst char * __kernel_version_format = \"%d.%d.%d-%s\";\n\n/* Version numbers X.Y.Z */\nint    __kernel_version_major = 2;\nint    __kernel_version_minor = 3;\nint    __kernel_version_lower = 0;\n\n/* Kernel build suffix, which doesn't necessarily\n * mean anything, but can be used to distinguish\n * between different features included while\n * building multiple kernels. */\n#ifdef KERNEL_GIT_TAG\n# define KERNEL_VERSION_SUFFIX STRSTR(KERNEL_GIT_TAG)\n#else\n# define KERNEL_VERSION_SUFFIX \"r\"\n#endif\nconst char * __kernel_version_suffix   = KERNEL_VERSION_SUFFIX;\n\n/* The release codename. */\nconst char * __kernel_version_codename = \"\\\"Grow Slowly\\\"\";\n\n/* Build architecture */\nconst char * __kernel_arch = STRSTR(KERNEL_ARCH);\n\n/* Rebuild from clean to reset these. */\nconst char * __kernel_build_date = __DATE__;\nconst char * __kernel_build_time = __TIME__;\n\n#if (defined(__GNUC__) || defined(__GNUG__)) && !(defined(__clang__) || defined(__INTEL_COMPILER))\n# define COMPILER_VERSION \"gcc \" __VERSION__\n#elif (defined(__clang__))\n# define COMPILER_VERSION \"clang \" __clang_version__\n#else\n# define COMPILER_VERSION \"unknown-compiler how-did-you-do-that\"\n#endif\n\nconst char * __kernel_compiler_version = COMPILER_VERSION;\n"
  },
  {
    "path": "kernel/vfs/console.c",
    "content": "/**\n * @file  kernel/vfs/console.c\n * @brief Device file interface to the kernel console.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdarg.h>\n#include <errno.h>\n#include <kernel/process.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/time.h>\n\nstatic fs_node_t * console_dev = NULL;\n\n/** Things we use to determine if the clock is ready. */\nextern uint64_t arch_boot_time;\n\n/** Things we use for framebuffer output. */\nextern uint16_t lfb_resolution_x;\nextern size_t (*printf_output)(size_t, uint8_t *);\n\nstatic size_t (*console_write)(size_t, uint8_t *) = NULL;\n\nstatic uint8_t tmp_buffer[4096] __attribute__((aligned(4096)));\nstatic uint8_t * buffer_start = tmp_buffer;\n\nstatic ssize_t write_console(size_t size, uint8_t *buffer) {\n\tif (console_write) return console_write(size,buffer);\n\n\tif (buffer_start + size >= tmp_buffer + sizeof(tmp_buffer)) {\n\t\treturn 0; /* uh oh */\n\t}\n\n\tmemcpy(buffer_start, buffer, size);\n\tbuffer_start += size;\n\n\treturn size;\n}\n\nstruct dprintf_data {\n\tint prev_was_lf;\n\tint left_width;\n};\n\nstatic int cb_printf(void * user, char c) {\n\tstruct dprintf_data * data = user;\n\tif (data->prev_was_lf) {\n\t\tfor (int i = 0; i < data->left_width; ++i) write_console(1, (uint8_t*)\" \");\n\t\tdata->prev_was_lf = 0;\n\t}\n\tif (c == '\\n') data->prev_was_lf = 1;\n\twrite_console(1, (uint8_t*)&c);\n\treturn 0;\n}\n\nvoid console_set_output(size_t (*output)(size_t,uint8_t*)) {\n\tconsole_write = output;\n\n\tif (buffer_start != tmp_buffer) {\n\t\tconsole_write(buffer_start - tmp_buffer, tmp_buffer);\n\t\tbuffer_start = tmp_buffer;\n\t}\n}\n\nint dprintf(const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\n\t/* Is this a FATAL message? */\n\n\t/* If it's ready now but wasn't ready previously, are there\n\t * things in the queue to dump? */\n\n\t/* Is this a fresh message for this core that we need to assign a timestamp to? */\n\n\n\tstruct dprintf_data _data = {0,0};\n\n\tif (*fmt == '\\a') {\n\t\tfmt++;\n\t} else {\n\t\tchar timestamp[32];\n\t\tunsigned long timer_ticks, timer_subticks;\n\t\trelative_time(0,0,&timer_ticks,&timer_subticks);\n\t\tsize_t ts_len = snprintf(timestamp, 31, \"[%5lu.%06lu] \", timer_ticks, timer_subticks);\n\t\t_data.left_width = ts_len;\n\t\twrite_console(ts_len, (uint8_t*)timestamp);\n\t}\n\n\tint out = xvasprintf(cb_printf, &_data, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n\nstatic ssize_t write_fs_console(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tif (size > 0x1000) return -EINVAL;\n\tsize_t size_in = size;\n\tif (size && *buffer == '\\r') {\n\t\twrite_console(1,(uint8_t*)\"\\r\");\n\t\tbuffer++;\n\t\tsize--;\n\t}\n\tif (size) dprintf(\"%*s\", (unsigned int)size, buffer);\n\treturn size_in;\n}\n\nstatic fs_node_t * console_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"console\");\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0660;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->write   = write_fs_console;\n\treturn fnode;\n}\n\nvoid console_initialize(void) {\n\tconsole_dev = console_device_create();\n\tvfs_mount(\"/dev/console\", console_dev, \"console\", \"\");\n}\n"
  },
  {
    "path": "kernel/vfs/packetfs.c",
    "content": "/**\n * @file kernel/vfs/packetfs.c\n * @brief Packet-based multiple-client IPC mechanism. aka PEX\n *\n * Provides a server-client packet-based IPC socket system for\n * userspace applications. Primarily used by the compositor to\n * communicate with clients.\n *\n * Care must be taken to ensure that this is backed by an atomic\n * stream; the legacy pseudo-pipe interface is used at the moment.\n *\n * @bug We leak kernel heap addresses directly to userspace as the\n *      client identifiers in PEX messages. We should probably do\n *      something else. I'm also reasonably certain a server can\n *      just throw a random address at the PEX API?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <errno.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/assert.h>\n#include <kernel/types.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/vfs.h>\n#include <kernel/pipe.h>\n#include <kernel/spinlock.h>\n#include <kernel/process.h>\n\nextern void pipe_destroy(fs_node_t * node);\n\n#include <sys/ioctl.h>\n\n#define MAX_PACKET_SIZE 1024\n#define debug_print(x, ...) do { if (0) {printf(\"packetfs.c [%s] \", #x); printf(__VA_ARGS__); printf(\"\\n\"); } } while (0)\n\ntypedef struct packet_manager {\n\t/* uh, nothing, lol */\n\tlist_t * exchanges;\n\tspin_lock_t lock;\n} pex_t;\n\ntypedef struct packet_exchange {\n\tchar * name;\n\tchar fresh;\n\tspin_lock_t lock;\n\tfs_node_t * server_pipe;\n\tlist_t * clients;\n\tpex_t * parent;\n} pex_ex_t;\n\ntypedef struct packet_client {\n\tpex_ex_t * parent;\n\tfs_node_t * pipe;\n} pex_client_t;\n\n\ntypedef struct packet {\n\tpex_client_t * source;\n\tsize_t      size;\n\tuint8_t     data[];\n} packet_t;\n\ntypedef struct server_write_header {\n\tpex_client_t * target;\n\tuint8_t data[];\n} header_t;\n\nstatic ssize_t receive_packet(pex_ex_t * exchange, fs_node_t * socket, packet_t ** out) {\n\tssize_t r;\n\tdo {\n\t\tr = read_fs(socket, 0, sizeof(struct packet *), (uint8_t*)out);\n\t} while (r == 0);\n\tif (r < 0) return r;\n\tassert(r == sizeof(struct packet*));\n\tassert((uintptr_t)*out >= 0xFFFFff0000000000UL && (uintptr_t)*out < 0xffffff1fc0000000UL);\n\treturn r;\n}\n\nstatic void send_to_server(pex_ex_t * p, pex_client_t * c, size_t size, void * data) {\n\tsize_t p_size = size + sizeof(struct packet);\n\tpacket_t * packet = malloc(p_size);\n\tif ((uintptr_t)c < 0x800000000) {\n\t\tprintf(\"suspicious pex client received: %p\\n\", (char*)c);\n\t}\n\n\tpacket->source = c;\n\tpacket->size = size;\n\n\tif (size) {\n\t\tmemcpy(packet->data, data, size);\n\t}\n\n\twrite_fs(p->server_pipe, 0, sizeof(struct packet*), (uint8_t*)&packet);\n}\n\nstatic int send_to_client(pex_ex_t * p, pex_client_t * c, size_t size, void * data) {\n\tsize_t p_size = size + sizeof(struct packet);\n\n\t/* Verify there is space on the client */\n\tif (pipe_unsize(c->pipe) < (int)sizeof(struct packet*)) {\n\t\treturn -1;\n\t}\n\n\tif ((uintptr_t)c < 0x800000000) {\n\t\tprintf(\"suspicious pex client received: %p\\n\", (char*)c);\n\t}\n\n\tpacket_t * packet = malloc(p_size);\n\n\tmemcpy(packet->data, data, size);\n\tpacket->source = NULL;\n\tpacket->size = size;\n\n\twrite_fs(c->pipe, 0, sizeof(struct packet*), (uint8_t*)&packet);\n\n\treturn size;\n}\n\nstatic pex_client_t * create_client(pex_ex_t * p) {\n\tpex_client_t * out = malloc(sizeof(pex_client_t));\n\tout->parent = p;\n\tout->pipe = make_pipe(4096);\n\treturn out;\n}\n\nstatic ssize_t read_server(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tpex_ex_t * p = (pex_ex_t *)node->device;\n\tdebug_print(INFO, \"[pex] server read(...)\");\n\n\tpacket_t * packet = NULL;\n\n\tssize_t response_size = receive_packet(p, p->server_pipe, &packet);\n\n\tif (response_size < 0) return response_size;\n\tif (!packet) return -1;\n\n\tdebug_print(INFO, \"Server recevied packet of size %zu, was waiting for at most %lu\", packet->size, size);\n\n\tif (packet->size + sizeof(packet_t) > size) {\n\t\tprintf(\"pex: read in server would be incomplete\\n\");\n\t\treturn -1;\n\t}\n\n\tmemcpy(buffer, packet, packet->size + sizeof(packet_t));\n\tssize_t out = packet->size + sizeof(packet_t);\n\n\tfree(packet);\n\treturn out;\n}\n\nstatic ssize_t write_server(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tpex_ex_t * p = (pex_ex_t *)node->device;\n\tdebug_print(INFO, \"[pex] server write(...)\");\n\n\theader_t * head = (header_t *)buffer;\n\n\tif (size - sizeof(header_t) > MAX_PACKET_SIZE) {\n\t\tprintf(\"pex: server write is too big\\n\");\n\t\treturn -1;\n\t}\n\n\tif (head->target == NULL) {\n\t\t/* Brodcast packet */\n\t\tspin_lock(p->lock);\n\t\tforeach(f, p->clients) {\n\t\t\tdebug_print(INFO, \"Sending to client %p\", f->value);\n\t\t\tsend_to_client(p, (pex_client_t *)f->value, size - sizeof(header_t), head->data);\n\t\t}\n\t\tspin_unlock(p->lock);\n\t\tdebug_print(INFO, \"Done broadcasting to clients.\");\n\t\treturn size;\n\t} else if (head->target->parent != p) {\n\t\tdebug_print(WARNING, \"[pex] Invalid packet from server? (pid=%d)\", this_core->current_process->id);\n\t\treturn -1;\n\t}\n\n\treturn send_to_client(p, head->target, size - sizeof(header_t), head->data) + sizeof(header_t);\n}\n\nstatic int ioctl_server(fs_node_t * node, unsigned long request, void * argp) {\n\tpex_ex_t * p = (pex_ex_t *)node->device;\n\n\tswitch (request) {\n\t\tcase IOCTL_PACKETFS_QUEUED:\n\t\t\treturn pipe_size(p->server_pipe);\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n}\n\nstatic ssize_t read_client(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\tif (c->parent != node->device) {\n\t\tprintf(\"pex: Invalid device endpoint on client read?\\n\");\n\t\treturn -EINVAL;\n\t}\n\n\tdebug_print(INFO, \"[pex] client read(...)\");\n\n\tpacket_t * packet = NULL;\n\n\tssize_t response_size = receive_packet(c->parent, c->pipe, &packet);\n\n\tif (response_size < 0) return response_size;\n\tif (!packet) return -EIO;\n\n\tif (packet->size > size) {\n\t\tprintf(\"pex: Client is not reading enough bytes to hold packet of size %zu\\n\", packet->size);\n\t\treturn -EINVAL;\n\t}\n\n\tmemcpy(buffer, &packet->data, packet->size);\n\tssize_t out = packet->size;\n\n\tdebug_print(INFO, \"[pex] Client received packet of size %zu\", packet->size);\n\tif (out == 0) {\n\t\tprintf(\"pex: packet is empty?\\n\");\n\t}\n\n\tfree(packet);\n\treturn out;\n}\n\nstatic ssize_t write_client(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\tif (c->parent != node->device) {\n\t\tdebug_print(WARNING, \"[pex] Invalid device endpoint on client write?\");\n\t\treturn -EINVAL;\n\t}\n\n\tdebug_print(INFO, \"[pex] client write(...)\");\n\n\tif (size > MAX_PACKET_SIZE) {\n\t\tdebug_print(WARNING, \"Size of %lu is too big.\", size);\n\t\treturn -EINVAL;\n\t}\n\n\tdebug_print(INFO, \"Sending packet of size %lu to parent\", size);\n\tsend_to_server(c->parent, c, size, buffer);\n\n\treturn size;\n}\n\nstatic int ioctl_client(fs_node_t * node, unsigned long request, void * argp) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\n\tswitch (request) {\n\t\tcase IOCTL_PACKETFS_QUEUED:\n\t\t\treturn pipe_size(c->pipe);\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n}\n\nstatic void close_client(fs_node_t * node) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\tpex_ex_t * p = c->parent;\n\n\tif (p) {\n\t\tdebug_print(WARNING, \"Closing packetfs client: %p:%p\", (void*)p, (void*)c);\n\t\tspin_lock(p->lock);\n\t\tnode_t * n = list_find(p->clients, c);\n\t\tif (n && n->owner == p->clients) {\n\t\t\tlist_delete(p->clients, n);\n\t\t\tfree(n);\n\t\t}\n\t\tspin_unlock(p->lock);\n\t\tchar tmp[1];\n\t\tsend_to_server(p, c, 0, tmp);\n\t}\n\n\tpipe_destroy(c->pipe);\n\tfree(c->pipe);\n\tfree(c);\n}\n\nstatic int wait_server(fs_node_t * node, void * process) {\n\tpex_ex_t * p = (pex_ex_t *)node->device;\n\treturn selectwait_fs(p->server_pipe, process);\n}\nstatic int check_server(fs_node_t * node) {\n\tpex_ex_t * p = (pex_ex_t *)node->device;\n\treturn selectcheck_fs(p->server_pipe);\n}\n\nstatic int wait_client(fs_node_t * node, void * process) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\treturn selectwait_fs(c->pipe, process);\n}\nstatic int check_client(fs_node_t * node) {\n\tpex_client_t * c = (pex_client_t *)node->inode;\n\treturn selectcheck_fs(c->pipe);\n}\n\n\nstatic void close_server(fs_node_t * node) {\n\tpex_ex_t * ex = (pex_ex_t *)node->device;\n\tpex_t * p = ex->parent;\n\n\tspin_lock(p->lock);\n\n\tnode_t * lnode = list_find(p->exchanges, ex);\n\n\t/* Remove from exchange list */\n\tif (lnode) {\n\t\tlist_delete(p->exchanges, lnode);\n\t\tfree(lnode);\n\t}\n\n\t/* Tell all clients we have disconnected */\n\tspin_lock(ex->lock);\n\twhile (ex->clients->length) {\n\t\tnode_t * f = list_pop(ex->clients);\n\t\tpex_client_t * client = (pex_client_t*)f->value;\n\t\tsend_to_client(ex, client, 0, NULL);\n\t\tclient->parent = NULL;\n\t\tfree(f);\n\t}\n\tspin_unlock(ex->lock);\n\n\tfree(ex->clients);\n\tpipe_destroy(ex->server_pipe);\n\tfree(ex->server_pipe);\n\tnode->device = NULL;\n\tfree(ex);\n\n\tspin_unlock(p->lock);\n\n}\n\nstatic void open_pex(fs_node_t * node, unsigned int flags) {\n\tpex_ex_t * t = (pex_ex_t *)(node->device);\n\n\tdebug_print(NOTICE, \"Opening packet exchange %s with flags 0x%x\", t->name, flags);\n\n\tif ((flags & O_CREAT) && (flags & O_EXCL)) {\n\t\tif (!t->fresh) {\n\t\t\treturn; /* Address in use; this should be handled by kopen... */\n\t\t}\n\t\tt->fresh = 0;\n\t\tnode->inode = 0;\n\t\t/* Set up the server side */\n\t\tnode->read   = read_server;\n\t\tnode->write  = write_server;\n\t\tnode->ioctl  = ioctl_server;\n\t\tnode->close  = close_server;\n\t\tnode->selectcheck = check_server;\n\t\tnode->selectwait  = wait_server;\n\t\tdebug_print(INFO, \"[pex] Server launched: %s\", t->name);\n\t\tdebug_print(INFO, \"fs_node = %p\", (void*)node);\n\t} else {\n\t\tpex_client_t * client = create_client(t);\n\t\tnode->inode = (uintptr_t)client;\n\n\t\tnode->read  = read_client;\n\t\tnode->write = write_client;\n\t\tnode->ioctl = ioctl_client;\n\t\tnode->close = close_client;\n\n\t\tnode->selectcheck = check_client;\n\t\tnode->selectwait  = wait_client;\n\n\t\tlist_insert(t->clients, client);\n\n\t\t/* XXX: Send plumbing message to server for new client connection */\n\t\tdebug_print(INFO, \"[pex] Client connected: %s:%lx\", t->name, node->inode);\n\t}\n\n\treturn;\n}\n\nstatic struct dirent * readdir_packetfs(fs_node_t *node, uint64_t index) {\n\tpex_t * p = (pex_t *)node->device;\n\tunsigned int i = 0;\n\n\tdebug_print(INFO, \"[pex] readdir(%lu)\", index);\n\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tif (index >= p->exchanges->length) {\n\t\treturn NULL;\n\t}\n\n\tspin_lock(p->lock);\n\n\tforeach(f, p->exchanges) {\n\t\tif (i == index) {\n\t\t\tspin_unlock(p->lock);\n\t\t\tpex_ex_t * t = (pex_ex_t *)f->value;\n\t\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\t\tout->d_ino = (uint64_t)t;\n\t\t\tstrcpy(out->d_name, t->name);\n\t\t\treturn out;\n\t\t} else {\n\t\t\t++i;\n\t\t}\n\t}\n\n\tspin_unlock(p->lock);\n\n\treturn NULL;\n}\n\nstatic fs_node_t * file_from_pex(pex_ex_t * pex) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, pex->name);\n\tfnode->device  = pex;\n\tfnode->mask    = 0666;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->open    = open_pex;\n\treturn fnode;\n}\n\nstatic fs_node_t * finddir_packetfs(fs_node_t * node, char * name) {\n\tif (!name) return NULL;\n\tpex_t * p = (pex_t *)node->device;\n\n\tdebug_print(INFO, \"[pex] finddir(%s)\", name);\n\n\tspin_lock(p->lock);\n\n\tforeach(f, p->exchanges) {\n\t\tpex_ex_t * t = (pex_ex_t *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tspin_unlock(p->lock);\n\t\t\treturn file_from_pex(t);\n\t\t}\n\t}\n\n\tspin_unlock(p->lock);\n\n\treturn NULL;\n}\n\nstatic int create_packetfs(fs_node_t *parent, char *name, mode_t permission) {\n\tif (!name) return -EINVAL;\n\n\tpex_t * p = (pex_t *)parent->device;\n\n\tdebug_print(NOTICE, \"[pex] create(%s)\", name);\n\n\tspin_lock(p->lock);\n\n\tforeach(f, p->exchanges) {\n\t\tpex_ex_t * t = (pex_ex_t *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tspin_unlock(p->lock);\n\t\t\t/* Already exists */\n\t\t\treturn -EEXIST;\n\t\t}\n\t}\n\n\t/* Make it */\n\tpex_ex_t * new_exchange = malloc(sizeof(pex_ex_t));\n\n\tnew_exchange->name = strdup(name);\n\tnew_exchange->fresh = 1;\n\tnew_exchange->clients = list_create(\"pex clients\",new_exchange);\n\tnew_exchange->server_pipe = make_pipe(4096);\n\tnew_exchange->parent = p;\n\n\tspin_init(new_exchange->lock);\n\t/* XXX Create exchange server pipe */\n\n\tlist_insert(p->exchanges, new_exchange);\n\n\tspin_unlock(p->lock);\n\n\treturn 0;\n}\n\nstatic void destroy_pex(pex_ex_t * p) {\n\t/* XXX */\n}\n\nstatic int unlink_packetfs(fs_node_t *parent, char *name) {\n\tif (!name) return -EINVAL;\n\n\tpex_t * p = (pex_t *)parent->device;\n\n\tdebug_print(NOTICE, \"[pex] unlink(%s)\", name);\n\n\tint i = -1, j = 0;\n\n\tspin_lock(p->lock);\n\n\tforeach(f, p->exchanges) {\n\t\tpex_ex_t * t = (pex_ex_t *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tdestroy_pex(t);\n\t\t\ti = j;\n\t\t\tbreak;\n\t\t}\n\t\tj++;\n\t}\n\n\tif (i >= 0) {\n\t\tlist_remove(p->exchanges, i);\n\t} else {\n\t\tspin_unlock(p->lock);\n\t\treturn -ENOENT;\n\t}\n\n\tspin_unlock(p->lock);\n\n\treturn 0;\n}\n\nstatic fs_node_t * packetfs_manager(void) {\n\tpex_t * pex = malloc(sizeof(pex_t));\n\tpex->exchanges = list_create(\"pex exchanges\",pex);\n\n\tspin_init(pex->lock);\n\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"pex\");\n\tfnode->device  = pex;\n\tfnode->mask    = 0777;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->readdir = readdir_packetfs;\n\tfnode->finddir = finddir_packetfs;\n\tfnode->create  = create_packetfs;\n\tfnode->unlink  = unlink_packetfs;\n\n\treturn fnode;\n}\n\nvoid packetfs_initialize(void) {\n\tfs_node_t * packet_mgr = packetfs_manager();\n\tvfs_mount(\"/dev/pex\", packet_mgr, \"pex\", \"\");\n}\n"
  },
  {
    "path": "kernel/vfs/pipe.c",
    "content": "/**\n * @file kernel/vfs/pipe.c\n * @brief Legacy buffered pipe, used for char devices.\n *\n * This is the legacy pipe implementation. If you are looking for\n * the userspace pipes, @ref read_unixpipe.\n *\n * This implements a simple one-direction buffer suitable for use\n * by, eg., device drivers that want to offer a character-driven\n * interface to userspace without having to worry too much about\n * timing or getting blocked.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2021 K. Lange\n */\n\n#include <errno.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/printf.h>\n#include <kernel/vfs.h>\n#include <kernel/pipe.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/spinlock.h>\n#include <kernel/signal.h>\n#include <kernel/time.h>\n\n#include <sys/signal_defs.h>\n\n#define DEBUG_PIPES 0\n\nstatic inline size_t pipe_unread(pipe_device_t * pipe) {\n\tif (pipe->read_ptr == pipe->write_ptr) {\n\t\treturn 0;\n\t}\n\tif (pipe->read_ptr > pipe->write_ptr) {\n\t\treturn (pipe->size - pipe->read_ptr) + pipe->write_ptr;\n\t} else {\n\t\treturn (pipe->write_ptr - pipe->read_ptr);\n\t}\n}\n\nint pipe_size(fs_node_t * node) {\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\tspin_lock(pipe->ptr_lock);\n\tint out = pipe_unread(pipe);\n\tspin_unlock(pipe->ptr_lock);\n\treturn out;\n}\n\nstatic inline size_t pipe_available(pipe_device_t * pipe) {\n\tif (pipe->read_ptr == pipe->write_ptr) {\n\t\treturn pipe->size - 1;\n\t}\n\n\tif (pipe->read_ptr > pipe->write_ptr) {\n\t\treturn pipe->read_ptr - pipe->write_ptr - 1;\n\t} else {\n\t\treturn (pipe->size - pipe->write_ptr) + pipe->read_ptr - 1;\n\t}\n}\n\nint pipe_unsize(fs_node_t * node) {\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\tspin_lock(pipe->ptr_lock);\n\tint out = pipe_available(pipe);\n\tspin_unlock(pipe->ptr_lock);\n\treturn out;\n}\n\nstatic inline void pipe_increment_read(pipe_device_t * pipe) {\n\tspin_lock(pipe->ptr_lock);\n\tpipe->read_ptr++;\n\tif (pipe->read_ptr == pipe->size) {\n\t\tpipe->read_ptr = 0;\n\t}\n\tspin_unlock(pipe->ptr_lock);\n}\n\nstatic inline void pipe_increment_write(pipe_device_t * pipe) {\n\tspin_lock(pipe->ptr_lock);\n\tpipe->write_ptr++;\n\tif (pipe->write_ptr == pipe->size) {\n\t\tpipe->write_ptr = 0;\n\t}\n\tspin_unlock(pipe->ptr_lock);\n}\n\nstatic inline void pipe_increment_write_by(pipe_device_t * pipe, size_t amount) {\n\tpipe->write_ptr = (pipe->write_ptr + amount) % pipe->size;\n}\n\nstatic void pipe_alert_waiters(pipe_device_t * pipe) {\n\tspin_lock(pipe->alert_lock);\n\twhile (pipe->alert_waiters->head) {\n\t\tnode_t * node = list_dequeue(pipe->alert_waiters);\n\t\tprocess_t * p = node->value;\n\t\tfree(node);\n\t\tspin_unlock(pipe->alert_lock);\n\n\t\tprocess_alert_node(p, pipe);\n\n\t\tspin_lock(pipe->alert_lock);\n\t}\n\tspin_unlock(pipe->alert_lock);\n}\n\nssize_t read_pipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\t/* Retreive the pipe object associated with this file node */\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\tif (pipe->dead) {\n\t\tsend_signal(this_core->current_process->id, SIGPIPE, 1);\n\t\treturn 0;\n\t}\n\n\tsize_t collected = 0;\n\twhile (collected == 0) {\n\t\tspin_lock(pipe->lock_read);\n\t\tif (pipe_unread(pipe) >= size) {\n\t\t\twhile (pipe_unread(pipe) > 0 && collected < size) {\n\t\t\t\tbuffer[collected] = pipe->buffer[pipe->read_ptr];\n\t\t\t\tpipe_increment_read(pipe);\n\t\t\t\tcollected++;\n\t\t\t}\n\t\t}\n\t\twakeup_queue(pipe->wait_queue_writers);\n\t\t/* Deschedule and switch */\n\t\tif (collected == 0) {\n\t\t\tif (sleep_on_unlocking(pipe->wait_queue_readers, &pipe->lock_read)) {\n\t\t\t\tif (!collected) return -ERESTARTSYS;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tspin_unlock(pipe->lock_read);\n\t\t}\n\t}\n\n\treturn collected;\n}\n\nssize_t write_pipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\t/* Retreive the pipe object associated with this file node */\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\tif (pipe->dead) {\n\t\tsend_signal(this_core->current_process->id, SIGPIPE, 1);\n\t\treturn 0;\n\t}\n\n\tsize_t written = 0;\n\twhile (written < size) {\n\t\tspin_lock(pipe->lock_read);\n\t\t/* These pipes enforce atomic writes, poorly. */\n\t\tif (pipe_available(pipe) > size) {\n\t\t\twhile (pipe_available(pipe) > 0 && written < size) {\n\t\t\t\tpipe->buffer[pipe->write_ptr] = buffer[written];\n\t\t\t\tpipe_increment_write(pipe);\n\t\t\t\twritten++;\n\t\t\t}\n\t\t}\n\t\twakeup_queue(pipe->wait_queue_readers);\n\t\tpipe_alert_waiters(pipe);\n\t\tif (written < size) {\n\t\t\tif (sleep_on_unlocking(pipe->wait_queue_writers, &pipe->lock_read)) {\n\t\t\t\tif (!written) return -ERESTARTSYS;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tspin_unlock(pipe->lock_read);\n\t\t}\n\t}\n\n\treturn written;\n}\n\nvoid open_pipe(fs_node_t * node, unsigned int flags) {\n\t/* Retreive the pipe object associated with this file node */\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\t/* Add a reference */\n\tpipe->refcount++;\n\n\treturn;\n}\n\nvoid close_pipe(fs_node_t * node) {\n\t/* Retreive the pipe object associated with this file node */\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\t/* Drop one reference */\n\tpipe->refcount--;\n\n\t/* Check the reference count number */\n\tif (pipe->refcount == 0) {\n#if 0\n\t\t/* No other references exist, free the pipe (but not its buffer) */\n\t\tfree(pipe->buffer);\n\t\tlist_free(pipe->wait_queue);\n\t\tfree(pipe->wait_queue);\n\t\tfree(pipe);\n\t\t/* And let the creator know there are no more references */\n\t\tnode->device = 0;\n#endif\n\t}\n\n\treturn;\n}\n\nstatic int pipe_check(fs_node_t * node) {\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\tif (pipe_unread(pipe) > 0) {\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nstatic int pipe_wait(fs_node_t * node, void * process) {\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\n\tspin_lock(pipe->alert_lock);\n\tif (!list_find(pipe->alert_waiters, process)) {\n\t\tlist_insert(pipe->alert_waiters, process);\n\t}\n\tspin_unlock(pipe->alert_lock);\n\n\tspin_lock(pipe->wait_lock);\n\tlist_insert(((process_t *)process)->node_waits, pipe);\n\tspin_unlock(pipe->wait_lock);\n\n\treturn 0;\n}\n\nvoid pipe_destroy(fs_node_t * node) {\n\tpipe_device_t * pipe = (pipe_device_t *)node->device;\n\tspin_lock(pipe->ptr_lock);\n\tpipe->dead = 1;\n\tpipe_alert_waiters(pipe);\n\twakeup_queue(pipe->wait_queue_writers);\n\twakeup_queue(pipe->wait_queue_readers);\n\tfree(pipe->alert_waiters);\n\tfree(pipe->wait_queue_writers);\n\tfree(pipe->wait_queue_readers);\n\tfree(pipe->buffer);\n\tspin_unlock(pipe->ptr_lock);\n\tfree(pipe);\n}\n\nfs_node_t * make_pipe(size_t size) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tpipe_device_t * pipe = malloc(sizeof(pipe_device_t));\n\tmemset(fnode, 0, sizeof(fs_node_t));\n\tmemset(pipe, 0, sizeof(pipe_device_t));\n\n\tfnode->device = 0;\n\tfnode->name[0] = '\\0';\n\tsnprintf(fnode->name, 100, \"[pipe]\");\n\tfnode->uid   = 0;\n\tfnode->gid   = 0;\n\tfnode->mask  = 0666;\n\tfnode->flags = FS_PIPE;\n\tfnode->read  = read_pipe;\n\tfnode->write = write_pipe;\n\tfnode->open  = open_pipe;\n\tfnode->close = close_pipe;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL; /* TODO ioctls for pipes? maybe */\n\tfnode->get_size = pipe_size;\n\n\tfnode->selectcheck = pipe_check;\n\tfnode->selectwait  = pipe_wait;\n\n\tfnode->atime = now();\n\tfnode->mtime = fnode->atime;\n\tfnode->ctime = fnode->atime;\n\n\tfnode->device = pipe;\n\n\tpipe->buffer    = malloc(size);\n\tpipe->write_ptr = 0;\n\tpipe->read_ptr  = 0;\n\tpipe->size      = size;\n\tpipe->refcount  = 0;\n\tpipe->dead      = 0;\n\n\tspin_init(pipe->lock_read);\n\tspin_init(pipe->alert_lock);\n\tspin_init(pipe->wait_lock);\n\tspin_init(pipe->ptr_lock);\n\n\tpipe->wait_queue_writers = list_create(\"pipe writers\",pipe);\n\tpipe->wait_queue_readers = list_create(\"pip readers\",pipe);\n\tpipe->alert_waiters = list_create(\"pipe alert waiters\",pipe);\n\n\treturn fnode;\n}\n"
  },
  {
    "path": "kernel/vfs/portio.c",
    "content": "/**\n * @file  kernel/vfs/portio.c\n * @brief File-based interface to x86 CPU port I/O.\n *\n * Provides a seek/read/write interface to x86 ports at /dev/port\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n\n#include <kernel/process.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n#include <kernel/arch/x86_64/ports.h>\n\nstatic ssize_t read_port(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tswitch (size) {\n\t\tcase 1:\n\t\t\tbuffer[0] = inportb(offset);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\t((uint16_t *)buffer)[0] = inports(offset);\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\t((uint32_t *)buffer)[0] = inportl(offset);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfor (unsigned int i = 0; i < size; ++i) {\n\t\t\t\tbuffer[i] = inportb(offset + i);\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn size;\n}\n\nstatic ssize_t write_port(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tswitch (size) {\n\t\tcase 1:\n\t\t\toutportb(offset, buffer[0]);\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\toutports(offset, ((uint16_t*)buffer)[0]);\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\toutportl(offset, ((uint32_t*)buffer)[0]);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfor (unsigned int i = 0; i < size; ++i) {\n\t\t\t\toutportb(offset +i, buffer[i]);\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn size;\n}\n\nstatic fs_node_t * port_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"port\");\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0660;\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_port;\n\tfnode->write   = write_port;\n\tfnode->open    = NULL;\n\tfnode->close   = NULL;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL;\n\treturn fnode;\n}\n\nvoid portio_initialize(void) {\n\tvfs_mount(\"/dev/port\", port_device_create(), \"port\", \"\");\n}\n"
  },
  {
    "path": "kernel/vfs/procfs.c",
    "content": "/**\n * @file  kernel/vfs/procfs.c\n * @brief Extensible file-based information interface.\n *\n * Provides /proc and its contents, which allow userspace tools\n * to query kernel status through directory and text file interfaces.\n *\n * When a procfs entry is opened, a dynamic buffer is allocated and\n * the bound function is called. The function can then print into\n * the buffer, which will expand as necessary. Reads on the device\n * will then return data from that buffer. When the file node for\n * the entry is later closed, the dynamic buffer is freed. This\n * resolves a long-standing issue with the previous implementation\n * where subsequent reads could return corrupted data if offsets\n * changed from newly generated data.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2023 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/vfs.h>\n#include <kernel/version.h>\n#include <kernel/process.h>\n#include <kernel/pci.h>\n#include <kernel/procfs.h>\n#include <kernel/hashmap.h>\n#include <kernel/time.h>\n#include <kernel/syscall.h>\n#include <kernel/mmu.h>\n#include <kernel/misc.h>\n#include <kernel/module.h>\n#include <kernel/ksym.h>\n\n#define PROCFS_STANDARD_ENTRIES (sizeof(std_entries) / sizeof(struct procfs_entry))\n#define PROCFS_PROCDIR_ENTRIES  (sizeof(procdir_entries) / sizeof(struct procfs_entry))\n\ntypedef struct procfs_entry_node {\n\tfs_node_t fnode;\n\tchar * buf;\n\tsize_t avail;\n\tsize_t used;\n\tprocfs_populate_t func;\n} procfs_entry_t;\n\nstatic ssize_t procfs_entry_read(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tprocfs_entry_t * entry = (void*)node;\n\tif ((size_t)offset > entry->used) return 0;\n\tif (size > entry->used - offset) size = entry->used - offset;\n\tmemcpy(buffer, (uint8_t*)entry->buf + offset, size);\n\treturn size;\n}\n\n\n/**\n * Dynamic reallocating printf thingy\n */\n\nstatic int procfs_cb(void * user, char c) {\n\tprocfs_entry_t * entry = user;\n\n\tif (entry->used >= entry->avail) {\n\t\tentry->avail += 64;\n\t\tentry->buf = realloc(entry->buf, entry->avail);\n\t}\n\n\tentry->buf[entry->used] = c;\n\tentry->used++;\n\treturn 0;\n}\n\nint procfs_printf(fs_node_t * node, const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = xvasprintf(procfs_cb, node, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n\n\nstatic void procfs_entry_open(fs_node_t * node, unsigned int flags) {\n\tprocfs_entry_t * entry = (void*)node;\n\tentry->func(node);\n\tnode->length = entry->used;\n}\n\nstatic void procfs_entry_close(fs_node_t * node) {\n\tprocfs_entry_t * entry = (void*)node;\n\tif (entry->avail) free(entry->buf);\n\tentry->buf = NULL;\n\tentry->avail = 0;\n}\n\nstatic fs_node_t * procfs_generic_create(const char * name, procfs_populate_t read_func) {\n\tprocfs_entry_t * entry = malloc(sizeof(procfs_entry_t));\n\tmemset(entry, 0x00, sizeof(procfs_entry_t));\n\tentry->fnode.inode = 0;\n\tstrcpy(entry->fnode.name, name);\n\n\tentry->buf = NULL;\n\tentry->avail = 0;\n\tentry->used = 0;\n\tentry->func = read_func;\n\n\tentry->fnode.uid = 0;\n\tentry->fnode.gid = 0;\n\tentry->fnode.mask    = 0444;\n\tentry->fnode.flags   = FS_FILE;\n\tentry->fnode.read    = procfs_entry_read;\n\tentry->fnode.write   = NULL;\n\tentry->fnode.open    = procfs_entry_open;\n\tentry->fnode.close   = procfs_entry_close;\n\tentry->fnode.readdir = NULL;\n\tentry->fnode.finddir = NULL;\n\tentry->fnode.ctime   = now();\n\tentry->fnode.mtime   = now();\n\tentry->fnode.atime   = now();\n\treturn &entry->fnode;\n}\n\nstatic void proc_cmdline_func(fs_node_t *node) {\n\tprocess_t * proc = process_from_pid(node->inode);\n\n\tif (!proc) {\n\t\t/* wat */\n\t\treturn;\n\t}\n\n\tif (!proc->cmdline) {\n\t\tprocfs_printf(node, \"%s\", proc->name);\n\t\treturn;\n\t}\n\n\tchar ** args = proc->cmdline;\n\twhile (*args) {\n\t\tprocfs_printf(node, \"%s\", *args);\n\t\tif (*(args+1)) {\n\t\t\tprocfs_printf(node, \"\\036\");\n\t\t}\n\t\targs++;\n\t}\n}\n\nstatic void proc_status_func(fs_node_t *node) {\n\tprocess_t * proc = process_from_pid(node->inode);\n\tprocess_t * parent = process_get_parent(proc);\n\n\tif (!proc) {\n\t\t/* wat */\n\t\treturn;\n\t}\n\n\tchar state = 'S';\n\n\t/* Base state */\n\tif ((proc->flags & PROC_FLAG_RUNNING) || process_is_ready(proc)) {\n\t\tstate = 'R'; /* Running or runnable */\n\t} else if ((proc->flags & PROC_FLAG_FINISHED)) {\n\t\tstate = 'Z'; /* Zombie - exited but not yet reaped */\n\t} else if ((proc->flags & PROC_FLAG_SUSPENDED)) {\n\t\tstate = 'T'; /* Stopped; TODO can we differentiate stopped tracees correctly? */\n\t}\n\n\tchar * name = proc->name + strlen(proc->name) - 1;\n\n\twhile (1) {\n\t\tif (*name == '/') {\n\t\t\tname++;\n\t\t\tbreak;\n\t\t}\n\t\tif (name == proc->name) break;\n\t\tname--;\n\t}\n\n\t/* Calculate process memory usage */\n\tlong mem_usage = mmu_count_user(proc->thread.page_directory->directory) * 4;\n\tlong shm_usage = mmu_count_shm(proc->thread.page_directory->directory) * 4;\n\tlong mem_permille = 1000 * (mem_usage + shm_usage) / mmu_total_memory();\n\n\tprocfs_printf(node,\n\t\t\t\"Name:\\t%s\\n\"  /* name */\n\t\t\t\"State:\\t%c\\n\"\n\t\t\t\"Tgid:\\t%d\\n\"  /* group ? group : pid */\n\t\t\t\"Pid:\\t%d\\n\"   /* pid */\n\t\t\t\"PPid:\\t%d\\n\"  /* parent pid */\n\t\t\t\"Pgid:\\t%d\\n\"  /* progress group id (job) */\n\t\t\t\"Sid:\\t%d\\n\"   /* session id */\n\t\t\t\"Uid:\\t%d\\n\"\n\t\t\t\"Ueip:\\t%#zx\\n\"\n\t\t\t\"SCid:\\t%zu\\n\"\n\t\t\t\"SC0:\\t%#zx\\n\"\n\t\t\t\"SC1:\\t%#zx\\n\"\n\t\t\t\"SC2:\\t%#zx\\n\"\n\t\t\t\"SC3:\\t%#zx\\n\"\n\t\t\t\"SC4:\\t%#zx\\n\"\n\t\t\t\"UserStack:\\t%#zx\\n\"\n\t\t\t\"Path:\\t%s\\n\"\n\t\t\t\"VmSize:\\t %ld kB\\n\"\n\t\t\t\"RssShmem:\\t %ld kB\\n\"\n\t\t\t\"MemPermille:\\t %ld\\n\"\n\t\t\t\"LastCore:\\t %d\\n\"\n\t\t\t\"TotalTime:\\t %ld us\\n\"\n\t\t\t\"SysTime:\\t %ld us\\n\"\n\t\t\t\"CpuPermille:\\t %d %d %d %d\\n\"\n\t\t\t\"UserBrk:\\t%#zx\\n\"\n\t\t\t,\n\t\t\tname,\n\t\t\tstate,\n\t\t\tproc->group ? proc->group : proc->id,\n\t\t\tproc->id,\n\t\t\tparent ? parent->id : 0,\n\t\t\tproc->job,\n\t\t\tproc->session,\n\t\t\tproc->user,\n\t\t\tproc->syscall_registers ? arch_user_ip(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_number(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_arg0(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_arg1(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_arg2(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_arg3(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_syscall_arg4(proc->syscall_registers) : 0,\n\t\t\tproc->syscall_registers ? arch_stack_pointer(proc->syscall_registers) : 0,\n\t\t\tproc->cmdline ? proc->cmdline[0] : \"(none)\",\n\t\t\tmem_usage, shm_usage, mem_permille,\n\t\t\tproc->owner,\n\t\t\tproc->time_total / arch_cpu_mhz(),\n\t\t\tproc->time_sys / arch_cpu_mhz(),\n\t\t\tproc->usage[0], proc->usage[1], proc->usage[2], proc->usage[3],\n\t\t\tproc->image.heap\n\t\t\t);\n}\n\nstatic struct procfs_entry procdir_entries[] = {\n\t{1, \"cmdline\", proc_cmdline_func},\n\t{2, \"status\",  proc_status_func},\n};\n\nstatic struct dirent * readdir_procfs_procdir(fs_node_t *node, uint64_t index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tif (index < PROCFS_PROCDIR_ENTRIES) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = procdir_entries[index].id;\n\t\tstrcpy(out->d_name, procdir_entries[index].name);\n\t\treturn out;\n\t}\n\treturn NULL;\n}\n\nstatic fs_node_t * finddir_procfs_procdir(fs_node_t * node, char * name) {\n\tif (!name) return NULL;\n\n\tfor (unsigned int i = 0; i < PROCFS_PROCDIR_ENTRIES; ++i) {\n\t\tif (!strcmp(name, procdir_entries[i].name)) {\n\t\t\tfs_node_t * out = procfs_generic_create(procdir_entries[i].name, procdir_entries[i].func);\n\t\t\tout->inode = node->inode;\n\t\t\treturn out;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n\nstatic fs_node_t * procfs_procdir_create(process_t * process) {\n\tpid_t pid = process->id;\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = pid;\n\tsnprintf(fnode->name, 100, \"%d\", pid);\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0555;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->read    = NULL;\n\tfnode->write   = NULL;\n\tfnode->open    = NULL;\n\tfnode->close   = NULL;\n\tfnode->readdir = readdir_procfs_procdir;\n\tfnode->finddir = finddir_procfs_procdir;\n\tfnode->nlink   = 1;\n\tfnode->ctime   = process->start.tv_sec;\n\tfnode->mtime   = process->start.tv_sec;\n\tfnode->atime   = process->start.tv_sec;\n\treturn fnode;\n}\n\nstatic void cpuinfo_func(fs_node_t *node) {\n#ifdef __x86_64__\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tprocfs_printf(node,\n\t\t\t\t\"Processor: %d\\n\"\n\t\t\t\t\"Manufacturer: %s\\n\"\n\t\t\t\t\"MHz: %zd\\n\"\n\t\t\t\t\"Family: %d\\n\"\n\t\t\t\t\"Model: %d\\n\"\n\t\t\t\t\"Model name: %s\\n\"\n\t\t\t\t\"LAPIC id: %d\\n\"\n\t\t\t\t\"\\n\",\n\t\t\t\tprocessor_local_data[i].cpu_id,\n\t\t\t\tprocessor_local_data[i].cpu_manufacturer,\n\t\t\t\tarch_cpu_mhz(), /* TODO Should this be per-cpu? */\n\t\t\t\tprocessor_local_data[i].cpu_family,\n\t\t\t\tprocessor_local_data[i].cpu_model,\n\t\t\t\tprocessor_local_data[i].cpu_model_name,\n\t\t\t\tprocessor_local_data[i].lapic_id\n\t\t\t\t);\n\t}\n#elif defined(__aarch64__)\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tprocfs_printf(node,\n\t\t\t\"Processor: %d\\n\"\n\t\t\t\"Implementer: %#x\\n\"\n\t\t\t\"Variant: %#x\\n\"\n\t\t\t\"Architecture: %#x\\n\"\n\t\t\t\"PartNum: %#x\\n\"\n\t\t\t\"Revision: %#x\\n\"\n\t\t\t\"\\n\",\n\t\t\tprocessor_local_data[i].cpu_id,\n\t\t\t(unsigned int)(processor_local_data[i].midr >> 24) & 0xFF,\n\t\t\t(unsigned int)(processor_local_data[i].midr >> 20) & 0xF,\n\t\t\t(unsigned int)(processor_local_data[i].midr >> 16) & 0xF,\n\t\t\t(unsigned int)(processor_local_data[i].midr >> 4)  & 0xFFF,\n\t\t\t(unsigned int)(processor_local_data[i].midr >> 0)  & 0xF\n\t\t\t);\n\t}\n#endif\n}\n\nstatic void meminfo_func(fs_node_t *node) {\n\tsize_t total = mmu_total_memory();\n\tsize_t free  = total - mmu_used_memory();\n\tsize_t kheap = ((uintptr_t)sbrk(0) - 0xffffff0000000000UL) / 1024;\n\n\tprocfs_printf(node,\n\t\t\"MemTotal: %zu kB\\n\"\n\t\t\"MemFree: %zu kB\\n\"\n\t\t\"KHeapUse: %zu kB\\n\"\n\t\t, total, free, kheap);\n}\n\n#ifdef __x86_64__\nstatic void pat_func(fs_node_t *node) {\n\tuint32_t pat_value_low, pat_value_high;\n\tasm volatile ( \"rdmsr\" : \"=a\" (pat_value_low), \"=d\" (pat_value_high): \"c\" (0x277) );\n\tuint64_t pat_values = ((uint64_t)pat_value_high << 32) | (pat_value_low);\n\n\tconst char * pat_names[] = {\n\t\t\"uncacheable (UC)\",\n\t\t\"write combining (WC)\",\n\t\t\"Reserved\",\n\t\t\"Reserved\",\n\t\t\"write through (WT)\",\n\t\t\"write protected (WP)\",\n\t\t\"write back (WB)\",\n\t\t\"uncached (UC-)\"\n\t};\n\n\tint pa_0 = (pat_values >>  0) & 0x7;\n\tint pa_1 = (pat_values >>  8) & 0x7;\n\tint pa_2 = (pat_values >> 16) & 0x7;\n\tint pa_3 = (pat_values >> 24) & 0x7;\n\tint pa_4 = (pat_values >> 32) & 0x7;\n\tint pa_5 = (pat_values >> 40) & 0x7;\n\tint pa_6 = (pat_values >> 48) & 0x7;\n\tint pa_7 = (pat_values >> 56) & 0x7;\n\n\tprocfs_printf(node,\n\t\t\t\"PA0: %d %s\\n\"\n\t\t\t\"PA1: %d %s\\n\"\n\t\t\t\"PA2: %d %s\\n\"\n\t\t\t\"PA3: %d %s\\n\"\n\t\t\t\"PA4: %d %s\\n\"\n\t\t\t\"PA5: %d %s\\n\"\n\t\t\t\"PA6: %d %s\\n\"\n\t\t\t\"PA7: %d %s\\n\",\n\t\t\tpa_0, pat_names[pa_0],\n\t\t\tpa_1, pat_names[pa_1],\n\t\t\tpa_2, pat_names[pa_2],\n\t\t\tpa_3, pat_names[pa_3],\n\t\t\tpa_4, pat_names[pa_4],\n\t\t\tpa_5, pat_names[pa_5],\n\t\t\tpa_6, pat_names[pa_6],\n\t\t\tpa_7, pat_names[pa_7]\n\t);\n}\n#endif\n\nstatic void uptime_func(fs_node_t *node) {\n\tunsigned long timer_ticks, timer_subticks;\n\trelative_time(0,0,&timer_ticks,&timer_subticks);\n\tprocfs_printf(node, \"%lu.%06lu\\n\", timer_ticks, timer_subticks);\n}\n\nstatic void cmdline_func(fs_node_t *node) {\n\tconst char * cmdline = arch_get_cmdline();\n\tprocfs_printf(node, \"%s\\n\", cmdline ? cmdline : \"\");\n}\n\nstatic void version_func(fs_node_t *node) {\n\tprocfs_printf(node, \"%s \", __kernel_name);\n\tprocfs_printf(node, __kernel_version_format,\n\t\t\t__kernel_version_major,\n\t\t\t__kernel_version_minor,\n\t\t\t__kernel_version_lower,\n\t\t\t__kernel_version_suffix);\n\tprocfs_printf(node, \" %s %s %s %s\\n\",\n\t\t\t__kernel_version_codename,\n\t\t\t__kernel_build_date,\n\t\t\t__kernel_build_time,\n\t\t\t__kernel_arch);\n}\n\nstatic void compiler_func(fs_node_t *node) {\n\tprocfs_printf(node, \"%s\\n\", __kernel_compiler_version);\n}\n\nextern tree_t * fs_tree; /* kernel/fs/vfs.c */\n\nstatic void mount_recurse(fs_node_t * pnode, tree_node_t * node, size_t height, char * prefix, char *p) {\n\t/* End recursion on a blank entry */\n\tif (!node) return;\n\n\t/* Get the current process */\n\tstruct vfs_entry * fnode = (struct vfs_entry *)node->value;\n\t/* Print the process name */\n\n\t/* Set up name */\n\tif (node != fs_tree->root) {\n\t\t*p++ = '/';\n\t\tfor (char * n = fnode->name; *n; ++n) {\n\t\t\t*p++ = *n;\n\t\t}\n\t\t*p = '\\0';\n\t}\n\n\tif (fnode->file) procfs_printf(pnode, \"%s %s %s\\n\", prefix, fnode->fs_type, fnode->device);\n\n\tforeach(child, node->children) {\n\t\t/* Recursively print the children */\n\t\tmount_recurse(pnode, child->value, height + 1, prefix, p);\n\t}\n}\n\nstatic void mounts_func(fs_node_t *node) {\n\tchar prefix[1024] = {'/',0};\n\tmount_recurse(node, fs_tree->root, 0, prefix, prefix);\n}\n\nstatic void modules_func(fs_node_t *node) {\n\tlist_t * hash_keys = hashmap_keys(modules_get_list());\n\tif (!hash_keys || !hash_keys->length) return;\n\tforeach(_key, hash_keys) {\n\t\tchar * key = (char *)_key->value;\n\t\tstruct LoadedModule * mod_info = hashmap_get(modules_get_list(), key);\n\t\tprocfs_printf(node, \"%#zx %zu %zu %s\\n\",\n\t\t\tmod_info->baseAddress,\n\t\t\tmod_info->fileSize,\n\t\t\tmod_info->loadedSize,\n\t\t\tkey);\n\t}\n\tfree(hash_keys);\n}\n\nextern hashmap_t * fs_types; /* from kernel/fs/vfs.c */\n\nstatic void filesystems_func(fs_node_t *node) {\n\tlist_t * hash_keys = hashmap_keys(fs_types);\n\tif (!hash_keys || !hash_keys->length) return;\n\tforeach(_key, hash_keys) {\n\t\tchar * key = (char *)_key->value;\n\t\tprocfs_printf(node, \"%s\\n\", key);\n\t}\n\tfree(hash_keys);\n}\n\nstatic void loader_func(fs_node_t *node) {\n\tprocfs_printf(node, \"%s\\n\", arch_get_loader());\n}\n\n#ifdef __x86_64__\n#include <kernel/arch/x86_64/irq.h>\n#include <kernel/arch/x86_64/ports.h>\nstatic void irq_func(fs_node_t *node) {\n\tfor (int i = 0; i < 16; ++i) {\n\t\tprocfs_printf(node, \"irq %d: \", i);\n\t\tfor (int j = 0; j < 4; ++j) {\n\t\t\tconst char * t = get_irq_handler(i, j);\n\t\t\tif (!t) break;\n\t\t\tprocfs_printf(node, \"%s%s\", j ? \",\" : \"\", t);\n\t\t}\n\t\tprocfs_printf(node, \"\\n\");\n\t}\n\n\toutportb(0x20, 0x0b);\n\toutportb(0xa0, 0x0b);\n\tprocfs_printf(node, \"isr=0x%04x\\n\", (inportb(0xA0) << 8) | inportb(0x20));\n\n\toutportb(0x20, 0x0a);\n\toutportb(0xa0, 0x0a);\n\tprocfs_printf(node, \"irr=0x%04x\\n\", (inportb(0xA0) << 8) | inportb(0x20));\n\n\tprocfs_printf(node, \"imr=0x%04x\\n\", (inportb(0xA1) << 8) | inportb(0x21));\n}\n#endif\n\n/**\n * Basically the same as the kdebug `pci` command.\n */\nstatic void scan_hit_list(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tfs_node_t * node = extra;\n\n\tprocfs_printf(node, \"%02x:%02x.%d (%04x, %04x:%04x)\\n\",\n\t\t\t(int)pci_extract_bus(device),\n\t\t\t(int)pci_extract_slot(device),\n\t\t\t(int)pci_extract_func(device),\n\t\t\t(int)pci_find_type(device),\n\t\t\tvendorid,\n\t\t\tdeviceid);\n\n\tprocfs_printf(node, \" BAR0: 0x%08x\", pci_read_field(device, PCI_BAR0, 4));\n\tprocfs_printf(node, \" BAR1: 0x%08x\", pci_read_field(device, PCI_BAR1, 4));\n\tprocfs_printf(node, \" BAR2: 0x%08x\", pci_read_field(device, PCI_BAR2, 4));\n\tprocfs_printf(node, \" BAR3: 0x%08x\", pci_read_field(device, PCI_BAR3, 4));\n\tprocfs_printf(node, \" BAR4: 0x%08x\", pci_read_field(device, PCI_BAR4, 4));\n\tprocfs_printf(node, \" BAR5: 0x%08x\\n\", pci_read_field(device, PCI_BAR5, 4));\n\n\tprocfs_printf(node, \" IRQ Line: %d\", pci_read_field(device, 0x3C, 1));\n\tprocfs_printf(node, \" IRQ Pin: %d\", pci_read_field(device, 0x3D, 1));\n\tprocfs_printf(node, \" Interrupt: %d\", pci_get_interrupt(device));\n\tprocfs_printf(node, \" Status: 0x%04x\\n\", pci_read_field(device, PCI_STATUS, 2));\n}\n\nstatic void pci_func(fs_node_t *node) {\n\tpci_scan(&scan_hit_list, -1, node);\n}\n\nstatic void idle_func(fs_node_t *node) {\n\tfor (int i = 0; i < processor_count; ++i) {\n\t\tprocfs_printf(node, \"%d: %4d %4d %4d %4d\\n\",\n\t\t\ti,\n\t\t\tprocessor_local_data[i].kernel_idle_task->usage[0],\n\t\t\tprocessor_local_data[i].kernel_idle_task->usage[1],\n\t\t\tprocessor_local_data[i].kernel_idle_task->usage[2],\n\t\t\tprocessor_local_data[i].kernel_idle_task->usage[3]\n\t\t);\n\t}\n}\n\nstatic void kallsyms_func(fs_node_t *fnode) {\n\t/* This doesn't include module symbols at the moment... */\n\tlist_t * syms = ksym_list();\n\n\tforeach(node, syms) {\n\t\tprocfs_printf(fnode, \"%016zx %s\\n\", this_core->current_process->user == USER_ROOT_UID ? (uintptr_t)ksym_lookup(node->value) : (uintptr_t)0, (char*)node->value);\n\t}\n\n\tlist_free(syms);\n\tfree(syms);\n}\n\nstatic struct procfs_entry std_entries[] = {\n\t{-1, \"cpuinfo\",  cpuinfo_func},\n\t{-2, \"meminfo\",  meminfo_func},\n\t{-3, \"uptime\",   uptime_func},\n\t{-4, \"cmdline\",  cmdline_func},\n\t{-5, \"version\",  version_func},\n\t{-6, \"compiler\", compiler_func},\n\t{-7, \"mounts\",   mounts_func},\n\t{-8, \"modules\",  modules_func},\n\t{-9, \"filesystems\", filesystems_func},\n\t{-10,\"loader\",   loader_func},\n\t{-11,\"idle\",     idle_func},\n\t{-12,\"kallsyms\", kallsyms_func},\n\t{-13,\"pci\",      pci_func},\n#ifdef __x86_64__\n\t{-14,\"irq\",      irq_func},\n\t{-15,\"pat\",      pat_func},\n#endif\n};\n\nstatic list_t * extended_entries = NULL;\nstatic long next_id = 0;\n\nint procfs_install(struct procfs_entry * entry) {\n\tif (!extended_entries) {\n\t\textended_entries = list_create(\"procfs entries\",NULL);\n\t\tnext_id = -PROCFS_STANDARD_ENTRIES - 1;\n\t}\n\n\tentry->id = next_id--;\n\tlist_insert(extended_entries, entry);\n\n\treturn 0;\n}\n\nstatic struct dirent * readdir_procfs_root(fs_node_t *node, uint64_t index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tif (index == 2) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"self\");\n\t\treturn out;\n\t}\n\n\tindex -= 3;\n\n\tif (index < PROCFS_STANDARD_ENTRIES) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = std_entries[index].id;\n\t\tstrcpy(out->d_name, std_entries[index].name);\n\t\treturn out;\n\t}\n\n\tindex -= PROCFS_STANDARD_ENTRIES;\n\n\tif (extended_entries) {\n\t\tif (index < extended_entries->length) {\n\t\t\tsize_t i = 0;\n\t\t\tnode_t * n = extended_entries->head;\n\t\t\twhile (i < index) {\n\t\t\t\tn = n->next;\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\tstruct procfs_entry * e = n->value;\n\t\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\t\tout->d_ino = e->id;\n\t\t\tstrcpy(out->d_name, e->name);\n\t\t\treturn out;\n\t\t}\n\t\tindex -=  extended_entries->length;\n\t}\n\n\tint i = index + 1;\n\n\tpid_t pid = 0;\n\n\tforeach(lnode, process_list) {\n\t\ti--;\n\t\tif (i == 0) {\n\t\t\tprocess_t * proc = (process_t *)lnode->value;\n\t\t\tpid = proc->id;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (pid == 0) {\n\t\treturn NULL;\n\t}\n\n\tstruct dirent * out = malloc(sizeof(struct dirent));\n\tmemset(out, 0x00, sizeof(struct dirent));\n\tout->d_ino  = pid;\n\tsnprintf(out->d_name, 100, \"%d\", pid);\n\n\treturn out;\n}\n\nstatic ssize_t readlink_self(fs_node_t * node, char * buf, size_t size) {\n\tchar tmp[30];\n\tsize_t req;\n\tsnprintf(tmp, 100, \"/proc/%d\", this_core->current_process->id);\n\treq = strlen(tmp) + 1;\n\n\tif (size < req) {\n\t\tmemcpy(buf, tmp, size);\n\t\tbuf[size-1] = '\\0';\n\t\treturn size-1;\n\t}\n\n\tif (size > req) size = req;\n\n\tmemcpy(buf, tmp, size);\n\treturn size-1;\n}\n\nstatic fs_node_t * procfs_create_self(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"self\");\n\tfnode->mask = 0777;\n\tfnode->uid  = 0;\n\tfnode->gid  = 0;\n\tfnode->flags   = FS_FILE | FS_SYMLINK;\n\tfnode->readlink = readlink_self;\n\tfnode->length  = 1;\n\tfnode->nlink   = 1;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\treturn fnode;\n}\n\nstatic fs_node_t * finddir_procfs_root(fs_node_t * node, char * name) {\n\tif (!name) return NULL;\n\tif (strlen(name) < 1) return NULL;\n\n\tif (name[0] >= '0' && name[0] <= '9') {\n\t\t/* XXX process entries */\n\t\tpid_t pid = atoi(name);\n\t\tprocess_t * proc = process_from_pid(pid);\n\t\tif (!proc) {\n\t\t\treturn NULL;\n\t\t}\n\t\tfs_node_t * out = procfs_procdir_create(proc);\n\t\treturn out;\n\t}\n\n\tif (!strcmp(name,\"self\")) {\n\t\treturn procfs_create_self();\n\t}\n\n\tfor (unsigned int i = 0; i < PROCFS_STANDARD_ENTRIES; ++i) {\n\t\tif (!strcmp(name, std_entries[i].name)) {\n\t\t\tfs_node_t * out = procfs_generic_create(std_entries[i].name, std_entries[i].func);\n\t\t\treturn out;\n\t\t}\n\t}\n\n\tif (extended_entries) {\n\t\tforeach(node, extended_entries) {\n\t\t\tstruct procfs_entry * e = node->value;\n\t\t\tif (!strcmp(name, e->name)) {\n\t\t\t\tfs_node_t * out = procfs_generic_create(e->name, e->func);\n\t\t\t\treturn out;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n\nstatic fs_node_t * procfs_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"proc\");\n\tfnode->mask = 0555;\n\tfnode->uid  = 0;\n\tfnode->gid  = 0;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->read    = NULL;\n\tfnode->write   = NULL;\n\tfnode->open    = NULL;\n\tfnode->close   = NULL;\n\tfnode->readdir = readdir_procfs_root;\n\tfnode->finddir = finddir_procfs_root;\n\tfnode->nlink   = 1;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\treturn fnode;\n}\n\nvoid procfs_initialize(void) {\n\t/* TODO Move this to some sort of config */\n\tvfs_mount(\"/proc\", procfs_create(), \"procfs\", \"\");\n\n\t//debug_print_vfs_tree();\n}\n\n"
  },
  {
    "path": "kernel/vfs/ramdisk.c",
    "content": "/**\n * @file  kernel/vfs/ramdisk.c\n * @brief VFS wrapper for physical memory blocks.\n *\n * Allows raw physical memory blocks provided by the loader to be\n * used like a block file. Used to provide multiboot payloads\n * as /dev/ram* files.\n *\n * Note that the ramdisk driver really does deal with physical\n * memory addresses, not virtual address, and once a block of\n * pages has been handed over to the ramdisk driver it is owned\n * by the ramdisk driver which may mark those pages as available\n * (via an ioctl request).\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n\nstatic ssize_t read_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer);\nstatic ssize_t write_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer);\nstatic void     open_ramdisk(fs_node_t *node, unsigned int flags);\nstatic void     close_ramdisk(fs_node_t *node);\n\nstatic ssize_t read_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\n\tif ((size_t)offset > node->length) {\n\t\treturn 0;\n\t}\n\n\tif ((size_t)offset + size > node->length) {\n\t\tsize_t i = node->length - offset;\n\t\tsize = i;\n\t}\n\n\tmemcpy(buffer, (void *)((uintptr_t)mmu_map_from_physical(node->inode) + (uintptr_t)offset), size);\n\n\treturn size;\n}\n\nstatic ssize_t write_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tif ((size_t)offset > node->length) {\n\t\treturn 0;\n\t}\n\n\tif (offset + size > node->length) {\n\t\tunsigned int i = node->length - offset;\n\t\tsize = i;\n\t}\n\n\tmemcpy((void *)((uintptr_t)mmu_map_from_physical(node->inode) + (uintptr_t)offset), buffer, size);\n\treturn size;\n}\n\nstatic void open_ramdisk(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void close_ramdisk(fs_node_t * node) {\n\treturn;\n}\n\nstatic int ioctl_ramdisk(fs_node_t * node, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase 0x4001:\n\t\t\tif (this_core->current_process->user != 0) {\n\t\t\t\treturn -EPERM;\n\t\t\t} else {\n\t\t\t\t/* Clear all of the memory used by this ramdisk */\n\t\t\t\tif (node->length >= 0x1000) {\n\t\t\t\t\tif (node->length % 0x1000) {\n\t\t\t\t\t\t/* It would be a very bad idea to wipe the wrong page here. */\n\t\t\t\t\t\tnode->length -= node->length % 0x1000;\n\t\t\t\t\t}\n\t\t\t\t\tfor (uintptr_t i = node->inode; i < (node->inode + node->length); i += 0x1000) {\n\t\t\t\t\t\tmmu_frame_release(i);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t/* Mark the file length as 0 */\n\t\t\t\tnode->length = 0;\n\t\t\t\t((fs_node_t*)node->device)->length = 0;\n\t\t\t\treturn 0;\n\t\t\t}\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n\treturn -1;\n}\n\nstatic fs_node_t * ramdisk_device_create(int device_number, uintptr_t location, size_t size) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = location;\n\tsnprintf(fnode->name, 10, \"ram%d\", device_number);\n\tfnode->device = fnode; /* stupid vfs */\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask    = 0770;\n\tfnode->length  = size;\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_ramdisk;\n\tfnode->write   = write_ramdisk;\n\tfnode->open    = open_ramdisk;\n\tfnode->close   = close_ramdisk;\n\tfnode->ioctl   = ioctl_ramdisk;\n\treturn fnode;\n}\n\nstatic int last_device_number = 0;\nfs_node_t * ramdisk_mount(uintptr_t location, size_t size) {\n\tfs_node_t * ramdisk = ramdisk_device_create(last_device_number, location, size);\n\tif (ramdisk) {\n\t\tchar tmp[64];\n\t\tsnprintf(tmp, 63, \"/dev/%s\", ramdisk->name);\n\t\tchar addr[64];\n\t\tsnprintf(addr, 63, \"%p,%zu\", (void*)location, size);\n\t\tvfs_mount(tmp, ramdisk, \"ramdisk\", addr);\n\t\tlast_device_number += 1;\n\t\treturn ramdisk;\n\t}\n\n\treturn NULL;\n}\n\n"
  },
  {
    "path": "kernel/vfs/random.c",
    "content": "/**\n * @file  kernel/vfs/random.c\n * @brief Bad RNG.\n *\n * Provides a terrible little xorshift random number generator.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <stdint.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n\nuint32_t rand(void) {\n\tstatic uint32_t x = 123456789;\n\tstatic uint32_t y = 362436069;\n\tstatic uint32_t z = 521288629;\n\tstatic uint32_t w = 88675123;\n\n\tuint32_t t;\n\n\tt = x ^ (x << 11);\n\tx = y; y = z; z = w;\n\treturn w = w ^ (w >> 19) ^ t ^ (t >> 8);\n}\n\nstatic ssize_t read_random(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tsize_t s = 0;\n\twhile (s < size) {\n\t\tbuffer[s] = rand() % 0xFF;\n\t\ts++;\n\t}\n\treturn size;\n}\n\nstatic fs_node_t * random_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"random\");\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0444;\n\tfnode->length  = 1024;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->read    = read_random;\n\treturn fnode;\n}\n\nvoid random_initialize(void) {\n\tvfs_mount(\"/dev/random\", random_device_create(), \"random\", \"\");\n\tvfs_mount(\"/dev/urandom\", random_device_create(), \"random\", \"\");\n}\n\n"
  },
  {
    "path": "kernel/vfs/tarfs.c",
    "content": "/**\n * @file  kernel/vfs/tarfs.c\n * @brief Read-only filesystem driver for ustar archives.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/tokenize.h>\n\n#include <kernel/list.h>\n#include <kernel/hashmap.h>\n\n#define TARFS_LOG_LEVEL WARNING\n\nstruct tarfs {\n\tfs_node_t * device;\n\tunsigned int length;\n};\n\nstruct ustar {\n\tchar filename[100];\n\tchar mode[8];\n\tchar ownerid[8];\n\tchar groupid[8];\n\n\tchar size[12];\n\tchar mtime[12];\n\n\tchar checksum[8];\n\tchar type[1];\n\tchar link[100];\n\n\tchar ustar[6];\n\tchar version[2];\n\n\tchar owner[32];\n\tchar group[32];\n\n\tchar dev_major[8];\n\tchar dev_minor[8];\n\n\tchar prefix[155];\n};\n\nstatic unsigned int interpret_uid(struct ustar * file) {\n\treturn \n\t\t((file->ownerid[0] - '0') << 18) |\n\t\t((file->ownerid[1] - '0') << 15) |\n\t\t((file->ownerid[2] - '0') << 12) |\n\t\t((file->ownerid[3] - '0') <<  9) |\n\t\t((file->ownerid[4] - '0') <<  6) |\n\t\t((file->ownerid[5] - '0') <<  3) |\n\t\t((file->ownerid[6] - '0') <<  0);\n}\n\nstatic unsigned int interpret_gid(struct ustar * file) {\n\treturn \n\t\t((file->groupid[0] - '0') << 18) |\n\t\t((file->groupid[1] - '0') << 15) |\n\t\t((file->groupid[2] - '0') << 12) |\n\t\t((file->groupid[3] - '0') <<  9) |\n\t\t((file->groupid[4] - '0') <<  6) |\n\t\t((file->groupid[5] - '0') <<  3) |\n\t\t((file->groupid[6] - '0') <<  0);\n}\n\nstatic unsigned int interpret_mode(struct ustar * file) {\n\treturn \n\t\t((file->mode[0] - '0') << 18) |\n\t\t((file->mode[1] - '0') << 15) |\n\t\t((file->mode[2] - '0') << 12) |\n\t\t((file->mode[3] - '0') <<  9) |\n\t\t((file->mode[4] - '0') <<  6) |\n\t\t((file->mode[5] - '0') <<  3) |\n\t\t((file->mode[6] - '0') <<  0);\n}\n\nstatic unsigned int interpret_size(struct ustar * file) {\n\treturn\n\t\t((file->size[ 0] - '0') << 30) |\n\t\t((file->size[ 1] - '0') << 27) |\n\t\t((file->size[ 2] - '0') << 24) |\n\t\t((file->size[ 3] - '0') << 21) |\n\t\t((file->size[ 4] - '0') << 18) |\n\t\t((file->size[ 5] - '0') << 15) |\n\t\t((file->size[ 6] - '0') << 12) |\n\t\t((file->size[ 7] - '0') <<  9) |\n\t\t((file->size[ 8] - '0') <<  6) |\n\t\t((file->size[ 9] - '0') <<  3) |\n\t\t((file->size[10] - '0') <<  0);\n}\n\nstatic unsigned int round_to_512(unsigned int i) {\n\tunsigned int t = i % 512;\n\n\tif (!t) return i;\n\treturn i + (512 - t);\n}\n\nstatic int ustar_from_offset(struct tarfs * self, unsigned int offset, struct ustar * out);\nstatic fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset);\n\n#ifndef strncat\nstatic char * strncat(char *dest, const char *src, size_t n) {\n\tchar * end = dest;\n\twhile (*end != '\\0') {\n\t\t++end;\n\t}\n\tsize_t i = 0;\n\twhile (*src && i < n) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t\ti++;\n\t}\n\t*end = '\\0';\n\treturn dest;\n}\n#endif\n\nstatic int count_slashes(char * string) {\n\tint i = 0;\n\tchar * s = strstr(string, \"/\");\n\twhile (s) {\n\t\tif (*(s+1) == '\\0') return i;\n\t\ti++;\n\t\ts = strstr(s+1,\"/\");\n\t}\n\treturn i;\n}\n\nstatic struct dirent * readdir_tar_root(fs_node_t *node, unsigned long index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tstruct tarfs * self = node->device;\n\t/* Go through each file and pick the ones are at the root */\n\t/* Root files will have no /, so this is easy */\n\tunsigned int offset = 0;\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\twhile (offset < self->length) {\n\t\tint status = ustar_from_offset(self, offset, file);\n\n\t\tif (!status) {\n\t\t\tfree(file);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchar filename_workspace[256];\n\n\t\tmemset(filename_workspace, 0, 256);\n\t\tstrncat(filename_workspace, file->prefix, 155);\n\t\tstrncat(filename_workspace, file->filename, 100);\n\n\t\tif (!count_slashes(filename_workspace)) {\n\t\t\tchar * slash = strstr(filename_workspace,\"/\");\n\t\t\tif (slash) *slash = '\\0'; /* remove trailing slash */\n\t\t\tif (strlen(filename_workspace)) {\n\t\t\t\tif (index == 0) {\n\t\t\t\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\t\t\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\t\t\t\tout->d_ino = offset;\n\t\t\t\t\tstrcpy(out->d_name, filename_workspace);\n\t\t\t\t\tfree(file);\n\t\t\t\t\treturn out;\n\t\t\t\t} else {\n\t\t\t\t\tindex--;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\toffset += 512;\n\t\toffset += round_to_512(interpret_size(file));\n\n\t}\n\n\tfree(file);\n\treturn NULL;\n}\n\nstatic ssize_t read_tarfs(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tstruct tarfs * self = node->device;\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\tustar_from_offset(self, node->inode, file);\n\tsize_t file_size = interpret_size(file);\n\n\tif ((size_t)offset > file_size) return 0;\n\tif (offset + size > file_size) {\n\t\tsize = file_size - offset;\n\t}\n\n\tfree(file);\n\n\treturn read_fs(self->device, offset + node->inode + 512, size, buffer);\n}\n\nstatic struct dirent * readdir_tarfs(fs_node_t *node, unsigned long index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tstruct tarfs * self = node->device;\n\n\t/* Go through each file and pick the ones are at the root */\n\t/* Root files will have no /, so this is easy */\n\tunsigned int offset = node->inode;\n\n\t/* Read myself */\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\tint status = ustar_from_offset(self, node->inode, file);\n\tchar my_filename[256];\n\n\t/* Figure out my own filename, with forward slash */\n\tmemset(my_filename, 0, 256);\n\tstrncat(my_filename, file->prefix, 155);\n\tstrncat(my_filename, file->filename, 100);\n\n\twhile (offset < self->length) {\n\t\tustar_from_offset(self, offset, file);\n\n\t\tif (!status) {\n\t\t\tfree(file);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchar filename_workspace[256];\n\t\tmemset(filename_workspace, 0, 256);\n\t\tstrncat(filename_workspace, file->prefix, 155);\n\t\tstrncat(filename_workspace, file->filename, 100);\n\n\t\tif (startswith(filename_workspace, my_filename)) {\n\t\t\tif (!count_slashes(filename_workspace + strlen(my_filename))) {\n\t\t\t\tif (strlen(filename_workspace + strlen(my_filename))) {\n\t\t\t\t\tif (index == 0) {\n\t\t\t\t\t\tchar * slash = strstr(filename_workspace+strlen(my_filename),\"/\");\n\t\t\t\t\t\tif (slash) *slash = '\\0'; /* remove trailing slash */\n\t\t\t\t\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\t\t\t\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\t\t\t\t\tout->d_ino = offset;\n\t\t\t\t\t\tstrcpy(out->d_name, filename_workspace+strlen(my_filename));\n\t\t\t\t\t\tfree(file);\n\t\t\t\t\t\treturn out;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tindex--;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\toffset += 512;\n\t\toffset += round_to_512(interpret_size(file));\n\t}\n\n\tfree(file);\n\treturn NULL;\n}\n\nstatic fs_node_t * finddir_tarfs(fs_node_t *node, char *name) {\n\tstruct tarfs * self = node->device;\n\n\t/* find my own filename */\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\tustar_from_offset(self, node->inode, file);\n\n\tchar my_filename[256];\n\t/* Figure out my own filename, with forward slash */\n\tmemset(my_filename, 0, 256);\n\tstrncat(my_filename, file->prefix, 155);\n\tstrncat(my_filename, file->filename, 100);\n\n\t/* Append name */\n\tstrncat(my_filename, name, strlen(name));\n\tif (strlen(my_filename) > 255) {\n\t\tprintf(\"tarfs: critical: what?\");\n\t}\n\n\tunsigned int offset = node->inode;\n\twhile (offset < self->length) {\n\t\tint status = ustar_from_offset(self, offset, file);\n\n\t\tif (!status) {\n\t\t\tfree(file);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchar filename_workspace[256];\n\t\tmemset(filename_workspace, 0, 256);\n\t\tstrncat(filename_workspace, file->prefix, 155);\n\t\tstrncat(filename_workspace, file->filename, 100);\n\n\t\tif (filename_workspace[strlen(filename_workspace)-1] == '/') {\n\t\t\tfilename_workspace[strlen(filename_workspace)-1] = '\\0';\n\t\t}\n\t\tif (!strcmp(filename_workspace, my_filename)) {\n\t\t\treturn file_from_ustar(self, file, offset);\n\t\t}\n\n\t\toffset += 512;\n\t\toffset += round_to_512(interpret_size(file));\n\t}\n\n\n\tfree(file);\n\treturn NULL;\n}\n\nstatic ssize_t readlink_tarfs(fs_node_t * node, char * buf, size_t size) {\n\tstruct tarfs * self = node->device;\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\tustar_from_offset(self, node->inode, file);\n\n\tif (size < strlen(file->link) + 1) {\n\t\t//debug_print(INFO, \"Requested read size was only %d, need %d.\", size, strlen(file->link)+1);\n\t\tmemcpy(buf, file->link, size-1);\n\t\tbuf[size-1] = '\\0';\n\t\tfree(file);\n\t\treturn size-1;\n\t} else {\n\t\t//debug_print(INFO, \"Reading link target is [%s]\", file->link);\n\t\tmemcpy(buf, file->link, strlen(file->link) + 1);\n\t\tfree(file);\n\t\treturn strlen(file->link);\n\t}\n\n}\n\nstatic int create_ret_rofs(fs_node_t *parent, char *name, mode_t permission) {\n\treturn -EROFS;\n}\n\nstatic fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset) {\n\tfs_node_t * fs = malloc(sizeof(fs_node_t));\n\tmemset(fs, 0, sizeof(fs_node_t));\n\tfs->device = self;\n\tfs->inode  = offset;\n\tfs->impl   = 0;\n\tchar filename_workspace[256];\n\tmemcpy(fs->name, filename_workspace, strlen(filename_workspace)+1);\n\n\tfs->uid = interpret_uid(file);\n\tfs->gid = interpret_gid(file);\n\tfs->length = interpret_size(file);\n\tfs->mask = interpret_mode(file);\n\tfs->nlink = 0; /* Unsupported */\n\tfs->flags = FS_FILE;\n\tif (file->type[0] == '5') {\n\t\tfs->flags = FS_DIRECTORY;\n\t\tfs->readdir = readdir_tarfs;\n\t\tfs->finddir = finddir_tarfs;\n\t\tfs->create  = create_ret_rofs;\n\t} else if (file->type[0] == '1') {\n\t\t//debug_print(ERROR, \"Hardlink detected\");\n\t\t/* go through file and find target, reassign inode to point to that */\n\t} else if (file->type[0] == '2') {\n\t\tfs->flags = FS_SYMLINK;\n\t\tfs->readlink = readlink_tarfs;\n\t} else {\n\t\tfs->flags = FS_FILE;\n\t\tfs->read = read_tarfs;\n\t}\n\tfree(file);\n#if 0\n\t/* TODO times are also available from the file */\n\tfs->atime = now();\n\tfs->mtime = now();\n\tfs->ctime = now();\n#endif\n\treturn fs;\n}\n\nstatic fs_node_t * finddir_tar_root(fs_node_t *node, char *name) {\n\tstruct tarfs * self = node->device;\n\n\tunsigned int offset = 0;\n\tstruct ustar * file = malloc(sizeof(struct ustar));\n\twhile (offset < self->length) {\n\t\tint status = ustar_from_offset(self, offset, file);\n\n\t\tif (!status) {\n\t\t\tfree(file);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchar filename_workspace[256];\n\t\tmemset(filename_workspace, 0, 256);\n\t\tstrncat(filename_workspace, file->prefix, 155);\n\t\tstrncat(filename_workspace, file->filename, 100);\n\n\t\tif (count_slashes(filename_workspace)) {\n\t\t\t/* skip */\n\t\t} else {\n\t\t\tchar * slash = strstr(filename_workspace,\"/\");\n\t\t\tif (slash) *slash = '\\0';\n\t\t\tif (!strcmp(filename_workspace, name)) {\n\t\t\t\treturn file_from_ustar(self, file, offset);\n\t\t\t}\n\t\t}\n\n\t\toffset += 512;\n\t\toffset += round_to_512(interpret_size(file));\n\t}\n\n\tfree(file);\n\treturn NULL;\n}\n\nstatic int ustar_from_offset(struct tarfs * self, unsigned int offset, struct ustar * out) {\n\tread_fs(self->device, offset, sizeof(struct ustar), (unsigned char*)out);\n\tif (out->ustar[0] != 'u' ||\n\t\tout->ustar[1] != 's' ||\n\t\tout->ustar[2] != 't' ||\n\t\tout->ustar[3] != 'a' ||\n\t\tout->ustar[4] != 'r') {\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\nstatic fs_node_t * tar_mount(const char * device, const char * mount_path) {\n\tchar * arg = strdup(device);\n\tchar * argv[10];\n\tint argc = tokenize(arg, \",\", argv);\n\n\tif (argc > 1) {\n\t\t//debug_print(WARNING, \"tarfs driver takes no options\");\n\t\tprintf(\"tarfs got unexpected mount arguments: %s\\n\", device);\n\t}\n\n\tfs_node_t * dev = kopen(argv[0], 0);\n\tfree(arg); /* Shouldn't need the filename or args anymore */\n\n\tif (!dev) {\n\t\t//debug_print(ERROR, \"failed to open %s\", device);\n\t\tprintf(\"tarfs could not open target device\\n\");\n\t\treturn NULL;\n\t}\n\n\t/* Create a metadata struct for this mount */\n\tstruct tarfs * self = malloc(sizeof(struct tarfs));\n\n\tself->device = dev;\n\tself->length = dev->length;\n\n\tfs_node_t * root = malloc(sizeof(fs_node_t));\n\tmemset(root, 0, sizeof(fs_node_t));\n\n\troot->uid     = 0;\n\troot->gid     = 0;\n\troot->length  = 0;\n\troot->mask    = 0555;\n\troot->readdir = readdir_tar_root;\n\troot->finddir = finddir_tar_root;\n\troot->create  = create_ret_rofs;\n\troot->flags   = FS_DIRECTORY;\n\troot->device  = self;\n\n\treturn root;\n}\n\nint tarfs_register_init(void) {\n\tvfs_register(\"tar\", tar_mount);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "kernel/vfs/tmpfs.c",
    "content": "/**\n * @file  kernel/vfs/tmpfs.c\n * @brief In-memory read-write filesystem.\n *\n * Generally provides the filesystem for \"migrated\" live CDs,\n * as well as /tmp and /var.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <stdint.h>\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/vfs.h>\n#include <kernel/process.h>\n#include <kernel/tokenize.h>\n#include <kernel/tmpfs.h>\n#include <kernel/spinlock.h>\n#include <kernel/mmu.h>\n#include <kernel/time.h>\n#include <kernel/procfs.h>\n\n/* 4KB */\n#define BLOCKSIZE 0x1000\n\n#define TMPFS_TYPE_FILE 1\n#define TMPFS_TYPE_DIR  2\n#define TMPFS_TYPE_LINK 3\n\nstatic volatile intptr_t tmpfs_total_blocks = 0;\n\nstatic fs_node_t * tmpfs_from_dir(struct tmpfs_dir * d);\n\nstatic struct tmpfs_file * tmpfs_file_new(char * name) {\n\tstruct tmpfs_file * t = malloc(sizeof(struct tmpfs_file));\n\tspin_init(t->lock);\n\tt->name = strdup(name);\n\tt->type = TMPFS_TYPE_FILE;\n\tt->length = 0;\n\tt->pointers = 2;\n\tt->block_count = 0;\n\tt->mask = 0;\n\tt->uid = 0;\n\tt->gid = 0;\n\tt->atime = now();\n\tt->mtime = t->atime;\n\tt->ctime = t->atime;\n\tt->blocks = malloc(t->pointers * sizeof(char *));\n\tfor (size_t i = 0; i < t->pointers; ++i) {\n\t\tt->blocks[i] = 0;\n\t}\n\n\treturn t;\n}\n\nstatic int symlink_tmpfs(fs_node_t * parent, char * target, char * name) {\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode;\n\n\tspin_lock(d->lock);\n\tforeach(f, d->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tspin_unlock(d->lock);\n\t\t\treturn -EEXIST; /* Already exists */\n\t\t}\n\t}\n\tspin_unlock(d->lock);\n\n\tstruct tmpfs_file * t = tmpfs_file_new(name);\n\tt->mount = parent->mount;\n\tt->type = TMPFS_TYPE_LINK;\n\tt->target = strdup(target);\n\tt->length = strlen(target);\n\n\tt->mask = 0777;\n\tt->uid = this_core->current_process->user;\n\tt->gid = this_core->current_process->user;\n\n\tspin_lock(d->lock);\n\tlist_insert(d->files, t);\n\tspin_unlock(d->lock);\n\n\treturn 0;\n}\n\nstatic ssize_t readlink_tmpfs(fs_node_t * node, char * buf, size_t size) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\tspin_lock(t->lock);\n\tif (t->type != TMPFS_TYPE_LINK) {\n\t\tspin_unlock(t->lock);\n\t\tprintf(\"tmpfs: not a symlink?\\n\");\n\t\treturn -1;\n\t}\n\n\tif (size < strlen(t->target) + 1) {\n\t\tmemcpy(buf, t->target, size-1);\n\t\tbuf[size-1] = '\\0';\n\t\tspin_unlock(t->lock);\n\t\treturn size-2;\n\t} else {\n\t\tsize_t len = strlen(t->target);\n\t\tmemcpy(buf, t->target, len + 1);\n\t\tspin_unlock(t->lock);\n\t\treturn len;\n\t}\n}\n\nstatic struct tmpfs_dir * tmpfs_dir_new(char * name, struct tmpfs_dir * parent) {\n\tstruct tmpfs_dir * d = malloc(sizeof(struct tmpfs_dir));\n\tspin_init(d->lock);\n\tspin_init(d->nest_lock);\n\td->mount = parent ? parent->mount : NULL;\n\td->name = strdup(name);\n\td->type = TMPFS_TYPE_DIR;\n\td->mask = 0;\n\td->uid = 0;\n\td->gid = 0;\n\td->atime = now();\n\td->mtime = d->atime;\n\td->ctime = d->atime;\n\td->files = list_create(\"tmpfs directory entries\",d);\n\treturn d;\n}\n\nstatic void tmpfs_file_free(struct tmpfs_file * t) {\n\tspin_lock(t->lock);\n\tif (t->type == TMPFS_TYPE_LINK) {\n\t\t/* free target string */\n\t\tfree(t->target);\n\t}\n\tfor (size_t i = 0; i < t->block_count; ++i) {\n\t\tmmu_frame_release((uintptr_t)t->blocks[i] * 0x1000);\n\t\ttmpfs_total_blocks--;\n\t}\n\tspin_unlock(t->lock);\n}\n\nstatic void tmpfs_file_blocks_embiggen(struct tmpfs_file * t) {\n\tt->pointers *= 2;\n\tt->blocks = realloc(t->blocks, sizeof(char *) * t->pointers);\n}\n\nstatic char * tmpfs_file_getset_block(struct tmpfs_file * t, size_t blockid, int create) {\n\tif (create) {\n\t\twhile (blockid >= t->pointers) {\n\t\t\ttmpfs_file_blocks_embiggen(t);\n\t\t}\n\t\twhile (blockid >= t->block_count) {\n\t\t\tuintptr_t index = mmu_allocate_a_frame();\n\t\t\ttmpfs_total_blocks++;\n\t\t\tif (create == 2) {\n\t\t\t\tmemset((char*)mmu_map_from_physical(index << 12), 0, BLOCKSIZE);\n\t\t\t}\n\t\t\tt->blocks[t->block_count] = index;\n\t\t\tt->block_count += 1;\n\t\t}\n\t} else {\n\t\tif (blockid >= t->block_count) {\n\t\t\tprintf(\"tmpfs: not enough blocks?\\n\");\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn (char *)mmu_map_from_physical(t->blocks[blockid] << 12);\n}\n\n\nstatic ssize_t read_tmpfs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\tspin_lock(t->lock);\n\n\tt->atime = now();\n\n\tuint64_t end;\n\tif ((size_t)offset + size > t->length) {\n\t\tend = t->length;\n\t} else {\n\t\tend = offset + size;\n\t}\n\tuint64_t start_block  = offset / BLOCKSIZE;\n\tuint64_t end_block    = end / BLOCKSIZE;\n\tuint64_t end_size     = end - end_block * BLOCKSIZE;\n\tuint64_t size_to_read = end - offset;\n\tif (start_block == end_block && (size_t)offset == end) {\n\t\tspin_unlock(t->lock);\n\t\treturn 0;\n\t}\n\tif (start_block == end_block) {\n\t\tvoid *buf = tmpfs_file_getset_block(t, start_block, 0);\n\t\tmemcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), size_to_read);\n\t\tspin_unlock(t->lock);\n\t\treturn size_to_read;\n\t} else {\n\t\tuint64_t block_offset;\n\t\tuint64_t blocks_read = 0;\n\t\tfor (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) {\n\t\t\tif (block_offset == start_block) {\n\t\t\t\tvoid *buf = tmpfs_file_getset_block(t, block_offset, 0);\n\t\t\t\tmemcpy(buffer, (uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), BLOCKSIZE - (offset % BLOCKSIZE));\n\t\t\t} else {\n\t\t\t\tvoid *buf = tmpfs_file_getset_block(t, block_offset, 0);\n\t\t\t\tmemcpy(buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), buf, BLOCKSIZE);\n\t\t\t}\n\t\t}\n\t\tif (end_size) {\n\t\t\tvoid *buf = tmpfs_file_getset_block(t, end_block, 0);\n\t\t\tmemcpy(buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), buf, end_size);\n\t\t}\n\t}\n\tspin_unlock(t->lock);\n\treturn size_to_read;\n}\n\nstatic ssize_t write_tmpfs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\tspin_lock(t->lock);\n\tt->atime = now();\n\tt->mtime = t->atime;\n\n\tuint64_t end;\n\tif ((size_t)offset + size > t->length) {\n\t\tt->length = offset + size;\n\t}\n\tend = offset + size;\n\tuint64_t start_block  = offset / BLOCKSIZE;\n\tuint64_t end_block    = end / BLOCKSIZE;\n\tuint64_t end_size     = end - end_block * BLOCKSIZE;\n\tuint64_t size_to_read = end - offset;\n\tif (start_block == end_block) {\n\t\tvoid *buf = tmpfs_file_getset_block(t, start_block, 1);\n\t\tmemcpy((uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), buffer, size_to_read);\n\t\tspin_unlock(t->lock);\n\t\treturn size_to_read;\n\t} else {\n\t\tuint64_t block_offset;\n\t\tuint64_t blocks_read = 0;\n\t\tfor (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) {\n\t\t\tif (block_offset == start_block) {\n\t\t\t\tvoid *buf = tmpfs_file_getset_block(t, block_offset, 1);\n\t\t\t\tmemcpy((uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), buffer, BLOCKSIZE - (offset % BLOCKSIZE));\n\t\t\t} else {\n\t\t\t\tvoid *buf = tmpfs_file_getset_block(t, block_offset, 1);\n\t\t\t\tmemcpy(buf, buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), BLOCKSIZE);\n\t\t\t}\n\t\t}\n\t\tif (end_size) {\n\t\t\tvoid *buf = tmpfs_file_getset_block(t, end_block, 1);\n\t\t\tmemcpy(buf, buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), end_size);\n\t\t}\n\t}\n\tspin_unlock(t->lock);\n\treturn size_to_read;\n}\n\nstatic int chmod_tmpfs(fs_node_t * node, int mode) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\t/* XXX permissions */\n\tt->mask = mode;\n\n\treturn 0;\n}\n\nstatic int chown_tmpfs(fs_node_t * node, int uid, int gid) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\tspin_lock(t->lock);\n\tif (uid != -1) t->uid = uid;\n\tif (gid != -1) t->gid = gid;\n\tspin_unlock(t->lock);\n\n\treturn 0;\n}\n\nstatic int truncate_tmpfs(fs_node_t * node, size_t size) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\tspin_lock(t->lock);\n\n\tif (size == t->length) goto _exit_truncate;\n\n\tuint64_t old_end_block = (t->length / BLOCKSIZE);\n\tuint64_t old_end_size  = t->length - old_end_block * BLOCKSIZE;\n\tuint64_t old_blocks = old_end_block + !!old_end_size;\n\tuint64_t new_end_block = (size / BLOCKSIZE);\n\tuint64_t new_end_size  = size - new_end_block * BLOCKSIZE;\n\tuint64_t new_blocks = new_end_block + !!new_end_size;\n\n\n\t/* Is the target size bigger or smaller? */\n\tif (size > t->length) {\n\t\tif (old_end_block == new_end_block) {\n\t\t\tchar *buf = tmpfs_file_getset_block(t, old_end_block, 0);\n\t\t\tmemset(buf + old_end_size, 0, new_end_size - old_end_size);\n\t\t} else {\n\t\t\ttmpfs_file_getset_block(t, new_end_block, 2);\n\t\t\tchar *buf = tmpfs_file_getset_block(t, old_end_block, 0);\n\t\t\tmemset(buf + old_end_size, 0, BLOCKSIZE - old_end_size);\n\t\t}\n\t\tt->length = size;\n\t\tgoto _exit_truncate;\n\t}\n\n\tif (size == 0) {\n\t\tfor (size_t i = 0; i < t->block_count; ++i) {\n\t\t\tmmu_frame_release((uintptr_t)t->blocks[i] * 0x1000);\n\t\t\ttmpfs_total_blocks--;\n\t\t\tt->blocks[i] = 0;\n\t\t}\n\t\tt->block_count = 0;\n\t\tt->length = 0;\n\t\tgoto _exit_truncate;\n\t}\n\n\t/* Size is less than current but > 0 */\n\tif (new_blocks < old_blocks) {\n\t\tfor (uint64_t i = new_blocks; i < old_blocks; ++i) {\n\t\t\tmmu_frame_release((uintptr_t)t->blocks[i] * 0x1000);\n\t\t\ttmpfs_total_blocks--;\n\t\t\tt->blocks[i] = 0;\n\t\t}\n\t\tt->block_count = new_blocks;\n\t}\n\n\tt->length = size;\n\n_exit_truncate:\n\tt->mtime = node->atime;\n\tspin_unlock(t->lock);\n\treturn 0;\n}\n\nstatic void open_tmpfs(fs_node_t * node, unsigned int flags) {\n\tstruct tmpfs_file * t = (struct tmpfs_file *)(node->inode);\n\n\tt->atime = now();\n}\n\nstatic fs_node_t * tmpfs_from_file(struct tmpfs_file * t) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tspin_lock(t->lock);\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tstrcpy(fnode->name, t->name);\n\tfnode->inode = (uintptr_t)t;\n\tfnode->mask = t->mask;\n\tfnode->uid = t->uid;\n\tfnode->gid = t->gid;\n\tfnode->atime = t->atime;\n\tfnode->ctime = t->ctime;\n\tfnode->mtime = t->mtime;\n\tfnode->flags   = FS_FILE;\n\tfnode->read    = read_tmpfs;\n\tfnode->write   = write_tmpfs;\n\tfnode->open    = open_tmpfs;\n\tfnode->close   = NULL;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->chmod   = chmod_tmpfs;\n\tfnode->chown   = chown_tmpfs;\n\tfnode->length  = t->length;\n\tfnode->truncate = truncate_tmpfs;\n\tfnode->nlink   = 1;\n\tfnode->mount   = t->mount;\n\tfnode->device  = t->mount;\n\tspin_unlock(t->lock);\n\treturn fnode;\n}\n\nstatic fs_node_t * tmpfs_from_link(struct tmpfs_file * t) {\n\tfs_node_t * fnode = tmpfs_from_file(t);\n\tfnode->flags   |= FS_SYMLINK;\n\tfnode->readlink = readlink_tmpfs;\n\tfnode->read     = NULL;\n\tfnode->write    = NULL;\n\tfnode->create   = NULL;\n\tfnode->mkdir    = NULL;\n\tfnode->readdir  = NULL;\n\tfnode->finddir  = NULL;\n\treturn fnode;\n}\n\nstatic struct dirent * readdir_tmpfs(fs_node_t *node, uint64_t index) {\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)node->inode;\n\tuint64_t i = 0;\n\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tif (index >= d->files->length) return NULL;\n\n\tforeach(f, d->files) {\n\t\tif (i == index) {\n\t\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\t\tout->d_ino = (uint64_t)t;\n\t\t\tstrcpy(out->d_name, t->name);\n\t\t\treturn out;\n\t\t} else {\n\t\t\t++i;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nstatic fs_node_t * finddir_tmpfs(fs_node_t * node, char * name) {\n\tif (!name) return NULL;\n\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)node->inode;\n\n\tspin_lock(d->lock);\n\n\tforeach(f, d->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tfs_node_t * out = NULL;\n\t\t\tswitch (t->type) {\n\t\t\t\tcase TMPFS_TYPE_FILE:\n\t\t\t\t\tout = tmpfs_from_file(t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase TMPFS_TYPE_LINK:\n\t\t\t\t\tout = tmpfs_from_link(t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase TMPFS_TYPE_DIR:\n\t\t\t\t\tout = tmpfs_from_dir((struct tmpfs_dir *)t);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tspin_unlock(d->lock);\n\t\t\treturn out;\n\t\t}\n\t}\n\n\tspin_unlock(d->lock);\n\treturn NULL;\n}\n\n\nstatic int try_free_dir(struct tmpfs_dir * d) {\n\tspin_lock(d->lock);\n\tif (d->files && d->files->length != 0) {\n\t\tspin_unlock(d->lock);\n\t\treturn 1;\n\t}\n\tfree(d->files);\n\tspin_unlock(d->lock);\n\treturn 0;\n}\n\nstatic int unlink_tmpfs(fs_node_t * node, char * name) {\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)node->inode;\n\tint i = -1, j = 0;\n\n\tspin_lock(d->lock);\n\tforeach(f, d->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tif (t->type == TMPFS_TYPE_DIR) {\n\t\t\t\tif (try_free_dir((void*)t)) {\n\t\t\t\t\tspin_unlock(d->lock);\n\t\t\t\t\treturn -ENOTEMPTY;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttmpfs_file_free(t);\n\t\t\t}\n\t\t\tfree(t);\n\t\t\ti = j;\n\t\t\tbreak;\n\t\t}\n\t\tj++;\n\t}\n\n\tif (i >= 0) {\n\t\tlist_remove(d->files, i);\n\t} else {\n\t\tspin_unlock(d->lock);\n\t\treturn -ENOENT;\n\t}\n\n\tspin_unlock(d->lock);\n\treturn 0;\n}\n\nstatic int create_tmpfs(fs_node_t *parent, char *name, mode_t permission) {\n\tif (!name) return -EINVAL;\n\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode;\n\n\tspin_lock(d->lock);\n\tforeach(f, d->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tspin_unlock(d->lock);\n\t\t\treturn -EEXIST; /* Already exists */\n\t\t}\n\t}\n\tspin_unlock(d->lock);\n\n\tstruct tmpfs_file * t = tmpfs_file_new(name);\n\tt->mount = parent->mount;\n\tt->mask = permission;\n\tt->uid = this_core->current_process->user;\n\tt->gid = this_core->current_process->user_group;\n\n\tspin_lock(d->lock);\n\tlist_insert(d->files, t);\n\tspin_unlock(d->lock);\n\n\treturn 0;\n}\n\nstatic int mkdir_tmpfs(fs_node_t * parent, char * name, mode_t permission) {\n\tif (!name) return -EINVAL;\n\tif (!strlen(name)) return -EINVAL;\n\n\tstruct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode;\n\n\tspin_lock(d->lock);\n\tforeach(f, d->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!strcmp(name, t->name)) {\n\t\t\tspin_unlock(d->lock);\n\t\t\treturn -EEXIST; /* Already exists */\n\t\t}\n\t}\n\tspin_unlock(d->lock);\n\n\t/* Need both exec and write on the parent to create a new entry */\n\tif (!has_permission(parent, 02) || !has_permission(parent, 01)) {\n\t\treturn -EACCES;\n\t}\n\n\tstruct tmpfs_dir * out = tmpfs_dir_new(name, d);\n\tout->mask = permission;\n\tout->uid  = this_core->current_process->user;\n\tout->gid  = this_core->current_process->user;\n\n\tspin_lock(d->lock);\n\tlist_insert(d->files, out);\n\tspin_unlock(d->lock);\n\n\treturn 0;\n}\n\nstatic int path_comp(const char * a, const char * b) {\n\twhile (*a && *b && *a != '/' && *b != '/') {\n\t\tif (*a != *b) return 1;\n\t\ta++;\n\t\tb++;\n\t}\n\n\tif ((*a == '/' || !*a) && (*b == '/' || !*b)) return 0;\n\treturn 1;\n}\n\nstatic int endswith(const char * str, char ch) {\n\tsize_t len = strlen(str);\n\tif (len > 1 && str[len-1] == ch) return 1;\n\treturn 0;\n}\n\nstatic char * path_dup(const char * path) {\n\tconst char * n = path;\n\twhile (*n && *n != '/') n++;\n\tchar * out = malloc(n - path + 1);\n\tmemcpy(out, path, n - path);\n\tout[n-path] = '\\0';\n\treturn out;\n}\n\n\nstatic int rename_tmpfs(fs_node_t * mount_root, fs_node_t * src_dir, const char * src_name, fs_node_t * dest_dir, const char * dest_name) {\n\t/* src_dir and dest_dir are definitely from us, no worries there */\n\tint ret = 0;\n\n\tstruct tmpfs_dir * root = (struct tmpfs_dir*)mount_root->inode;\n\tspin_lock(root->nest_lock);\n\n\tstruct tmpfs_dir * ds = (struct tmpfs_dir *)src_dir->inode;\n\tspin_lock(ds->lock);\n\n\t/* First, get the source file */\n\tstruct tmpfs_file * src_file = NULL;\n\tnode_t * src_node = NULL;\n\tforeach(f, ds->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!path_comp(src_name, t->name)) {\n\t\t\tsrc_file = t;\n\t\t\tsrc_node = f;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!src_file) {\n\t\tret = -ENOENT;\n\t\tgoto _cleanup_src;\n\t}\n\n\tif (src_file->type != TMPFS_TYPE_DIR && endswith(src_name, '/')) {\n\t\t/* Source ended with trailing slashes, but was not a directory. */\n\t\tret = -ENOTDIR;\n\t\tgoto _cleanup_src;\n\t}\n\n\tstruct tmpfs_dir * dd = (struct tmpfs_dir *)dest_dir->inode;\n\tif (dd != ds) spin_lock(dd->lock);\n\n\tstruct tmpfs_file * dest_file = NULL;\n\tnode_t * dest_node = NULL;\n\tforeach(f, dd->files) {\n\t\tstruct tmpfs_file * t = (struct tmpfs_file *)f->value;\n\t\tif (!path_comp(dest_name, t->name)) {\n\t\t\tdest_file = t;\n\t\t\tdest_node = f;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (dest_file && dest_file->type != TMPFS_TYPE_DIR && endswith(dest_name, '/')) {\n\t\t/* Destination ended with trailing slashes, but was not a directory. */\n\t\tret = -ENOTDIR;\n\t\tgoto _cleanup;\n\t}\n\n\tif (!dest_file) {\n\t\tif (endswith(dest_name,'/') && src_file->type != TMPFS_TYPE_DIR) {\n\t\t\t/* Destination did not exist, ended with trailing slashes, but the source was not a directory. */\n\t\t\tret = -ENOTDIR;\n\t\t\tgoto _cleanup;\n\t\t}\n\t\tchar * old_name = src_file->name;\n\t\tsrc_file->name = path_dup(dest_name);\n\t\tfree(old_name);\n\n\t\tlist_insert(dd->files, src_file);\n\t\tlist_delete(ds->files, src_node);\n\t} else if (src_file == dest_file) {\n\t\t/* Do nothing */\n\t} else {\n\t\tif (dest_file->type == TMPFS_TYPE_DIR) {\n\t\t\tstruct tmpfs_dir * dest = (struct tmpfs_dir*)dest_file;\n\t\t\tif (dest->files && dest->files->length) {\n\t\t\t\t/* Destination is not empty */\n\t\t\t\tret = -ENOTEMPTY;\n\t\t\t\tgoto _cleanup;\n\t\t\t}\n\t\t\tif (src_file->type != TMPFS_TYPE_DIR) {\n\t\t\t\t/* Source is not a directory but destination is */\n\t\t\t\tret = -EISDIR;\n\t\t\t\tgoto _cleanup;\n\t\t\t}\n\t\t} else if (src_file->type == TMPFS_TYPE_DIR) {\n\t\t\t/* Source is a directory, but destination is not */\n\t\t\tret = -ENOTDIR;\n\t\t\tgoto _cleanup;\n\t\t}\n\n\t\t/* Rename src */\n\t\tchar * old_name = src_file->name;\n\t\tsrc_file->name = path_dup(dest_name);\n\t\tfree(old_name);\n\n\t\tlist_delete(ds->files, src_node);\n\t\tdest_node->value = src_file;\n\n\t\t/* Unlink the original destination file */\n\t\tif (dest_file->type == TMPFS_TYPE_DIR) {\n\t\t\ttry_free_dir((void*)dest_file);\n\t\t} else {\n\t\t\ttmpfs_file_free(dest_file);\n\t\t}\n\t}\n\n_cleanup:\n\tif (dd != ds) spin_unlock(dd->lock);\n_cleanup_src:\n\tspin_unlock(ds->lock);\n\tspin_unlock(root->nest_lock);\n\treturn ret;\n}\n\nstatic fs_node_t * tmpfs_from_dir(struct tmpfs_dir * d) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tspin_lock(d->lock);\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"tmp\");\n\tfnode->mount = d->mount;\n\tfnode->device = d->mount;\n\tfnode->mask = d->mask;\n\tfnode->uid  = d->uid;\n\tfnode->gid  = d->gid;\n\tfnode->inode   = (uintptr_t)d;\n\tfnode->atime   = d->atime;\n\tfnode->mtime   = d->mtime;\n\tfnode->ctime   = d->ctime;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->read    = NULL;\n\tfnode->write   = NULL;\n\tfnode->open    = NULL;\n\tfnode->close   = NULL;\n\tfnode->readdir = readdir_tmpfs;\n\tfnode->finddir = finddir_tmpfs;\n\tfnode->create  = create_tmpfs;\n\tfnode->unlink  = unlink_tmpfs;\n\tfnode->mkdir   = mkdir_tmpfs;\n\tfnode->nlink   = 1; /* should be \"number of children that are directories + 1\" */\n\tfnode->symlink = symlink_tmpfs;\n\n\tfnode->chown   = chown_tmpfs;\n\tfnode->chmod   = chmod_tmpfs;\n\tfnode->rename  = rename_tmpfs;\n\tspin_unlock(d->lock);\n\n\treturn fnode;\n}\n\nfs_node_t * tmpfs_create(char * name) {\n\tstruct tmpfs_dir * tmpfs_root = tmpfs_dir_new(name, NULL);\n\ttmpfs_root->mask = 0777;\n\ttmpfs_root->uid  = 0;\n\ttmpfs_root->gid  = 0;\n\n\tfs_node_t * out = tmpfs_from_dir(tmpfs_root);\n\ttmpfs_root->mount = out;\n\tout->mount = out;\n\tout->device = out;\n\treturn out;\n}\n\nfs_node_t * tmpfs_mount(const char * device, const char * mount_path) {\n\tchar * arg = strdup(device);\n\tchar * argv[10];\n\tint argc = tokenize(arg, \",\", argv);\n\n\tfs_node_t * fs = tmpfs_create(argv[0]);\n\n\tif (argc > 1) {\n\t\tif (strlen(argv[1]) < 3) {\n\t\t\tprintf(\"tmpfs: ignoring bad permission option for tmpfs\\n\");\n\t\t} else {\n\t\t\tint mode = ((argv[1][0] - '0') << 6) |\n\t\t\t           ((argv[1][1] - '0') << 3) |\n\t\t\t           ((argv[1][2] - '0') << 0);\n\t\t\tfs->mask = mode;\n\t\t}\n\t}\n\n\t//free(arg);\n\treturn fs;\n}\n\nstatic void tmpfs_func(fs_node_t * node) {\n\tprocfs_printf(node,\n\t\t\"UsedBlocks:\\t%zd\\n\",\n\t\ttmpfs_total_blocks);\n}\n\nstatic struct procfs_entry tmpfs_entry = {\n\t0,\n\t\"tmpfs\",\n\ttmpfs_func,\n};\n\nvoid tmpfs_register_init(void) {\n\tvfs_register(\"tmpfs\", tmpfs_mount);\n\tprocfs_install(&tmpfs_entry);\n}\n\n"
  },
  {
    "path": "kernel/vfs/tty.c",
    "content": "/**\n * @file kernel/vfs/tty.c\n * @brief PTY driver.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2021 K. Lange\n */\n#include <stddef.h>\n#include <errno.h>\n#include <kernel/vfs.h>\n#include <kernel/pipe.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/ringbuffer.h>\n#include <kernel/pty.h>\n#include <kernel/hashmap.h>\n#include <kernel/process.h>\n#include <kernel/signal.h>\n#include <kernel/time.h>\n#include <sys/ioctl.h>\n#include <sys/termios.h>\n#include <sys/signal_defs.h>\n\n#define TTY_BUFFER_SIZE 4096\n\n#define MIN(a,b) ((a) < (b) ? (a) : (b))\nextern void ptr_validate(void * ptr, const char * syscall);\n#define validate(o) ptr_validate(o,\"ioctl\")\n\nstatic int _pty_counter = 0;\nstatic hashmap_t * _pty_index = NULL;\nstatic fs_node_t * _pty_dir = NULL;\nstatic fs_node_t * _dev_tty = NULL;\n\nstatic void pty_write_in(pty_t * pty, uint8_t c) {\n\tring_buffer_write(pty->in, 1, &c);\n}\n\nstatic void pty_write_out(pty_t * pty, uint8_t c) {\n\tring_buffer_write(pty->out, 1, &c);\n}\n\n#define IN(character)   pty->write_in(pty, (uint8_t)character)\n#define OUT(character)  pty->write_out(pty, (uint8_t)character)\n\nstatic void dump_input_buffer(pty_t * pty) {\n\tchar * c = pty->canon_buffer;\n\twhile (pty->canon_buflen > 0) {\n\t\tIN(*c);\n\t\tpty->canon_buflen--;\n\t\tc++;\n\t}\n}\n\nstatic void clear_input_buffer(pty_t * pty) {\n\tpty->canon_buflen = 0;\n\tpty->canon_buffer[0] = '\\0';\n}\n\n#define output_process_slave tty_output_process_slave\n#define output_process tty_output_process\n#define input_process tty_input_process\n\nvoid tty_output_process_slave(pty_t * pty, uint8_t c) {\n\tif (!(pty->tios.c_oflag & OPOST)) {\n\t\tOUT(c);\n\t\treturn;\n\t}\n\n\tif (c == '\\n' && (pty->tios.c_oflag & ONLCR)) {\n\t\tc = '\\n';\n\t\tOUT(c);\n\t\tc = '\\r';\n\t\tOUT(c);\n\t\treturn;\n\t}\n\n\tif (c == '\\r' && (pty->tios.c_oflag & ONLRET)) {\n\t\treturn;\n\t}\n\n\tif (c >= 'a' && c <= 'z' && (pty->tios.c_oflag & OLCUC)) {\n\t\tc = c + 'A' - 'a';\n\t\tOUT(c);\n\t\treturn;\n\t}\n\n\tOUT(c);\n}\n\nvoid tty_output_process(pty_t * pty, uint8_t c) {\n\toutput_process_slave(pty, c);\n}\n\nstatic int is_control(int c) {\n\treturn c < ' ' || c == 0x7F;\n}\n\nstatic void erase_one(pty_t * pty, int erase) {\n\tif (pty->canon_buflen > 0) {\n\t\t/* How many do we backspace? */\n\t\tint vwidth = 1;\n\t\tpty->canon_buflen--;\n\t\tif (is_control(pty->canon_buffer[pty->canon_buflen])) {\n\t\t\t/* Erase ^@ */\n\t\t\tvwidth = 2;\n\t\t}\n\t\tpty->canon_buffer[pty->canon_buflen] = '\\0';\n\t\tif (pty->tios.c_lflag & ECHO) {\n\t\t\tif (erase) {\n\t\t\t\tfor (int i = 0; i < vwidth; ++i) {\n\t\t\t\t\toutput_process(pty, '\\010');\n\t\t\t\t\toutput_process(pty, ' ');\n\t\t\t\t\toutput_process(pty, '\\010');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid tty_input_process(pty_t * pty, uint8_t c) {\n\tif (pty->next_is_verbatim) {\n\t\tpty->next_is_verbatim = 0;\n\t\tif (pty->canon_buflen < pty->canon_bufsize) {\n\t\t\tpty->canon_buffer[pty->canon_buflen] = c;\n\t\t\tpty->canon_buflen++;\n\t\t}\n\t\tif (pty->tios.c_lflag & ECHO) {\n\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) {\n\t\t\t\toutput_process(pty, '^');\n\t\t\t\toutput_process(pty, ('@'+c) % 128);\n\t\t\t} else {\n\t\t\t\toutput_process(pty, c);\n\t\t\t}\n\t\t}\n\t\treturn;\n\t}\n\tif (pty->tios.c_lflag & ISIG) {\n\t\tint sig = -1;\n\t\tif (c == pty->tios.c_cc[VINTR]) {\n\t\t\tsig = SIGINT;\n\t\t} else if (c == pty->tios.c_cc[VQUIT]) {\n\t\t\tsig = SIGQUIT;\n\t\t} else if (c == pty->tios.c_cc[VSUSP]) {\n\t\t\tsig = SIGTSTP;\n\t\t}\n\t\t/* VSUSP */\n\t\tif (sig != -1) {\n\t\t\tif (pty->tios.c_lflag & ECHO) {\n\t\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) {\n\t\t\t\t\toutput_process(pty, '^');\n\t\t\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t\t\t} else {\n\t\t\t\t\toutput_process(pty, c);\n\t\t\t\t}\n\t\t\t}\n\t\t\tclear_input_buffer(pty);\n\t\t\tif (pty->fg_proc) {\n\t\t\t\tgroup_send_signal(pty->fg_proc, sig, 1);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t}\n#if 0\n\tif (pty->tios.c_lflag & IXON ) {\n\t\t/* VSTOP, VSTART */\n\t}\n#endif\n\n\t/* ISTRIP: Strip eighth bit */\n\tif (pty->tios.c_iflag & ISTRIP) {\n\t\tc &= 0x7F;\n\t}\n\n\t/* IGNCR: Ignore carriage return. */\n\tif ((pty->tios.c_iflag & IGNCR) && c == '\\r') {\n\t\treturn;\n\t}\n\n\tif ((pty->tios.c_iflag & INLCR) && c == '\\n') {\n\t\t/* INLCR: Translate NL to CR. */\n\t\tc = '\\r';\n\t} else if ((pty->tios.c_iflag & ICRNL) && c == '\\r') {\n\t\t/* ICRNL: Convert carriage return. */\n\t\tc = '\\n';\n\t}\n\n\tif ((pty->tios.c_iflag & IUCLC) && (c >= 'A' && c <= 'Z')) {\n\t\tc = c - 'A' + 'a';\n\t}\n\n\tif (pty->tios.c_lflag & ICANON) {\n\n\t\tif (c == pty->tios.c_cc[VLNEXT] && (pty->tios.c_lflag & IEXTEN)) {\n\t\t\tpty->next_is_verbatim = 1;\n\t\t\toutput_process(pty, '^');\n\t\t\toutput_process(pty, '\\010');\n\t\t\treturn;\n\t\t}\n\n\t\tif (c == pty->tios.c_cc[VKILL]) {\n\t\t\twhile (pty->canon_buflen > 0) {\n\t\t\t\terase_one(pty, pty->tios.c_lflag & ECHOK);\n\t\t\t}\n\t\t\tif ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOK)) {\n\t\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) {\n\t\t\t\t\toutput_process(pty, '^');\n\t\t\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t\t\t} else {\n\t\t\t\t\toutput_process(pty, c);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (c == pty->tios.c_cc[VERASE]) {\n\t\t\t/* Backspace */\n\t\t\terase_one(pty, pty->tios.c_lflag & ECHOE);\n\t\t\tif ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) {\n\t\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) {\n\t\t\t\t\toutput_process(pty, '^');\n\t\t\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t\t\t} else {\n\t\t\t\t\toutput_process(pty, c);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (c == pty->tios.c_cc[VWERASE] && (pty->tios.c_lflag & IEXTEN)) {\n\t\t\twhile (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] == ' ') {\n\t\t\t\terase_one(pty, pty->tios.c_lflag & ECHOE);\n\t\t\t}\n\t\t\twhile (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] != ' ') {\n\t\t\t\terase_one(pty, pty->tios.c_lflag & ECHOE);\n\t\t\t}\n\t\t\tif ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) {\n\t\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) {\n\t\t\t\t\toutput_process(pty, '^');\n\t\t\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t\t\t} else {\n\t\t\t\t\toutput_process(pty, c);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (c == pty->tios.c_cc[VEOF]) {\n\t\t\tif (pty->canon_buflen) {\n\t\t\t\tdump_input_buffer(pty);\n\t\t\t} else {\n\t\t\t\tring_buffer_eof(pty->in);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (pty->canon_buflen < pty->canon_bufsize) {\n\t\t\tpty->canon_buffer[pty->canon_buflen] = c;\n\t\t\tpty->canon_buflen++;\n\t\t}\n\t\tif (pty->tios.c_lflag & ECHO) {\n\t\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c) && c != '\\n') {\n\t\t\t\toutput_process(pty, '^');\n\t\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t\t} else {\n\t\t\t\toutput_process(pty, c);\n\t\t\t}\n\t\t}\n\t\tif (c == '\\n' || (pty->tios.c_cc[VEOL] && c == pty->tios.c_cc[VEOL])) {\n\t\t\tif (!(pty->tios.c_lflag & ECHO) && (pty->tios.c_lflag & ECHONL)) {\n\t\t\t\toutput_process(pty, c);\n\t\t\t}\n\t\t\tpty->canon_buffer[pty->canon_buflen-1] = c;\n\t\t\tdump_input_buffer(pty);\n\t\t\treturn;\n\t\t}\n\t\treturn;\n\t} else if (pty->tios.c_lflag & ECHO) {\n\t\tif ((pty->tios.c_lflag & ECHOCTL) && is_control(c) && c != '\\n') {\n\t\t\toutput_process(pty, '^');\n\t\t\toutput_process(pty, ('@' + c) % 128);\n\t\t} else {\n\t\t\toutput_process(pty, c);\n\t\t}\n\t}\n\tIN(c);\n}\n\nstatic void tty_fill_name(pty_t * pty, char * out) {\n\t((char*)out)[0] = '\\0';\n\tsnprintf((char*)out, 100, \"/dev/pts/%zd\", pty->name);\n}\n\nint pty_ioctl(pty_t * pty, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase IOCTLDTYPE:\n\t\t\t/*\n\t\t\t * This is a special toaru-specific call to get a simple\n\t\t\t * integer that describes the kind of device this is.\n\t\t\t * It's more specific than just \"character device\" or \"file\",\n\t\t\t * but for here we just need to say we're a TTY.\n\t\t\t */\n\t\t\treturn IOCTL_DTYPE_TTY;\n\t\tcase IOCTLTTYNAME:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tpty->fill_name(pty, argp);\n\t\t\treturn 0;\n\t\tcase IOCTLTTYLOGIN:\n\t\t\t/* Set the user id of the login user */\n\t\t\tif (this_core->current_process->user != 0) return -EPERM;\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tpty->slave->uid = *(int*)argp;\n\t\t\tpty->master->uid = *(int*)argp;\n\t\t\treturn 0;\n\t\tcase TIOCSWINSZ:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tmemcpy(&pty->size, argp, sizeof(struct winsize));\n\t\t\tif (pty->fg_proc) {\n\t\t\t\tgroup_send_signal(pty->fg_proc, SIGWINCH, 1);\n\t\t\t}\n\t\t\treturn 0;\n\t\tcase TIOCGWINSZ:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tmemcpy(argp, &pty->size, sizeof(struct winsize));\n\t\t\treturn 0;\n\t\tcase TCGETS:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tmemcpy(argp, &pty->tios, sizeof(struct termios));\n\t\t\treturn 0;\n\t\tcase TIOCSPGRP:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\tpty->fg_proc = *(pid_t *)argp;\n\t\t\treturn 0;\n\t\tcase TIOCGPGRP:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\t*(pid_t *)argp = pty->fg_proc;\n\t\t\treturn 0;\n\t\tcase TIOCSCTTY:\n\t\t\t/* If this is already the control session, quietly ignore. */\n\t\t\tif (this_core->current_process->session == this_core->current_process->id &&\n\t\t\t\tpty->ct_proc == this_core->current_process->session) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\t/* If we aren't a session leader, we can't do this. */\n\t\t\tif (this_core->current_process->session != this_core->current_process->id) {\n\t\t\t\treturn -EPERM;\n\t\t\t}\n\t\t\t/* If there's already a control session, only root can steal control, and only if *argp is 1\n\t\t\t * (on Linux, that's \"if argp is 1\", but we kinda messed this up by checking ioctl argp stuff\n\t\t\t * for bounds validity in the system call layer, so instead we use a pointer to 1... */\n\t\t\tif (pty->ct_proc && (!argp || (*(int*)argp != 1) || this_core->current_process->user != 0)) {\n\t\t\t\treturn -EPERM;\n\t\t\t}\n\t\t\tpty->ct_proc = this_core->current_process->session;\n\t\t\treturn 0;\n\t\tcase TCSETS:\n\t\tcase TCSETSW:\n\t\t\tif (!argp) return -EINVAL;\n\t\t\tvalidate(argp);\n\t\t\t/* TODO wait on output for SETSW */\n\t\t\tif (!(((struct termios *)argp)->c_lflag & ICANON) && (pty->tios.c_lflag & ICANON)) {\n\t\t\t\t/* Switch out of canonical mode, the dump the input buffer */\n\t\t\t\tdump_input_buffer(pty);\n\t\t\t}\n\t\t\tgoto tcset_common;\n\t\tcase TCSETSF:\n\t\t\tclear_input_buffer(pty);\n\t\t\tring_buffer_discard(pty->in);\n\t\ttcset_common:\n\t\t\tmemcpy(&pty->tios, argp, sizeof(struct termios));\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nssize_t  read_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tpty_t * pty = (pty_t *)node->device;\n\n\t/* Standard pipe read */\n\treturn ring_buffer_read(pty->out, size, buffer);\n}\nssize_t write_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tpty_t * pty = (pty_t *)node->device;\n\n\tsize_t l = 0;\n\tfor (uint8_t * c = buffer; l < size; ++c, ++l) {\n\t\tinput_process(pty, *c);\n\t}\n\n\treturn l;\n}\nvoid      open_pty_master(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\nvoid     close_pty_master(fs_node_t * node) {\n\treturn;\n}\n\nstatic int ignoring(int sig) {\n\tif (this_core->current_process->blocked_signals & (1ULL << sig)) return 1;\n\tif (this_core->current_process->signals[sig].handler == 1) return 1;\n\treturn 0;\n}\n\nssize_t read_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tpty_t * pty = (pty_t *)node->device;\n\n\t/* If this process *is* part of this tty's session, but is NOT in the foreground job\n\t * and it tries to read from the TTY, then send it SIGTTIN - but only if it's not\n\t * ignoring it. If it is ignoring, make the write fail with EIO. */\n\tif (pty->ct_proc == this_core->current_process->session && pty->fg_proc && this_core->current_process->job != pty->fg_proc) {\n\t\tif (ignoring(SIGTTIN)) return -EIO;\n\t\tgroup_send_signal(this_core->current_process->job, SIGTTIN, 1);\n\t\treturn -ERESTARTSYS;\n\t}\n\n\tif (pty->tios.c_lflag & ICANON) {\n\t\treturn ring_buffer_read(pty->in, size, buffer);\n\t} else {\n\t\tif (pty->tios.c_cc[VMIN] == 0) {\n\t\t\treturn ring_buffer_read(pty->in, size, buffer);\n\t\t} else {\n\t\t\tssize_t c = 0;\n\t\t\tssize_t vmin = MIN(pty->tios.c_cc[VMIN], size);\n\t\t\twhile (c < vmin) {\n\t\t\t\tssize_t r = ring_buffer_read(pty->in, size - c, buffer + c);\n\t\t\t\tif (r < 0) return c ? c : r;\n\t\t\t\tc += r;\n\t\t\t}\n\t\t\treturn c;\n\t\t}\n\t}\n}\n\nssize_t write_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tpty_t * pty = (pty_t *)node->device;\n\n\tif (pty->tios.c_lflag & TOSTOP) {\n\t\t/* If TOSTOP is enabled and this process *is* part of this tty's session but\n\t\t * is NOT in the foreground job, then send the whole job SIGTTOU - but only\n\t\t * if we weren't ignoring SIGTTOU ourselves. If we were ignoring it, then\n\t\t * the write can continue without issue. */\n\t\tif (pty->ct_proc == this_core->current_process->session && pty->fg_proc && this_core->current_process->job != pty->fg_proc) {\n\t\t\tif (!ignoring(SIGTTOU)) {\n\t\t\t\tgroup_send_signal(this_core->current_process->job, SIGTTOU, 1);\n\t\t\t\treturn -ERESTARTSYS;\n\t\t\t}\n\t\t}\n\t}\n\n\tsize_t l = 0;\n\tfor (uint8_t * c = buffer; l < size; ++c, ++l) {\n\t\toutput_process_slave(pty, *c);\n\t}\n\n\treturn l;\n}\nvoid      open_pty_slave(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\nvoid     close_pty_slave(fs_node_t * node) {\n\tpty_t * pty = (pty_t *)node->device;\n\n\tif (pty->name) {\n\t\thashmap_remove(_pty_index, (void*)pty->name);\n\t}\n\n\treturn;\n}\n\n/*\n * These are separate functions just in case I ever feel the need to do\n * things differently in the slave or master.\n */\nint ioctl_pty_master(fs_node_t * node, unsigned long request, void * argp) {\n\tpty_t * pty = (pty_t *)node->device;\n\treturn pty_ioctl(pty, request, argp);\n}\n\nint ioctl_pty_slave(fs_node_t * node, unsigned long request, void * argp) {\n\tpty_t * pty = (pty_t *)node->device;\n\treturn pty_ioctl(pty, request, argp);\n}\n\nint pty_available_input(fs_node_t * node) {\n\tpty_t * pty = (pty_t *)node->device;\n\treturn ring_buffer_unread(pty->in);\n}\n\nint pty_available_output(fs_node_t * node) {\n\tpty_t * pty = (pty_t *)node->device;\n\treturn ring_buffer_unread(pty->out);\n}\n\nstatic int check_pty_master(fs_node_t * node) {\n\tpty_t * pty = (pty_t *)node->device;\n\tif (ring_buffer_unread(pty->out) > 0) {\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\nstatic int check_pty_slave(fs_node_t * node) {\n\tpty_t * pty = (pty_t *)node->device;\n\tif (ring_buffer_unread(pty->in) > 0) {\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\nstatic int wait_pty_master(fs_node_t * node, void * process) {\n\tpty_t * pty = (pty_t *)node->device;\n\tring_buffer_select_wait(pty->out, process);\n\treturn 0;\n}\n\nstatic int wait_pty_slave(fs_node_t * node, void * process) {\n\tpty_t * pty = (pty_t *)node->device;\n\tring_buffer_select_wait(pty->in, process);\n\treturn 0;\n}\n\nfs_node_t * pty_master_create(pty_t * pty) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\n\tfnode->name[0] = '\\0';\n\tsnprintf(fnode->name, 100, \"pty master\");\n\tfnode->uid   = this_core->current_process->user;\n\tfnode->gid   = this_core->current_process->user_group;\n\tfnode->mask  = 0666;\n\tfnode->flags = FS_PIPE;\n\tfnode->read  =  read_pty_master;\n\tfnode->write = write_pty_master;\n\tfnode->open  =  open_pty_master;\n\tfnode->close = close_pty_master;\n\tfnode->selectcheck = check_pty_master;\n\tfnode->selectwait  = wait_pty_master;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl = ioctl_pty_master;\n\tfnode->get_size = pty_available_output;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\n\tfnode->device = pty;\n\n\treturn fnode;\n}\n\nfs_node_t * pty_slave_create(pty_t * pty) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\n\tfnode->name[0] = '\\0';\n\tsnprintf(fnode->name, 100, \"pty slave\");\n\tfnode->uid   = this_core->current_process->user;\n\tfnode->gid   = this_core->current_process->user_group;\n\tfnode->mask  = 0620;\n\tfnode->flags = FS_CHARDEVICE;\n\tfnode->read  =  read_pty_slave;\n\tfnode->write = write_pty_slave;\n\tfnode->open  =  open_pty_slave;\n\tfnode->close = close_pty_slave;\n\tfnode->selectcheck = check_pty_slave;\n\tfnode->selectwait  = wait_pty_slave;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl = ioctl_pty_slave;\n\tfnode->get_size = pty_available_input;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\n\tfnode->device = pty;\n\n\treturn fnode;\n}\n\nstatic int isatty(fs_node_t * node) {\n\tif (!node) return 0;\n\tif (!node->ioctl) return 0;\n\treturn ioctl_fs(node, IOCTLDTYPE, NULL) == IOCTL_DTYPE_TTY;\n}\n\nstatic ssize_t readlink_dev_tty(fs_node_t * node, char * buf, size_t size) {\n\tpty_t * pty = NULL;\n\n\tfor (unsigned int i = 0; i < ((this_core->current_process->fds->length < 3) ? this_core->current_process->fds->length : 3); ++i) {\n\t\tif (isatty(this_core->current_process->fds->entries[i])) {\n\t\t\tpty = (pty_t *)this_core->current_process->fds->entries[i]->device;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tchar tmp[30];\n\tsize_t req;\n\tif (!pty) {\n\t\tsnprintf(tmp, 100, \"/dev/null\");\n\t} else {\n\t\tpty->fill_name(pty, tmp);\n\t}\n\n\treq = strlen(tmp) + 1;\n\n\tif (size < req) {\n\t\tmemcpy(buf, tmp, size);\n\t\tbuf[size-1] = '\\0';\n\t\treturn size-1;\n\t}\n\n\tif (size > req) size = req;\n\n\tmemcpy(buf, tmp, size);\n\treturn size-1;\n}\n\nstatic fs_node_t * create_dev_tty(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"tty\");\n\tfnode->mask = 0777;\n\tfnode->uid  = 0;\n\tfnode->gid  = 0;\n\tfnode->flags   = FS_FILE | FS_SYMLINK;\n\tfnode->readlink = readlink_dev_tty;\n\tfnode->length  = 1;\n\tfnode->nlink   = 1;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\treturn fnode;\n}\n\nstatic struct dirent * readdir_pty(fs_node_t *node, unsigned long index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tindex -= 2;\n\n\tpty_t * out_pty = NULL;\n\tlist_t * values = hashmap_values(_pty_index);\n\tforeach(node, values) {\n\t\tif (index == 0) {\n\t\t\tout_pty = node->value;\n\t\t\tbreak;\n\t\t}\n\t\tindex--;\n\t}\n\tlist_free(values);\n\n\tif (out_pty) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = out_pty->name;\n\t\tout->d_name[0] = '\\0';\n\t\tsnprintf(out->d_name, 100, \"%zd\", out_pty->name);\n\t\treturn out;\n\t} else {\n\t\treturn NULL;\n\t}\n}\n\nstatic fs_node_t * finddir_pty(fs_node_t * node, char * name) {\n\tif (!name) return NULL;\n\tif (strlen(name) < 1) return NULL;\n\n\tintptr_t c = 0;\n\tfor (intptr_t i = 0; name[i]; ++i) {\n\t\tif (name[i] < '0' || name[i] > '9') {\n\t\t\treturn NULL;\n\t\t}\n\t\tc = c * 10 + name[i] - '0';\n\t}\n\n\tpty_t * _pty = hashmap_get(_pty_index, (void*)c);\n\n\tif (!_pty) {\n\t\treturn NULL;\n\t}\n\n\treturn _pty->slave;\n}\n\nstatic fs_node_t * create_pty_dir(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"pty\");\n\tfnode->mask = 0555;\n\tfnode->uid  = 0;\n\tfnode->gid  = 0;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->read    = NULL;\n\tfnode->write   = NULL;\n\tfnode->open    = NULL;\n\tfnode->close   = NULL;\n\tfnode->readdir = readdir_pty;\n\tfnode->finddir = finddir_pty;\n\tfnode->nlink   = 1;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\treturn fnode;\n}\n\nvoid pty_install(void) {\n\t_pty_index = hashmap_create_int(10);\n\t_pty_dir   = create_pty_dir();\n\t_dev_tty   = create_dev_tty();\n\n\tvfs_mount(\"/dev/pts\", _pty_dir, \"pts\", \"\");\n\tvfs_mount(\"/dev/tty\", _dev_tty, \"devtty\", \"\");\n}\n\npty_t * pty_new(struct winsize * size, int index) {\n\n\tif (!_pty_index) {\n\t\tpty_install();\n\t}\n\n\tpty_t * pty = malloc(sizeof(pty_t));\n\n\tpty->next_is_verbatim = 0;\n\n\t/* stdin linkage; characters from terminal → PTY slave */\n\tpty->in  = ring_buffer_create(TTY_BUFFER_SIZE);\n\tpty->out = ring_buffer_create(TTY_BUFFER_SIZE);\n\n\t/* Master endpoint - writes go to stdin, reads come from stdout */\n\tpty->master = pty_master_create(pty);\n\n\t/* Slave endpoint, reads come from stdin, writes go to stdout */\n\tpty->slave  = pty_slave_create(pty);\n\n\t/* tty name */\n\tpty->name      = index;\n\tpty->fill_name = tty_fill_name;\n\n\tpty->write_in = pty_write_in;\n\tpty->write_out = pty_write_out;\n\n\tif (index) {\n\t\thashmap_set(_pty_index, (void*)pty->name, pty);\n\t}\n\n\tif (size) {\n\t\tmemcpy(&pty->size, size, sizeof(struct winsize));\n\t} else {\n\t\t/* Sane defaults */\n\t\tpty->size.ws_row = 25;\n\t\tpty->size.ws_col = 80;\n\t}\n\n\t/* Controlling and foreground processes are set to 0 by default */\n\tpty->ct_proc = 0;\n\tpty->fg_proc = 0;\n\n\tpty->tios.c_iflag = ICRNL | BRKINT;\n\tpty->tios.c_oflag = ONLCR | OPOST;\n\tpty->tios.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN | ECHOCTL;\n\tpty->tios.c_cflag = CREAD | CS8 | B38400;\n\tpty->tios.c_cc[VEOF]   =  4; /* ^D */\n\tpty->tios.c_cc[VEOL]   =  0; /* Not set */\n\tpty->tios.c_cc[VERASE] = 0x7f; /* ^? */\n\tpty->tios.c_cc[VINTR]  =  3; /* ^C */\n\tpty->tios.c_cc[VKILL]  = 21; /* ^U */\n\tpty->tios.c_cc[VMIN]   =  1;\n\tpty->tios.c_cc[VQUIT]  = 28; /* ^\\ */\n\tpty->tios.c_cc[VSTART] = 17; /* ^Q */\n\tpty->tios.c_cc[VSTOP]  = 19; /* ^S */\n\tpty->tios.c_cc[VSUSP] = 26; /* ^Z */\n\tpty->tios.c_cc[VTIME]  =  0;\n\tpty->tios.c_cc[VLNEXT] = 22; /* ^V */\n\tpty->tios.c_cc[VWERASE] = 23; /* ^W */\n\n\tpty->canon_buffer  = malloc(TTY_BUFFER_SIZE);\n\tpty->canon_bufsize = TTY_BUFFER_SIZE-2;\n\tpty->canon_buflen  = 0;\n\n\treturn pty;\n}\n\nint pty_create(void *size, fs_node_t ** fs_master, fs_node_t ** fs_slave) {\n\tpty_t * pty = pty_new(size, ++_pty_counter);\n\n\t*fs_master = pty->master;\n\t*fs_slave  = pty->slave;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "kernel/vfs/unixpipe.c",
    "content": "/**\n * @file kernel/vfs/unixpipe.c\n * @brief Implementation of Unix pipes.\n *\n * Provides for unidirectional communication between processes.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/printf.h>\n#include <kernel/pipe.h>\n#include <kernel/string.h>\n#include <kernel/ringbuffer.h>\n#include <kernel/process.h>\n#include <kernel/signal.h>\n\n#include <sys/signal_defs.h>\n#include <sys/ioctl.h>\n\n#define UNIX_PIPE_BUFFER 4096\n\nstruct unix_pipe {\n\tfs_node_t * read_end;\n\tfs_node_t * write_end;\n\n\tvolatile int read_closed;\n\tvolatile int write_closed;\n\n\tring_buffer_t * buffer;\n};\n\nstatic void close_complete(struct unix_pipe * self) {\n\tring_buffer_destroy(self->buffer);\n}\n\nstatic ssize_t read_unixpipe(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct unix_pipe * self = node->device;\n\tif (self->write_closed && !ring_buffer_unread(self->buffer)) {\n\t\treturn 0;\n\t}\n\treturn ring_buffer_read(self->buffer, size, buffer);\n}\n\nstatic ssize_t write_unixpipe(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct unix_pipe * self = node->device;\n\tif (self->read_closed) {\n\t\tsend_signal(this_core->current_process->id, SIGPIPE, 1);\n\t\treturn -EPIPE;\n\t}\n\treturn ring_buffer_write(self->buffer, size, buffer);\n}\n\nstatic void close_read_pipe(fs_node_t * node) {\n\tstruct unix_pipe * self = node->device;\n\n\tspin_lock(self->buffer->lock);\n\tself->read_closed = 1;\n\tif (!self->write_closed) {\n\t\tring_buffer_interrupt(self->buffer);\n\t}\n\tspin_unlock(self->buffer->lock);\n}\n\nstatic void close_write_pipe(fs_node_t * node) {\n\tstruct unix_pipe * self = node->device;\n\n\tspin_lock(self->buffer->lock);\n\tself->write_closed = 1;\n\tif (!self->read_closed) {\n\t\tring_buffer_interrupt(self->buffer);\n\t\tif (!ring_buffer_unread(self->buffer)) {\n\t\t\tring_buffer_alert_waiters(self->buffer);\n\t\t}\n\t}\n\tspin_unlock(self->buffer->lock);\n}\n\nstatic int check_pipe(fs_node_t * node) {\n\tstruct unix_pipe * self = node->device;\n\tif (ring_buffer_unread(self->buffer) > 0) {\n\t\treturn 0;\n\t}\n\tif (self->write_closed) return 0;\n\treturn 1;\n}\n\nstatic int wait_pipe(fs_node_t * node, void * process) {\n\tstruct unix_pipe * self = node->device;\n\tring_buffer_select_wait(self->buffer, process);\n\treturn 0;\n}\n\n\nint make_unix_pipe(fs_node_t ** pipes) {\n\tsize_t size = UNIX_PIPE_BUFFER;\n\n\tpipes[0] = malloc(sizeof(fs_node_t));\n\tpipes[1] = malloc(sizeof(fs_node_t));\n\n\tmemset(pipes[0], 0, sizeof(fs_node_t));\n\tmemset(pipes[1], 0, sizeof(fs_node_t));\n\n\tsnprintf(pipes[0]->name, 100, \"[pipe:read]\");\n\tsnprintf(pipes[1]->name, 100, \"[pipe:write]\");\n\n\tpipes[0]->mask = 0666;\n\tpipes[1]->mask = 0666;\n\n\tpipes[0]->flags = FS_PIPE;\n\tpipes[1]->flags = FS_PIPE;\n\n\tpipes[0]->read = read_unixpipe;\n\tpipes[1]->write = write_unixpipe;\n\n\tpipes[0]->close = close_read_pipe;\n\tpipes[1]->close = close_write_pipe;\n\n\t/* Read end can wait */\n\tpipes[0]->selectcheck = check_pipe;\n\tpipes[0]->selectwait = wait_pipe;\n\n\tstruct unix_pipe * internals = malloc(sizeof(struct unix_pipe));\n\tinternals->read_end = pipes[0];\n\tinternals->write_end = pipes[1];\n\tinternals->read_closed = 0;\n\tinternals->write_closed = 0;\n\tinternals->buffer = ring_buffer_create(size);\n\n\tpipes[0]->device = internals;\n\tpipes[1]->device = internals;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "kernel/vfs/vfs.c",
    "content": "/**\n * @file  kernel/vfs/vfs.c\n * @brief Virtual file system.\n *\n * Provides the high-level generic operations for the VFS.\n *\n * @warning Here be dragons\n *\n * This VFS implementation comes from toaru32. It has a lot of weird\n * quirks and doesn't quite work like a typical Unix VFS would.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2021 K. Lange\n * Copyright (C) 2014 Lioncash\n * Copyright (C) 2012 Tianyi Wang\n */\n#include <stddef.h>\n#include <stdint.h>\n#include <errno.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/vfs.h>\n#include <kernel/time.h>\n#include <kernel/process.h>\n\n#include <kernel/list.h>\n#include <kernel/hashmap.h>\n#include <kernel/tree.h>\n#include <kernel/spinlock.h>\n\n#define MAX_SYMLINK_DEPTH 8\n#define MAX_SYMLINK_SIZE 4096\n\ntree_t    * fs_tree = NULL; /* File system mountpoint tree */\nfs_node_t * fs_root = NULL; /* Pointer to the root mount fs_node (must be some form of filesystem, even ramdisk) */\n\nhashmap_t * fs_types = NULL;\n\n#define MIN(l,r) ((l) < (r) ? (l) : (r))\n#define MAX(l,r) ((l) > (r) ? (l) : (r))\n\n#define debug_print(x, ...) do { if (0) {printf(\"vfs.c [%s] \", #x); printf(__VA_ARGS__); printf(\"\\n\"); } } while (0)\n\nstatic int cb_printf(void * user, char c) {\n\tfs_node_t * f = user;\n\twrite_fs(f, 0, 1, (uint8_t*)&c);\n\treturn 0;\n}\n\n/**\n * @brief Write printf output to a simple file node.\n *\n * The file node, f, must be a simple character device that\n * allows repeated writes of a single byte without an incrementing\n * offset, such as a serial port or TTY.\n */\nint fprintf(fs_node_t * f, const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = xvasprintf(cb_printf, f, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n\nint has_permission(fs_node_t * node, int permission_bit) {\n\tif (!node) return 0;\n\n\tif (this_core->current_process->user == 0 && permission_bit != 01) { /* even root needs exec to exec */\n\t\treturn 1;\n\t}\n\n\tuint64_t permissions = node->mask;\n\n\tuint8_t my_permissions = (permissions) & 07;\n\tuint8_t user_perm  = (permissions >> 6) & 07;\n\tuint8_t group_perm = (permissions >> 3) & 07;\n\n\tif (this_core->current_process->user == node->uid) my_permissions |= user_perm;\n\tif (this_core->current_process->user_group == node->gid) my_permissions |= group_perm;\n\telse if (this_core->current_process->supplementary_group_count) {\n\t\tfor (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) {\n\t\t\tif (this_core->current_process->supplementary_group_list[i] == node->gid) {\n\t\t\t\tmy_permissions |= group_perm;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn (permission_bit & my_permissions);\n}\n\nstatic struct dirent * readdir_mapper(fs_node_t *node, unsigned long index) {\n\ttree_node_t * d = (tree_node_t *)node->device;\n\n\tif (!d) return NULL;\n\n\tif (index == 0) {\n\t\tstruct dirent * dir = malloc(sizeof(struct dirent));\n\t\tstrcpy(dir->d_name, \".\");\n\t\tdir->d_ino = 0;\n\t\treturn dir;\n\t} else if (index == 1) {\n\t\tstruct dirent * dir = malloc(sizeof(struct dirent));\n\t\tstrcpy(dir->d_name, \"..\");\n\t\tdir->d_ino = 1;\n\t\treturn dir;\n\t}\n\n\tindex -= 2;\n\tunsigned long i = 0;\n\tforeach(child, d->children) {\n\t\tif (i == index) {\n\t\t\t/* Recursively print the children */\n\t\t\ttree_node_t * tchild = (tree_node_t *)child->value;\n\t\t\tstruct vfs_entry * n = (struct vfs_entry *)tchild->value;\n\t\t\tstruct dirent * dir = malloc(sizeof(struct dirent));\n\n\t\t\tsize_t len = strlen(n->name) + 1;\n\t\t\tmemcpy(&dir->d_name, n->name, MIN(256, len));\n\t\t\tdir->d_ino = i;\n\t\t\treturn dir;\n\t\t}\n\t\t++i;\n\t}\n\n\treturn NULL;\n}\n\nstatic fs_node_t * vfs_mapper(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->mask    = 0555;\n\tfnode->flags   = FS_DIRECTORY;\n\tfnode->readdir = readdir_mapper;\n\tfnode->ctime   = now();\n\tfnode->mtime   = now();\n\tfnode->atime   = now();\n\treturn fnode;\n}\n\n/**\n * @brief Check if a read from this file would block.\n */\nint selectcheck_fs(fs_node_t * node) {\n\tif (!node) return -ENOENT;\n\n\tif (node->selectcheck) {\n\t\treturn node->selectcheck(node);\n\t}\n\n\treturn -EINVAL;\n}\n\n/**\n * @brief Inform a node that it should alert the current_process.\n */\nint selectwait_fs(fs_node_t * node, void * process) {\n\tif (!node) return -ENOENT;\n\n\tif (node->selectwait) {\n\t\treturn node->selectwait(node, process);\n\t}\n\n\treturn -EINVAL;\n}\n\n/**\n * @brief Read a file system node based on its underlying type.\n *\n * @param node    Node to read\n * @param offset  Offset into the node data to read from\n * @param size    How much data to read (in bytes)\n * @param buffer  A buffer to copy of the read data into\n * @returns Bytes read\n */\nssize_t read_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tif (!node) return -ENOENT;\n\tif (node->read) {\n\t\treturn node->read(node, offset, size, buffer);\n\t} else {\n\t\tif (node->flags & FS_DIRECTORY) return -EISDIR;\n\t\treturn -EINVAL;\n\t}\n}\n\n/**\n * @brief Write a file system node based on its underlying type.\n *\n * @param node    Node to write to\n * @param offset  Offset into the node data to write to\n * @param size    How much data to write (in bytes)\n * @param buffer  A buffer to copy from\n * @returns Bytes written\n */\nssize_t write_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tif (!node) return -ENOENT;\n\tif (node->write) {\n\t\treturn node->write(node, offset, size, buffer);\n\t} else {\n\t\tif (node->flags & FS_DIRECTORY) return -EISDIR;\n\t\treturn -EROFS;\n\t}\n}\n\n/**\n * @brief set the size of a file to 9\n *\n * @param node File to resize\n */\nint truncate_fs(fs_node_t * node, size_t size) {\n\tif (!node) return -ENOENT;\n\n\tif (node->truncate) {\n\t\treturn node->truncate(node, size);\n\t}\n\n\treturn -EINVAL;\n}\n\n//volatile uint8_t tmp_refcount_lock = 0;\nstatic spin_lock_t tmp_refcount_lock = { 0 };\n\nvoid vfs_lock(fs_node_t * node) {\n\tspin_lock(tmp_refcount_lock);\n\tnode->refcount = -1;\n\tspin_unlock(tmp_refcount_lock);\n}\n\n/**\n * @brief Open a file system node.\n *\n * @param node  Node to open\n * @param flags Same as open, specifies read/write/append/truncate\n */\nvoid open_fs(fs_node_t *node, unsigned int flags) {\n\n\tif (!node) return;\n\n\tif (node->refcount >= 0) {\n\t\tspin_lock(tmp_refcount_lock);\n\t\tnode->refcount++;\n\t\tspin_unlock(tmp_refcount_lock);\n\t}\n\n\tif (node->open) {\n\t\tnode->open(node, flags);\n\t}\n}\n\n/**\n * @brief Close a file system node\n *\n * @param node Node to close\n */\nvoid close_fs(fs_node_t *node) {\n\t//assert(node != fs_root && \"Attempted to close the filesystem root. kablooey\");\n\n\tif (!node) {\n\t\tdebug_print(WARNING, \"Double close? This isn't an fs_node.\");\n\t\treturn;\n\t}\n\n\tif (node->refcount == -1) return;\n\n\tspin_lock(tmp_refcount_lock);\n\tnode->refcount--;\n\tif (node->refcount == 0) {\n\t\tdebug_print(NOTICE, \"Node refcount [%s] is now 0: %ld\", node->name, node->refcount);\n\n\t\tif (node->close) {\n\t\t\tnode->close(node);\n\t\t}\n\n\t\tfree(node);\n\t}\n\tspin_unlock(tmp_refcount_lock);\n}\n\n/**\n * @brief Change permissions for a file system node.\n *\n * @param node Node to change permissions for\n * @param mode New mode bits\n */\nint chmod_fs(fs_node_t *node, mode_t mode) {\n\tif (node->chmod) {\n\t\treturn node->chmod(node, mode);\n\t}\n\treturn 0;\n}\n\n/**\n * @brief Change ownership for a file system node.\n */\nint chown_fs(fs_node_t *node, uid_t uid, gid_t gid) {\n\tif (node->chown) {\n\t\treturn node->chown(node, uid, gid);\n\t}\n\treturn 0;\n}\n\n/**\n * @brief Read a directory for the requested index\n *\n * @param node  Directory to read\n * @param index Offset to look for\n * @returns A dirent object.\n */\nstruct dirent *readdir_fs(fs_node_t *node, unsigned long index) {\n\tif (!node) return NULL;\n\n\tif ((node->flags & FS_DIRECTORY) && node->readdir) {\n\t\treturn node->readdir(node, index);\n\t} else {\n\t\treturn NULL;\n\t}\n}\n\n/**\n * @brief Find the requested file in the directory and return an fs_node for it\n *\n * @param node Directory to search\n * @param name File to look for\n * @returns An fs_node that the caller can free\n */\nfs_node_t *finddir_fs(fs_node_t *node, char *name) {\n\tif (!node) return NULL;\n\n\tif ((node->flags & FS_DIRECTORY) && node->finddir) {\n\t\treturn node->finddir(node, name);\n\t} else {\n\t\tdebug_print(WARNING, \"Node passed to finddir_fs isn't a directory!\");\n\t\tdebug_print(WARNING, \"node = %p, name = %s\", (void*)node, name);\n\t\treturn NULL;\n\t}\n}\n\n/**\n * @brief Control Device\n *\n * @param node    Device node to control\n * @param request Device-specific request code\n * @param argp    Depends on `request`\n * @returns Depends on `request`\n */\nint ioctl_fs(fs_node_t *node, unsigned long request, void * argp) {\n\tif (!node) return -ENOENT;\n\n\tif (node->ioctl) {\n\t\treturn node->ioctl(node, request, argp);\n\t} else {\n\t\treturn -EINVAL;\n\t}\n}\n\nfs_node_t * file_get_parent(const char * path) {\n\tchar * parent_path = malloc(strlen(path) + 5);\n\tsnprintf(parent_path, strlen(path) + 4, \"%s/..\", path);\n\tfs_node_t * parent  = kopen(parent_path, 0);\n\tfree(parent_path);\n\treturn parent;\n}\n\nstatic const char * fs_basename(const char * path) {\n\tconst char * f_path = path + strlen(path) - 1;\n\twhile (f_path > path && *f_path == '/') {\n\t\t/* Trailing slashes */\n\t\tf_path--;\n\t}\n\twhile (f_path > path) {\n\t\tif (*f_path == '/') {\n\t\t\tf_path += 1;\n\t\t\tbreak;\n\t\t}\n\t\tf_path--;\n\t}\n\n\twhile (*f_path == '/') {\n\t\tf_path++;\n\t}\n\n\treturn f_path;\n}\n\nint rename_file_fs(const char * src, const char * dest) {\n\tif (!*src || !*dest) return -ENOENT;\n\tfs_node_t * src_parent = file_get_parent(src);\n\tif (!src_parent) return -ENOENT;\n\tfs_node_t * dest_parent = file_get_parent(dest);\n\tif (!dest_parent) { close_fs(src_parent); return -ENOENT; }\n\n\tint out = 0;\n\tif (!src_parent->mount) { out = -EROFS; goto _nope; }\n\tif (src_parent->mount != dest_parent->mount) { out = -EXDEV; goto _nope; }\n\tif (!src_parent->mount->rename) { out = -ENOTSUP; goto _nope; }\n\n\tif (!has_permission(src_parent, 02) || !has_permission(src_parent, 01)) { out = -EACCES; goto _nope; }\n\tif (!has_permission(dest_parent, 02) || !has_permission(dest_parent, 01)) { out = -EACCES; goto _nope; }\n\n\t/* Get basename of each path component */\n\tconst char * src_name = fs_basename(src);\n\tconst char * dest_name = fs_basename(dest);\n\n\tif (!*src_name || !*dest_name) return -EINVAL;\n\tif (*src_name == '/' || *dest_name == '/') return -EINVAL;\n\n\tout = src_parent->mount->rename(src_parent->mount, src_parent, src_name, dest_parent, dest_name);\n\n_nope:\n\tclose_fs(dest_parent);\n\tclose_fs(src_parent);\n\treturn out;\n}\n\n\n/*\n * XXX: The following two function should be replaced with\n *      one function to create children of directory nodes.\n *      There is no fundamental difference between a directory\n *      and a file, thus, the use of flag sets should suffice\n */\n\nint create_file_fs(char *name, mode_t permission) {\n\tfs_node_t * parent;\n\tchar *cwd = (char *)(this_core->current_process->wd_name);\n\tchar *path = canonicalize_path(cwd, name);\n\n\tchar * parent_path = malloc(strlen(path) + 5);\n\tsnprintf(parent_path, strlen(path) + 4, \"%s/..\", path);\n\n\tchar * f_path = path + strlen(path) - 1;\n\twhile (f_path > path) {\n\t\tif (*f_path == '/') {\n\t\t\tf_path += 1;\n\t\t\tbreak;\n\t\t}\n\t\tf_path--;\n\t}\n\n\twhile (*f_path == '/') {\n\t\tf_path++;\n\t}\n\n\tdebug_print(NOTICE, \"creating file %s within %s (hope these strings are good)\", f_path, parent_path);\n\n\tparent = kopen(parent_path, 0);\n\tfree(parent_path);\n\n\tif (!parent) {\n\t\tdebug_print(WARNING, \"failed to open parent\");\n\t\tfree(path);\n\t\treturn -ENOENT;\n\t}\n\n\t/* Need both exec and write on the parent to create a new entry */\n\tif (!has_permission(parent, 02) || !has_permission(parent, 01)) {\n\t\tfree(path);\n\t\tclose_fs(parent);\n\t\treturn -EACCES;\n\t}\n\n\tint ret = 0;\n\tif (parent->create) {\n\t\tret = parent->create(parent, f_path, permission);\n\t} else {\n\t\tret = -EINVAL;\n\t}\n\n\tfree(path);\n\tfree(parent);\n\n\treturn ret;\n}\n\nint unlink_fs(char * name) {\n\tfs_node_t * parent;\n\tchar *cwd = (char *)(this_core->current_process->wd_name);\n\tchar *path = canonicalize_path(cwd, name);\n\n\tchar * parent_path = malloc(strlen(path) + 5);\n\tsnprintf(parent_path, strlen(path) + 4, \"%s/..\", path);\n\n\tchar * f_path = path + strlen(path) - 1;\n\twhile (f_path > path) {\n\t\tif (*f_path == '/') {\n\t\t\tf_path += 1;\n\t\t\tbreak;\n\t\t}\n\t\tf_path--;\n\t}\n\n\twhile (*f_path == '/') {\n\t\tf_path++;\n\t}\n\n\tdebug_print(WARNING, \"unlinking file %s within %s (hope these strings are good)\", f_path, parent_path);\n\n\tparent = kopen(parent_path, 0);\n\tfree(parent_path);\n\n\tif (!parent) {\n\t\tfree(path);\n\t\treturn -ENOENT;\n\t}\n\n\tif (!has_permission(parent, 02) || !has_permission(parent, 01)) {\n\t\tfree(path);\n\t\tclose_fs(parent);\n\t\treturn -EACCES;\n\t}\n\n\tint ret = 0;\n\tif (parent->unlink) {\n\t\tret = parent->unlink(parent, f_path);\n\t} else {\n\t\tret = -EINVAL;\n\t}\n\n\tfree(path);\n\tclose_fs(parent);\n\treturn ret;\n}\n\nint mkdir_fs(char *name, mode_t permission) {\n\tfs_node_t * parent;\n\tchar *cwd = (char *)(this_core->current_process->wd_name);\n\tchar *path = canonicalize_path(cwd, name);\n\n\tif (!name || !strlen(name)) {\n\t\treturn -EINVAL;\n\t}\n\n\tchar * parent_path = malloc(strlen(path) + 5);\n\tsnprintf(parent_path, strlen(path) + 4, \"%s/..\", path);\n\n\tchar * f_path = path + strlen(path) - 1;\n\twhile (f_path > path) {\n\t\tif (*f_path == '/') {\n\t\t\tf_path += 1;\n\t\t\tbreak;\n\t\t}\n\t\tf_path--;\n\t}\n\n\twhile (*f_path == '/') {\n\t\tf_path++;\n\t}\n\n\tdebug_print(WARNING, \"creating directory %s within %s (hope these strings are good)\", f_path, parent_path);\n\n\tparent = kopen(parent_path, 0);\n\tfree(parent_path);\n\n\tif (!parent) {\n\t\tfree(path);\n\t\treturn -ENOENT;\n\t}\n\n\tif (!f_path || !strlen(f_path)) {\n\t\t/* Odd edge case with / */\n\t\treturn -EEXIST;\n\t}\n\n\t/* Permission check was moved into methods for reasons. */\n\n\tint ret = 0;\n\tif (parent->mkdir) {\n\t\tret = parent->mkdir(parent, f_path, permission);\n\t} else {\n\t\tret = -EROFS;\n\t}\n\n\tfree(path);\n\tclose_fs(parent);\n\n\treturn ret;\n}\n\nfs_node_t *clone_fs(fs_node_t *source) {\n\tif (!source) return NULL;\n\n\tif (source->refcount >= 0) {\n\t\tspin_lock(tmp_refcount_lock);\n\t\tsource->refcount++;\n\t\tspin_unlock(tmp_refcount_lock);\n\t}\n\n\treturn source;\n}\n\nint symlink_fs(char * target, char * name) {\n\tfs_node_t * parent;\n\tchar *cwd = (char *)(this_core->current_process->wd_name);\n\tchar *path = canonicalize_path(cwd, name);\n\n\tchar * parent_path = malloc(strlen(path) + 5);\n\tsnprintf(parent_path, strlen(path) + 4, \"%s/..\", path);\n\n\tchar * f_path = path + strlen(path) - 1;\n\twhile (f_path > path) {\n\t\tif (*f_path == '/') {\n\t\t\tf_path += 1;\n\t\t\tbreak;\n\t\t}\n\t\tf_path--;\n\t}\n\n\tdebug_print(NOTICE, \"creating symlink %s within %s\", f_path, parent_path);\n\n\tparent = kopen(parent_path, 0);\n\tfree(parent_path);\n\n\tif (!parent) {\n\t\tfree(path);\n\t\treturn -ENOENT;\n\t}\n\n\t/* Need both exec and write on the parent to create a new entry */\n\tif (!has_permission(parent, 02) || !has_permission(parent, 01)) {\n\t\tfree(path);\n\t\tclose_fs(parent);\n\t\treturn -EACCES;\n\t}\n\n\tint ret = 0;\n\tif (parent->symlink) {\n\t\tret = parent->symlink(parent, target, f_path);\n\t} else {\n\t\tret = -EINVAL;\n\t}\n\n\tfree(path);\n\tclose_fs(parent);\n\n\treturn ret;\n}\n\nssize_t readlink_fs(fs_node_t *node, char * buf, size_t size) {\n\tif (!node) return -ENOENT;\n\n\tif (node->readlink) {\n\t\treturn node->readlink(node, buf, size);\n\t} else {\n\t\treturn -EINVAL;\n\t}\n}\n\n\n/**\n * @brief Canonicalize a path.\n *\n * @param cwd   Current working directory\n * @param input Path to append or canonicalize on\n * @returns An absolute path string\n */\nchar *canonicalize_path(const char *cwd, const char *input) {\n\t/* This is a stack-based canonicalizer; we use a list as a stack */\n\tlist_t *out = list_create(\"vfs canonicalize_path working memory\",input);\n\n\t/*\n\t * If we have a relative path, we need to canonicalize\n\t * the working directory and insert it into the stack.\n\t */\n\tif (strlen(input) && input[0] != PATH_SEPARATOR) {\n\t\t/* Make a copy of the working directory */\n\t\tchar *path = malloc((strlen(cwd) + 1) * sizeof(char));\n\t\tmemcpy(path, cwd, strlen(cwd) + 1);\n\n\t\t/* Setup tokenizer */\n\t\tchar *pch;\n\t\tchar *save;\n\t\tpch = strtok_r(path,PATH_SEPARATOR_STRING,&save);\n\n\t\t/* Start tokenizing */\n\t\twhile (pch != NULL) {\n\t\t\t/* Make copies of the path elements */\n\t\t\tchar *s = malloc(sizeof(char) * (strlen(pch) + 1));\n\t\t\tmemcpy(s, pch, strlen(pch) + 1);\n\t\t\t/* And push them */\n\t\t\tlist_insert(out, s);\n\t\t\tpch = strtok_r(NULL,PATH_SEPARATOR_STRING,&save);\n\t\t}\n\t\tfree(path);\n\t}\n\n\t/* Similarly, we need to push the elements from the new path */\n\tchar *path = malloc((strlen(input) + 1) * sizeof(char));\n\tmemcpy(path, input, strlen(input) + 1);\n\n\t/* Initialize the tokenizer... */\n\tchar *pch;\n\tchar *save;\n\tpch = strtok_r(path,PATH_SEPARATOR_STRING,&save);\n\n\t/*\n\t * Tokenize the path, this time, taking care to properly\n\t * handle .. and . to represent up (stack pop) and current\n\t * (do nothing)\n\t */\n\twhile (pch != NULL) {\n\t\tif (!strcmp(pch,PATH_UP)) {\n\t\t\t/*\n\t\t\t * Path = ..\n\t\t\t * Pop the stack to move up a directory\n\t\t\t */\n\t\t\tnode_t * n = list_pop(out);\n\t\t\tif (n) {\n\t\t\t\tfree(n->value);\n\t\t\t\tfree(n);\n\t\t\t}\n\t\t} else if (!strcmp(pch,PATH_DOT)) {\n\t\t\t/*\n\t\t\t * Path = .\n\t\t\t * Do nothing\n\t\t\t */\n\t\t} else {\n\t\t\t/*\n\t\t\t * Regular path, push it\n\t\t\t * XXX: Path elements should be checked for existence!\n\t\t\t */\n\t\t\tchar * s = malloc(sizeof(char) * (strlen(pch) + 1));\n\t\t\tmemcpy(s, pch, strlen(pch) + 1);\n\t\t\tlist_insert(out, s);\n\t\t}\n\t\tpch = strtok_r(NULL, PATH_SEPARATOR_STRING, &save);\n\t}\n\tfree(path);\n\n\t/* Calculate the size of the path string */\n\tsize_t size = 0;\n\tforeach(item, out) {\n\t\t/* Helpful use of our foreach macro. */\n\t\tsize += strlen(item->value) + 1;\n\t}\n\n\t/* join() the list */\n\tchar *output = malloc(sizeof(char) * (size + 1));\n\tchar *output_offset = output;\n\tif (size == 0) {\n\t\t/*\n\t\t * If the path is empty, we take this to mean the root\n\t\t * thus we synthesize a path of \"/\" to return.\n\t\t */\n\t\toutput = realloc(output, sizeof(char) * 2);\n\t\toutput[0] = PATH_SEPARATOR;\n\t\toutput[1] = '\\0';\n\t} else {\n\t\t/* Otherwise, append each element together */\n\t\tforeach(item, out) {\n\t\t\toutput_offset[0] = PATH_SEPARATOR;\n\t\t\toutput_offset++;\n\t\t\tmemcpy(output_offset, item->value, strlen(item->value) + 1);\n\t\t\toutput_offset += strlen(item->value);\n\t\t}\n\t}\n\n\t/* Clean up the various things we used to get here */\n\tlist_destroy(out);\n\tlist_free(out);\n\tfree(out);\n\n\t/* And return a working, absolute path */\n\treturn output;\n}\n\nvoid vfs_install(void) {\n\t/* Initialize the mountpoint tree */\n\tfs_tree = tree_create();\n\n\tstruct vfs_entry * root = malloc(sizeof(struct vfs_entry));\n\n\troot->name = strdup(\"[root]\");\n\troot->file = NULL; /* Nothing mounted as root */\n\troot->fs_type = NULL;\n\troot->device = NULL;\n\n\ttree_set_root(fs_tree, root);\n\n\tfs_types = hashmap_create(5);\n}\n\nint vfs_register(const char * name, vfs_mount_callback callback) {\n\tif (hashmap_get(fs_types, name)) return 1;\n\thashmap_set(fs_types, name, (void *)(uintptr_t)callback);\n\treturn 0;\n}\n\nint vfs_mount_type(const char * type, const char * arg, const char * mountpoint) {\n\n\tvfs_mount_callback t = (vfs_mount_callback)(uintptr_t)hashmap_get(fs_types, type);\n\tif (!t) {\n\t\tdebug_print(WARNING, \"Unknown filesystem type: %s\", type);\n\t\treturn -ENODEV;\n\t}\n\n\tfs_node_t * n = t(arg, mountpoint);\n\n\t/* Quick hack to let partition mappers not return a node to mount at 'mountpoint'... */\n\tif ((uintptr_t)n == (uintptr_t)1) return 0;\n\n\tif (!n) return -EINVAL;\n\n\ttree_node_t * node = vfs_mount(mountpoint, n, type, arg);\n\tif (!node) return -EINVAL;\n\n\tdebug_print(NOTICE, \"Mounted %s[%s] to %s: %p\", type, arg, mountpoint, (void*)n);\n\tdebug_print_vfs_tree();\n\n\treturn 0;\n}\n\nstatic spin_lock_t tmp_vfs_lock = { 0 };\n/**\n * @brief Mount a file system to the specified path.\n *\n * Mounts a file system node to a given base path.\n * For example, if we have an EXT2 filesystem with a root node\n * of ext2_root and we want to mount it to /, we would run\n * vfs_mount(\"/\", ext2_root); - or, if we have a procfs node,\n * we could mount that to /dev/procfs. Individual files can also\n * be mounted.\n *\n * Paths here must be absolute.\n */\nvoid * vfs_mount(const char * path, fs_node_t * local_root, const char * type, const char * options) {\n\tif (!fs_tree) {\n\t\tdebug_print(ERROR, \"VFS hasn't been initialized, you can't mount things yet!\");\n\t\treturn NULL;\n\t}\n\tif (!path || path[0] != '/') {\n\t\tdebug_print(ERROR, \"Path must be absolute for mountpoint.\");\n\t\treturn NULL;\n\t}\n\n\tspin_lock(tmp_vfs_lock);\n\n\tlocal_root->refcount = -1;\n\n\ttree_node_t * ret_val = NULL;\n\n\tchar * p = strdup(path);\n\tchar * i = p;\n\n\tint path_len   = strlen(p);\n\n\t/* Chop the path up */\n\twhile (i < p + path_len) {\n\t\tif (*i == PATH_SEPARATOR) {\n\t\t\t*i = '\\0';\n\t\t}\n\t\ti++;\n\t}\n\t/* Clean up */\n\tp[path_len] = '\\0';\n\ti = p + 1;\n\n\t/* Root */\n\ttree_node_t * root_node = fs_tree->root;\n\n\tif (*i == '\\0') {\n\t\t/* Special case, we're trying to set the root node */\n\t\tstruct vfs_entry * root = (struct vfs_entry *)root_node->value;\n\t\tif (root->file) {\n\t\t\tdebug_print(WARNING, \"Path %s already mounted, unmount before trying to mount something else.\", path);\n\t\t}\n\t\troot->file = local_root;\n\t\troot->device = strdup(options);\n\t\troot->fs_type = strdup(type);\n\t\t/* We also keep a legacy shortcut around for that */\n\t\tfs_root = local_root;\n\t\tret_val = root_node;\n\t} else {\n\t\ttree_node_t * node = root_node;\n\t\tchar * at = i;\n\t\twhile (1) {\n\t\t\tif (at >= p + path_len) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tint found = 0;\n\t\t\tdebug_print(NOTICE, \"Searching for %s\", at);\n\t\t\tforeach(child, node->children) {\n\t\t\t\ttree_node_t * tchild = (tree_node_t *)child->value;\n\t\t\t\tstruct vfs_entry * ent = (struct vfs_entry *)tchild->value;\n\t\t\t\tif (!strcmp(ent->name, at)) {\n\t\t\t\t\tfound = 1;\n\t\t\t\t\tnode = tchild;\n\t\t\t\t\tret_val = node;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!found) {\n\t\t\t\tdebug_print(NOTICE, \"Did not find %s, making it.\", at);\n\t\t\t\tstruct vfs_entry * ent = malloc(sizeof(struct vfs_entry));\n\t\t\t\tent->name = strdup(at);\n\t\t\t\tent->file = NULL;\n\t\t\t\tent->device = NULL;\n\t\t\t\tent->fs_type = NULL;\n\t\t\t\tnode = tree_node_insert_child(fs_tree, node, ent);\n\t\t\t}\n\t\t\tat = at + strlen(at) + 1;\n\t\t}\n\t\tstruct vfs_entry * ent = (struct vfs_entry *)node->value;\n\t\tif (ent->file) {\n\t\t\tdebug_print(WARNING, \"Path %s already mounted, unmount before trying to mount something else.\", path);\n\t\t}\n\t\tent->file = local_root;\n\t\tent->device = strdup(options);\n\t\tent->fs_type = strdup(type);\n\t\tret_val = node;\n\t}\n\n\tfree(p);\n\tspin_unlock(tmp_vfs_lock);\n\treturn ret_val;\n}\n\nvoid map_vfs_directory(const char * c) {\n\tfs_node_t * f = vfs_mapper();\n\ttree_node_t * e = vfs_mount((char*)c, f, \"vfs_mapper\", \"\");\n\tif (!strcmp(c, \"/\")) {\n\t\tf->device = fs_tree->root;\n\t} else {\n\t\tf->device = e;\n\t}\n}\n\n\nvoid debug_print_vfs_tree_node(tree_node_t * node, size_t height) {\n\t/* End recursion on a blank entry */\n\tif (!node) return;\n\tchar * tmp = malloc(512);\n\tmemset(tmp, 0, 512);\n\tchar * c = tmp;\n\t/* Indent output */\n\tfor (uint32_t i = 0; i < height; ++i) {\n\t\tc += snprintf(c, 3, \"  \");\n\t}\n\t/* Get the current process */\n\tstruct vfs_entry * fnode = (struct vfs_entry *)node->value;\n\t/* Print the process name */\n\tif (fnode->file) {\n\t\tc += snprintf(c, 100, \"%s → %s %p (%s, %s)\", fnode->name, fnode->device, (void*)fnode->file, fnode->fs_type, fnode->file->name);\n\t} else {\n\t\tc += snprintf(c, 100, \"%s → (empty)\", fnode->name);\n\t}\n\t/* Linefeed */\n\tdebug_print(NOTICE, \"%s\", tmp);\n\tfree(tmp);\n\tforeach(child, node->children) {\n\t\t/* Recursively print the children */\n\t\tdebug_print_vfs_tree_node(child->value, height + 1);\n\t}\n}\n\nvoid debug_print_vfs_tree(void) {\n\tdebug_print_vfs_tree_node(fs_tree->root, 0);\n}\n\n/**\n * get_mount_point\n *\n */\nfs_node_t *get_mount_point(char * path, unsigned int path_depth, char **outpath, unsigned int * outdepth) {\n\tsize_t depth;\n\n\tfor (depth = 0; depth <= path_depth; ++depth) {\n\t\tpath += strlen(path) + 1;\n\t}\n\n\t/* Last available node */\n\tfs_node_t   * last = fs_root;\n\ttree_node_t * node = fs_tree->root;\n\n\tchar * at = *outpath;\n\tint _depth = 1;\n\tint _tree_depth = 0;\n\n\twhile (1) {\n\t\tif (at >= path) {\n\t\t\tbreak;\n\t\t}\n\t\tint found = 0;\n\t\tdebug_print(INFO, \"Searching for %s\", at);\n\t\tforeach(child, node->children) {\n\t\t\ttree_node_t * tchild = (tree_node_t *)child->value;\n\t\t\tstruct vfs_entry * ent = (struct vfs_entry *)tchild->value;\n\t\t\tif (!strcmp(ent->name, at)) {\n\t\t\t\tfound = 1;\n\t\t\t\tnode = tchild;\n\t\t\t\tat = at + strlen(at) + 1;\n\t\t\t\tif (ent->file) {\n\t\t\t\t\t_tree_depth = _depth;\n\t\t\t\t\tlast = ent->file;\n\t\t\t\t\t*outpath = at;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!found) {\n\t\t\tbreak;\n\t\t}\n\t\t_depth++;\n\t}\n\n\t*outdepth = _tree_depth;\n\n\tif (last) {\n\t\tfs_node_t * last_clone = malloc(sizeof(fs_node_t));\n\t\tmemcpy(last_clone, last, sizeof(fs_node_t));\n\t\tlast_clone->refcount = 0;\n\t\treturn last_clone;\n\t}\n\treturn last;\n}\n\n\n\nfs_node_t *kopen_recur(const char *filename, uint64_t flags, uint64_t symlink_depth, char *relative_to) {\n\t/* Simple sanity checks that we actually have a file system */\n\tif (!filename) {\n\t\treturn NULL;\n\t}\n\n\t/* Canonicalize the (potentially relative) path... */\n\tchar *path = canonicalize_path(relative_to, filename);\n\t/* And store the length once to save recalculations */\n\tsize_t path_len = strlen(path);\n\n\t/* If strlen(path) == 1, then path = \"/\"; return root */\n\tif (path_len == 1) {\n\t\t/* Clone the root file system node */\n\t\tfs_node_t *root_clone = malloc(sizeof(fs_node_t));\n\t\tmemcpy(root_clone, fs_root, sizeof(fs_node_t));\n\t\troot_clone->refcount = 0;\n\n\t\t/* Free the path */\n\t\tfree(path);\n\n\t\topen_fs(root_clone, flags);\n\n\t\t/* And return the clone */\n\t\treturn root_clone;\n\t}\n\n\t/* Otherwise, we need to break the path up and start searching */\n\tchar *path_offset = path;\n\tuint64_t path_depth = 0;\n\twhile (path_offset < path + path_len) {\n\t\t/* Find each PATH_SEPARATOR */\n\t\tif (*path_offset == PATH_SEPARATOR) {\n\t\t\t*path_offset = '\\0';\n\t\t\tpath_depth++;\n\t\t}\n\t\tpath_offset++;\n\t}\n\t/* Clean up */\n\tpath[path_len] = '\\0';\n\tpath_offset = path + 1;\n\n\t/*\n\t * At this point, the path is tokenized and path_offset points\n\t * to the first token (directory) and path_depth is the number\n\t * of directories in the path\n\t */\n\n\t/*\n\t * Dig through the (real) tree to find the file\n\t */\n\tunsigned int depth = 0;\n\t/* Find the mountpoint for this file */\n\tfs_node_t *node_ptr = get_mount_point(path, path_depth, &path_offset, &depth);\n\tdebug_print(INFO, \"path_offset: %s\", path_offset);\n\tdebug_print(INFO, \"depth: %d\", depth);\n\n\tif (!node_ptr) return NULL;\n\n\tdo {\n\t\t/* \n\t\t * This test is a little complicated, but we basically always resolve symlinks in the\n\t\t * of a path (like /home/symlink/file) even if O_NOFOLLOW and O_PATH are set. If we are\n\t\t * on the leaf of the path then we will look at those flags and act accordingly\n\t\t */\n\t\tif ((node_ptr->flags & FS_SYMLINK) &&\n\t\t\t\t!((flags & O_NOFOLLOW) && (flags & O_PATH) && depth == path_depth)) {\n\t\t\t/* This ensures we don't return a path when NOFOLLOW is requested but PATH\n\t\t\t * isn't passed.\n\t\t\t */\n\t\t\tdebug_print(NOTICE, \"resolving symlink at %s\", node_ptr->name);\n\t\t\tif ((flags & O_NOFOLLOW) && depth == path_depth - 1) {\n\t\t\t\t/* TODO(gerow): should probably be setting errno from this */\n\t\t\t\tdebug_print(NOTICE, \"Refusing to follow final entry for open with O_NOFOLLOW for %s.\", node_ptr->name);\n\t\t\t\tfree((void *)path);\n\t\t\t\tfree(node_ptr);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tif (symlink_depth >= MAX_SYMLINK_DEPTH) {\n\t\t\t\t/* TODO(gerow): should probably be setting errno from this */\n\t\t\t\tdebug_print(WARNING, \"Reached max symlink depth on %s.\", node_ptr->name);\n\t\t\t\tfree((void *)path);\n\t\t\t\tfree(node_ptr);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\t/* \n\t\t\t * This may actually be big enough that we wouldn't want to allocate it on\n\t\t\t * the stack, especially considering this function is called recursively\n\t\t\t */\n\t\t\tchar symlink_buf[MAX_SYMLINK_SIZE];\n\t\t\tint len = readlink_fs(node_ptr, symlink_buf, sizeof(symlink_buf));\n\t\t\tif (len < 0) {\n\t\t\t\t/* TODO(gerow): should probably be setting errno from this */\n\t\t\t\tdebug_print(WARNING, \"Got error %d from symlink for %s.\", len, node_ptr->name);\n\t\t\t\tfree((void *)path);\n\t\t\t\tfree(node_ptr);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tif (symlink_buf[len] != '\\0') {\n\t\t\t\t/* TODO(gerow): should probably be setting errno from this */\n\t\t\t\tdebug_print(WARNING, \"readlink for %s doesn't end in a null pointer. That's weird...\", node_ptr->name);\n\t\t\t\tfree((void *)path);\n\t\t\t\tfree(node_ptr);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\tfs_node_t * old_node_ptr = node_ptr;\n\t\t\t/* Rebuild our path up to this point. This is hella hacky. */\n\t\t\tchar * relpath = malloc(path_len + 1);\n\t\t\tchar * ptr = relpath;\n\t\t\tmemcpy(relpath, path, path_len + 1);\n\t\t\tfor (unsigned int i = 0; depth && i < depth-1; i++) {\n\t\t\t\twhile(*ptr != '\\0') {\n\t\t\t\t\tptr++;\n\t\t\t\t}\n\t\t\t\t*ptr = PATH_SEPARATOR;\n\t\t\t}\n\t\t\tnode_ptr = kopen_recur(symlink_buf, 0, symlink_depth + 1, relpath);\n\t\t\tfree(relpath);\n\t\t\tfree(old_node_ptr);\n\t\t\tif (!node_ptr) {\n\t\t\t\t/* Dangling symlink? */\n\t\t\t\tdebug_print(WARNING, \"Failed to open symlink path %s. Perhaps it's a dangling symlink?\", symlink_buf);\n\t\t\t\tfree((void *)path);\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t\tif (path_offset >= path+path_len) {\n\t\t\tfree(path);\n\t\t\topen_fs(node_ptr, flags);\n\t\t\treturn node_ptr;\n\t\t}\n\t\tif (depth == path_depth) {\n\t\t\t/* We found the file and are done, open the node */\n\t\t\topen_fs(node_ptr, flags);\n\t\t\tfree((void *)path);\n\t\t\treturn node_ptr;\n\t\t}\n\t\t/* We are still searching... */\n\t\tif (!has_permission(node_ptr, 01)) {\n\t\t\t/*\n\t\t\t * TODO: kopen_recur has no way to pass along a failure reason?\n\t\t\t *       This will appear as 'ENOENT' instead of 'EACCESS', should fix that...\n\t\t\t */\n\t\t\tfree(node_ptr);\n\t\t\tfree((void*)path);\n\t\t\treturn NULL;\n\t\t}\n\t\tdebug_print(INFO, \"... Searching for %s\", path_offset);\n\t\tfs_node_t * node_next = finddir_fs(node_ptr, path_offset);\n\t\tfree(node_ptr); /* Always a clone or an unopened thing */\n\t\tnode_ptr = node_next;\n\t\t/* Search the active directory for the requested directory */\n\t\tif (!node_ptr) {\n\t\t\t/* We failed to find the requested directory */\n\t\t\tfree((void *)path);\n\t\t\treturn NULL;\n\t\t}\n\t\tpath_offset += strlen(path_offset) + 1;\n\t\t++depth;\n\t} while (depth < path_depth + 1);\n\tdebug_print(INFO, \"- Not found.\");\n\t/* We failed to find the requested file, but our loop terminated. */\n\tfree((void *)path);\n\treturn NULL;\n}\n\n/**\n * @brief Open a file by name.\n *\n * Explore the file system tree to find the appropriate node for\n * for a given path. The path can be relative to the working directory\n * and will be canonicalized by the kernel.\n *\n * @param filename Filename to open\n * @param flags    Flag bits for read/write mode.\n * @returns A file system node element that the caller can free.\n */\nfs_node_t *kopen(const char *filename, unsigned int flags) {\n\tdebug_print(NOTICE, \"kopen(%s)\", filename);\n\n\treturn kopen_recur(filename, flags, 0, (char *)(this_core->current_process->wd_name));\n}\n\n"
  },
  {
    "path": "kernel/vfs/zero.c",
    "content": "/**\n * @file  kernel/vfs/zero.c\n * @brief /dev/null and /dev/zero provider.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n\n#include <stdint.h>\n#include <stddef.h>\n#include <errno.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n\nstatic ssize_t read_null(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\treturn 0;\n}\n\nstatic ssize_t write_null(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\treturn size;\n}\n\nstatic void open_null(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void close_null(fs_node_t * node) {\n\treturn;\n}\n\nstatic ssize_t read_zero(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tmemset(buffer, 0x00, size);\n\treturn size;\n}\n\nstatic ssize_t write_zero(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\treturn size;\n}\n\nstatic void open_zero(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void close_zero(fs_node_t * node) {\n\treturn;\n}\n\nstatic fs_node_t * null_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"null\");\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0666;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->read    = read_null;\n\tfnode->write   = write_null;\n\tfnode->open    = open_null;\n\tfnode->close   = close_null;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL;\n\treturn fnode;\n}\n\nstatic fs_node_t * zero_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tstrcpy(fnode->name, \"zero\");\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask = 0666;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->read    = read_zero;\n\tfnode->write   = write_zero;\n\tfnode->open    = open_zero;\n\tfnode->close   = close_zero;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL;\n\treturn fnode;\n}\n\nvoid zero_initialize(void) {\n\tvfs_mount(\"/dev/null\", null_device_create(), \"null\", \"\");\n\tvfs_mount(\"/dev/zero\", zero_device_create(), \"zero\", \"\");\n}\n\n"
  },
  {
    "path": "kernel/video/lfbvideo.c",
    "content": "/**\n * @file  kernel/video/lfbvideo.c\n * @brief Shared linear framebuffer drivers for qemu/bochs/vbox, vmware,\n *        and platforms that can modeset in the bootloader.\n *\n * Detects a small set of video devices that can be configured with simple\n * port writes and provides a runtime modesetting API for them. For other\n * devices, provides framebuffer mapping and resolution querying for modes\n * that have been preconfigured by the bootloader.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/video.h>\n#include <kernel/process.h>\n#include <kernel/string.h>\n#include <kernel/signal.h>\n#include <kernel/tokenize.h>\n#include <kernel/multiboot.h>\n#include <kernel/procfs.h>\n#include <kernel/mmu.h>\n#include <kernel/args.h>\n\n/* FIXME: Not sure what to do with this; ifdef around it? */\n#include <kernel/arch/x86_64/ports.h>\n\nstatic int PREFERRED_W = 1440;\nstatic int PREFERRED_H = 900;\n#define PREFERRED_VY 4096\n#define PREFERRED_B 32\n\n/* Exported to other modules */\nuint16_t lfb_resolution_x = 0;\nuint16_t lfb_resolution_y = 0;\nuint16_t lfb_resolution_b = 0;\nuint32_t lfb_resolution_s = 0;\nuint8_t * lfb_vid_memory = (uint8_t *)0xE0000000;\nsize_t lfb_memsize = 0xFF0000;\nconst char * lfb_driver_name = NULL;\nint lfb_use_write_combining = 0;\n\nuintptr_t lfb_qemu_mmio = 0;\n\nfs_node_t * lfb_device = NULL;\nstatic int lfb_init(const char * c);\n\n/* Where to send display size change signals */\nstatic pid_t display_change_recipient = 0;\n\n/* Driver-specific modesetting function */\nvoid (*lfb_resolution_impl)(uint16_t,uint16_t) = NULL;\n\n/* Called by ioctl on /dev/fb0 */\nvoid lfb_set_resolution(uint16_t x, uint16_t y) {\n\tif (lfb_resolution_impl) {\n\t\tlfb_resolution_impl(x,y);\n\t\tif (display_change_recipient) {\n\t\t\tsend_signal(display_change_recipient, SIGWINEVENT, 1);\n\t\t}\n\t}\n}\n\nextern void ptr_validate(void * ptr, const char * syscall);\n#define validate(o) ptr_validate(o,\"ioctl\")\n\n/**\n * Framebuffer control ioctls.\n * Used by the compositor to get display sizes and by the\n * resolution changer to initiate modesetting.\n */\nstatic int ioctl_vid(fs_node_t * node, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase IO_VID_WIDTH:\n\t\t\t/* Get framebuffer width */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = lfb_resolution_x;\n\t\t\treturn 0;\n\t\tcase IO_VID_HEIGHT:\n\t\t\t/* Get framebuffer height */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = lfb_resolution_y;\n\t\t\treturn 0;\n\t\tcase IO_VID_DEPTH:\n\t\t\t/* Get framebuffer bit depth */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = lfb_resolution_b;\n\t\t\treturn 0;\n\t\tcase IO_VID_STRIDE:\n\t\t\t/* Get framebuffer scanline stride */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = lfb_resolution_s;\n\t\t\treturn 0;\n\t\tcase IO_VID_ADDR:\n\t\t\t/* Map framebuffer into userspace process */\n\t\t\tvalidate(argp);\n\t\t\t{\n\t\t\t\tuintptr_t lfb_user_offset;\n\t\t\t\tif (*(uintptr_t*)argp == 0) {\n\t\t\t\t\t/* Pick an address and map it */\n\t\t\t\t\tlfb_user_offset = USER_DEVICE_MAP;\n\t\t\t\t} else {\n\t\t\t\t\tvalidate((void*)(*(uintptr_t*)argp));\n\t\t\t\t\tlfb_user_offset = *(uintptr_t*)argp;\n\t\t\t\t}\n\t\t\t\tfor (uintptr_t i = 0; i < lfb_memsize; i += 0x1000) {\n\t\t\t\t\tunion PML * page = mmu_get_page(lfb_user_offset + i, MMU_GET_MAKE);\n\t\t\t\t\tmmu_frame_map_address(page,MMU_FLAG_WRITABLE | (lfb_use_write_combining ? MMU_FLAG_WC : 0),((uintptr_t)(lfb_vid_memory) & 0xFFFFFFFF) + i);\n\t\t\t\t}\n\t\t\t\t*((uintptr_t *)argp) = lfb_user_offset;\n\t\t\t}\n\t\t\treturn 0;\n\t\tcase IO_VID_SIGNAL:\n\t\t\t/* ioctl to register for a signal (vid device change? idk) on display change */\n\t\t\tdisplay_change_recipient = this_core->current_process->id;\n\t\t\treturn 0;\n\t\tcase IO_VID_SET:\n\t\t\t/* Initiate mode setting */\n\t\t\tvalidate(argp);\n\t\t\tlfb_set_resolution(((struct vid_size *)argp)->width, ((struct vid_size *)argp)->height);\n\t\t\treturn 0;\n\t\tcase IO_VID_DRIVER:\n\t\t\tvalidate(argp);\n\t\t\tmemcpy(argp, lfb_driver_name, strlen(lfb_driver_name));\n\t\t\treturn 0;\n\t\tcase IO_VID_REINIT:\n\t\t\tif (this_core->current_process->user != 0) {\n\t\t\t\treturn -EPERM;\n\t\t\t}\n\t\t\tvalidate(argp);\n\t\t\treturn lfb_init(argp);\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n\treturn -EINVAL;\n}\n\n/* Framebuffer device file initializer */\nstatic fs_node_t * lfb_video_device_create(void /* TODO */) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tsnprintf(fnode->name, 100, \"fb0\"); /* TODO */\n\tfnode->length  = 0;\n\tfnode->flags   = FS_BLOCKDEVICE; /* Framebuffers are block devices */\n\tfnode->mask    = 0660; /* Only accessible to root user/group */\n\tfnode->ioctl   = ioctl_vid; /* control function defined above */\n\treturn fnode;\n}\n\nstatic void framebuffer_func(fs_node_t * node) {\n\tif (lfb_driver_name) {\n\t\tprocfs_printf(node,\n\t\t\t\"Driver:\\t%s\\n\"\n\t\t\t\"XRes:\\t%d\\n\"\n\t\t\t\"YRes:\\t%d\\n\"\n\t\t\t\"BitsPerPixel:\\t%d\\n\"\n\t\t\t\"Stride:\\t%d\\n\"\n\t\t\t\"Address:\\t%p\\n\",\n\t\t\tlfb_driver_name,\n\t\t\tlfb_resolution_x,\n\t\t\tlfb_resolution_y,\n\t\t\tlfb_resolution_b,\n\t\t\tlfb_resolution_s,\n\t\t\tlfb_vid_memory);\n\t} else {\n\t\tprocfs_printf(node, \"Driver:\\tnone\\n\");\n\t}\n}\n\nstatic struct procfs_entry framebuffer_entry = {\n\t0,\n\t\"framebuffer\",\n\tframebuffer_func,\n};\n\n/* Install framebuffer device */\nstatic void finalize_graphics(const char * driver) {\n\tlfb_driver_name = driver;\n\tlfb_device = lfb_video_device_create();\n\tlfb_device->length  = lfb_resolution_s * lfb_resolution_y; /* Size is framebuffer size in bytes */\n\n\tchar info[100];\n\tsnprintf(info, 99, \"%s,%ux%u\", driver, lfb_resolution_x, lfb_resolution_y);\n\n\tvfs_mount(\"/dev/fb0\", lfb_device, \"lfb\", info);\n\n\tprocfs_install(&framebuffer_entry);\n}\n\n/* QEMU support {{{ */\nstatic void qemu_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tuintptr_t * output = extra;\n\tif ((v == 0x1234 && d == 0x1111) ||\n\t    (v == 0x10de && d == 0x0a20))  {\n\t\t#ifndef __x86_64__\n\t\t/* we have to configure this thing ourselves */\n\t\tuintptr_t t = 0x10000008;\n\t\tuintptr_t m = 0x11000000;\n\t\tpci_write_field(device, PCI_BAR0, 4, t); /* video memory? */\n\t\tpci_write_field(device, PCI_BAR2, 4, m); /* MMIO? */\n\t\tpci_write_field(device, PCI_COMMAND, 2, 4|2|1);\n\t\t#else\n\t\tuintptr_t t = pci_read_field(device, PCI_BAR0, 4);\n\t\tuintptr_t m = pci_read_field(device, PCI_BAR2, 4);\n\t\t#endif\n\n\t\tif (m == 0) {\n\t\t\t/* Shoot. */\n\t\t\treturn;\n\t\t}\n\n\t\tif (t > 0) {\n\t\t\toutput[0] = (uintptr_t)mmu_map_from_physical(t & 0xFFFFFFF0);\n\t\t\toutput[1] = (uintptr_t)mmu_map_from_physical(m & 0xFFFFFFF0);\n\t\t\t/* Figure out size */\n\t\t\tpci_write_field(device, PCI_BAR0, 4, 0xFFFFFFFF);\n\t\t\tuint32_t s = pci_read_field(device, PCI_BAR0, 4);\n\t\t\ts = ~(s & -15) + 1;\n\t\t\toutput[2] = s;\n\t\t\tpci_write_field(device, PCI_BAR0, 4, (uint32_t)t);\n\t\t}\n\t}\n}\n\n#define QEMU_MMIO_ID       0x00\n#define QEMU_MMIO_FBWIDTH  0x02\n#define QEMU_MMIO_FBHEIGHT 0x04\n#define QEMU_MMIO_BPP      0x06\n#define QEMU_MMIO_ENABLED  0x08\n#define QEMU_MMIO_VIRTX    0x0c\n#define QEMU_MMIO_VIRTY    0x0e\n\nstatic void qemu_mmio_out(int off, uint16_t val) {\n\t*(volatile uint16_t*)(lfb_qemu_mmio + 0x500 + off) = val;\n}\n\nstatic uint16_t qemu_mmio_in(int off) {\n\treturn *(volatile uint16_t*)(lfb_qemu_mmio + 0x500 + off);\n}\n\nstatic void qemu_set_resolution(uint16_t x, uint16_t y) {\n\tqemu_mmio_out(QEMU_MMIO_ENABLED,  0);\n\tqemu_mmio_out(QEMU_MMIO_FBWIDTH,  x);\n\tqemu_mmio_out(QEMU_MMIO_FBHEIGHT, y);\n\tqemu_mmio_out(QEMU_MMIO_BPP, PREFERRED_B);\n\tqemu_mmio_out(QEMU_MMIO_VIRTX, x);\n\tqemu_mmio_out(QEMU_MMIO_VIRTY, y);\n\tqemu_mmio_out(QEMU_MMIO_ENABLED,  0x41); /* 01h: enabled, 40h: lfb */\n\n\t/* unblank vga; this should only be necessary on secondary displays */\n\t*(volatile uint8_t*)(lfb_qemu_mmio + 0x400) = 0x20;\n\n\tlfb_resolution_x = qemu_mmio_in(QEMU_MMIO_FBWIDTH);\n\tlfb_resolution_y = qemu_mmio_in(QEMU_MMIO_FBHEIGHT);\n\tlfb_resolution_b = qemu_mmio_in(QEMU_MMIO_BPP);\n\tlfb_resolution_s = qemu_mmio_in(QEMU_MMIO_VIRTX) * (lfb_resolution_b / 8);\n}\n\nstatic void graphics_install_bochs(uint16_t resolution_x, uint16_t resolution_y);\n\nstatic void graphics_install_qemu(uint16_t resolution_x, uint16_t resolution_y) {\n\n\tuintptr_t vals[3] = {0,0,0};\n\tpci_scan(qemu_scan_pci, -1, vals);\n\n\tif (!vals[0]) {\n\t\t/* Try port-IO interface */\n\t\tgraphics_install_bochs(resolution_x, resolution_y);\n\t\treturn;\n\t}\n\n\tlfb_vid_memory = (uint8_t*)vals[0];\n\tlfb_qemu_mmio = vals[1];\n\tlfb_memsize    = vals[2];\n\n\tuint16_t i = qemu_mmio_in(QEMU_MMIO_ID);\n\tif (i < 0xB0C0 || i > 0xB0C6) return; /* Unsupported qemu device. */\n\tqemu_mmio_out(QEMU_MMIO_ID, 0xB0C4); /* We speak ver. 4 */\n\n\tqemu_set_resolution(resolution_x, resolution_y);\n\tresolution_x = lfb_resolution_x; /* may have changed */\n\n\tlfb_resolution_impl = &qemu_set_resolution;\n\n\tif (!lfb_vid_memory) {\n\t\tprintf(\"failed to locate video memory\\n\");\n\t\treturn;\n\t}\n\n\tfinalize_graphics(\"qemu\");\n}\n\n/* VirtualBox implements the portio-based interface, but not the MMIO one. */\nstatic void bochs_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif ((v == 0x80EE && d == 0xBEEF) || (v == 0x1234 && d == 0x1111)) {\n\t\tuintptr_t t = pci_read_field(device, PCI_BAR0, 4);\n\t\tif (t > 0) {\n\t\t\t*((uint8_t **)extra) = mmu_map_from_physical(t & 0xFFFFFFF0);\n\t\t}\n\t}\n}\n\nstatic void bochs_set_resolution(uint16_t x, uint16_t y) {\n\toutports(0x1CE, 0x04);\n\toutports(0x1CF, 0x00);\n\toutports(0x1CE, 0x01);\n\toutports(0x1CF, x);\n\toutports(0x1CE, 0x02);\n\toutports(0x1CF, y);\n\toutports(0x1CE, 0x03);\n\toutports(0x1CF, PREFERRED_B);\n\toutports(0x1CE, 0x07);\n\toutports(0x1CF, PREFERRED_VY);\n\toutports(0x1CE, 0x04);\n\toutports(0x1CF, 0x41);\n\toutports(0x1CE, 0x01);\n\tx = inports(0x1CF);\n\tlfb_resolution_x = x;\n\tlfb_resolution_s = x * 4;\n\tlfb_resolution_y = y;\n\tlfb_resolution_b = 32;\n}\n\nstatic void graphics_install_bochs(uint16_t resolution_x, uint16_t resolution_y) {\n\toutports(0x1CE, 0x00);\n\tuint16_t i = inports(0x1CF);\n\tif (i < 0xB0C0 || i > 0xB0C6) {\n\t\treturn;\n\t}\n\toutports(0x1CF, 0xB0C4);\n\ti = inports(0x1CF);\n\tbochs_set_resolution(resolution_x, resolution_y);\n\tresolution_x = lfb_resolution_x; /* may have changed */\n\tpci_scan(bochs_scan_pci, -1, &lfb_vid_memory);\n\tlfb_resolution_impl = &bochs_set_resolution;\n\tif (!lfb_vid_memory) {\n\t\tprintf(\"failed to locate video memory\\n\");\n\t\treturn;\n\t}\n\toutports(0x1CE, 0x0a);\n\ti = inports(0x1CF);\n\tif (i > 1) {\n\t\tlfb_memsize = (uint32_t)i * 64 * 1024;\n\t} else {\n\t\tlfb_memsize = inportl(0x1CF);\n\t}\n\tfinalize_graphics(\"bochs\");\n}\n\nextern void arch_framebuffer_initialize(void);\n\nstatic void graphics_install_preset(uint16_t w, uint16_t h) {\n\t/* Make sure memsize is actually big enough */\n\tsize_t minsize = lfb_resolution_s * lfb_resolution_y * 4;\n\tif (lfb_memsize < minsize) lfb_memsize = minsize;\n\n\tfinalize_graphics(\"preset\");\n}\n\n#define SVGA_IO_BASE (vmware_io)\n#define SVGA_IO_MUL 1\n#define SVGA_INDEX_PORT 0\n#define SVGA_VALUE_PORT 1\n\n#define SVGA_REG_ID 0\n#define SVGA_REG_ENABLE 1\n#define SVGA_REG_WIDTH 2\n#define SVGA_REG_HEIGHT 3\n#define SVGA_REG_BITS_PER_PIXEL 7\n#define SVGA_REG_BYTES_PER_LINE 12\n#define SVGA_REG_FB_START 13\n\nstatic uint32_t vmware_io = 0;\n\nstatic void vmware_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif ((v == 0x15ad && d == 0x0405)) {\n\t\tuintptr_t t = pci_read_field(device, PCI_BAR0, 4);\n\t\tif (t > 0) {\n\t\t\t*((uint8_t **)extra) = (uint8_t *)(t & 0xFFFFFFF0);\n\t\t}\n\t}\n}\n\nstatic void vmware_write(int reg, int value) {\n\toutportl(SVGA_IO_MUL * SVGA_INDEX_PORT + SVGA_IO_BASE, reg);\n\toutportl(SVGA_IO_MUL * SVGA_VALUE_PORT + SVGA_IO_BASE, value);\n}\n\nstatic uint32_t vmware_read(int reg) {\n\toutportl(SVGA_IO_MUL * SVGA_INDEX_PORT + SVGA_IO_BASE, reg);\n\treturn inportl(SVGA_IO_MUL * SVGA_VALUE_PORT + SVGA_IO_BASE);\n}\n\nstatic void vmware_set_resolution(uint16_t w, uint16_t h) {\n\tvmware_write(SVGA_REG_ENABLE, 0);\n\tvmware_write(SVGA_REG_ID, 0);\n\tvmware_write(SVGA_REG_WIDTH, w);\n\tvmware_write(SVGA_REG_HEIGHT, h);\n\tvmware_write(SVGA_REG_BITS_PER_PIXEL, 32);\n\tvmware_write(SVGA_REG_ENABLE, 1);\n\n\tuint32_t bpl = vmware_read(SVGA_REG_BYTES_PER_LINE);\n\n\tlfb_resolution_x = w;\n\tlfb_resolution_s = bpl;\n\tlfb_resolution_y = h;\n\tlfb_resolution_b = 32;\n\n\tlfb_memsize = vmware_read(15);\n}\n\nstatic void graphics_install_vmware(uint16_t w, uint16_t h) {\n\tpci_scan(vmware_scan_pci, -1, &vmware_io);\n\n\tif (!vmware_io) {\n\t\tprintf(\"vmware video, but no device found?\\n\");\n\t\treturn;\n\t} else {\n\t\tprintf(\"vmware io base: %p\\n\", (void*)(uintptr_t)vmware_io);\n\t}\n\n\tvmware_set_resolution(w,h);\n\tlfb_resolution_impl = &vmware_set_resolution;\n\n\tuintptr_t fb_addr = vmware_read(SVGA_REG_FB_START);\n\tprintf(\"vmware fb address: %p\\n\", (void*)fb_addr);\n\n\tlfb_memsize = vmware_read(15);\n\n\tprintf(\"vmware fb size: 0x%lx\\n\", lfb_memsize);\n\n\tlfb_vid_memory = mmu_map_from_physical(fb_addr);\n\n\tfinalize_graphics(\"vmware\");\n}\n\nstruct disp_mode {\n\tint16_t x;\n\tint16_t y;\n\tint set;\n};\n\nstatic void auto_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tstruct disp_mode * mode = extra;\n\tif (mode->set) return;\n\tif ((v == 0x1234 && d == 0x1111) ||\n\t    (v == 0x10de && d == 0x0a20))  {\n\t\tmode->set = 1;\n\t\tgraphics_install_qemu(mode->x, mode->y);\n\t} else if (v == 0x80EE && d == 0xBEEF) {\n\t\tmode->set = 1;\n\t\tgraphics_install_bochs(mode->x, mode->y);\n\t} else if ((v == 0x15ad && d == 0x0405)) {\n\t\tmode->set = 1;\n\t\tgraphics_install_vmware(mode->x, mode->y);\n\t}\n}\n\nstatic fs_node_t * vga_text_device = NULL;\n\nstatic int ioctl_vga(fs_node_t * node, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase IO_VID_WIDTH:\n\t\t\t/* Get framebuffer width */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = 80;\n\t\t\treturn 0;\n\t\tcase IO_VID_HEIGHT:\n\t\t\t/* Get framebuffer height */\n\t\t\tvalidate(argp);\n\t\t\t*((size_t *)argp) = 25;\n\t\t\treturn 0;\n\t\tcase IO_VID_ADDR:\n\t\t\t/* Map framebuffer into userspace process */\n\t\t\tvalidate(argp);\n\t\t\t{\n\t\t\t\tuintptr_t vga_user_offset;\n\t\t\t\tif (*(uintptr_t*)argp == 0) {\n\t\t\t\t\tvga_user_offset = USER_DEVICE_MAP;\n\t\t\t\t} else {\n\t\t\t\t\tvalidate((void*)(*(uintptr_t*)argp));\n\t\t\t\t\tvga_user_offset = *(uintptr_t*)argp;\n\t\t\t\t}\n\t\t\t\tfor (uintptr_t i = 0; i < 0x1000; i += 0x1000) {\n\t\t\t\t\tunion PML * page = mmu_get_page(vga_user_offset + i, MMU_GET_MAKE);\n\t\t\t\t\tmmu_frame_map_address(page,MMU_FLAG_WRITABLE/*|MMU_FLAG_WC*/,(uintptr_t)(0xB8000 + i));\n\t\t\t\t}\n\t\t\t\t*((uintptr_t *)argp) = vga_user_offset;\n\t\t\t}\n\t\t\treturn 0;\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic void vga_text_init(void) {\n\tvga_text_device = calloc(sizeof(fs_node_t), 1);\n\tsnprintf(vga_text_device->name, 100, \"vga0\");\n\tvga_text_device->length = 0;\n\tvga_text_device->flags  = FS_BLOCKDEVICE;\n\tvga_text_device->mask   = 0660;\n\tvga_text_device->ioctl  = ioctl_vga;\n\tvfs_mount(\"/dev/vga0\", vga_text_device, \"vgatext\", \"\");\n}\n\nstatic int lfb_init(const char * c) {\n\tchar * arg = strdup(c);\n\tchar * argv[10];\n\tint argc = tokenize(arg, \",\", argv);\n\n\tif (!strcmp(argv[0],\"text\")) {\n\t\t/* VGA text mode? TODO: We should try to detect this,\n\t\t * or limit it to things that are likely to have it... */\n\t\tvga_text_init();\n\t\tfree(arg);\n\t\treturn 0;\n\t}\n\n\tuint16_t x, y;\n\n\t/* Extract framebuffer information from multiboot */\n\tarch_framebuffer_initialize();\n\tx = lfb_resolution_x;\n\ty = lfb_resolution_y;\n\n\tif (argc >= 3) {\n\t\tx = atoi(argv[1]);\n\t\ty = atoi(argv[2]);\n\t} else if (!lfb_resolution_x) {\n\t\tx = PREFERRED_W;\n\t\ty = PREFERRED_H;\n\t}\n\n\tif (args_present(\"lfbwc\")) {\n\t\tlfb_use_write_combining = 1;\n\t}\n\n\tint ret_val = 0;\n\tif (!strcmp(argv[0], \"auto\")) {\n\t\t/* Attempt autodetection */\n\t\tstruct disp_mode mode = {x,y,0};\n\t\tpci_scan(auto_scan_pci, -1, &mode);\n\t\tif (!mode.set) {\n\t\t\tgraphics_install_preset(x,y);\n\t\t}\n\t} else if (!strcmp(argv[0], \"qemu\")) {\n\t\t/* BGA with MMIO */\n\t\tgraphics_install_qemu(x,y);\n\t} else if (!strcmp(argv[0], \"bochs\")) {\n\t\t/* BGA with no MMIO */\n\t\tgraphics_install_bochs(x,y);\n\t} else if (!strcmp(argv[0],\"vmware\")) {\n\t\t/* VMware SVGA */\n\t\tgraphics_install_vmware(x,y);\n\t} else if (!strcmp(argv[0],\"preset\")) {\n\t\t/* Set by bootloader (UEFI) */\n\t\tgraphics_install_preset(x,y);\n\t} else {\n\t\tret_val = 1;\n\t}\n\n\tfree(arg);\n\treturn ret_val;\n}\n\nint framebuffer_initialize(void) {\n\tlfb_init(args_present(\"vid\") ? args_value(\"vid\") : \"auto\");\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "lib/README.md",
    "content": "# ToaruOS System Libraries\n\nThese are the core system libraries of ToaruOS. Where functionality isn't expected in the C standard library, these provide additional features that are shared by multiple ToaruOS applications.\n\n## `toaru_auth`\n\nProvides password validation and login helper methods. Exists primarily because `libc` doesn't have these things and there are multiple places where logins are checked (`login`, `glogin`, `sudo`, `gsudo`...).\n\n## `toaru_button`\n\nRenderer for button widgets. Not really a widget library at the moment.\n\n## `toaru_confreader`\n\nImplements a basic INI parser for use with configuration files.\n\n## `toaru_decorations`\n\nClient-side decoration library for the compositor. Supports pluggable decoration themes through additional libraries, which are named as `libtoaru_decor-...`.\n\n## `toaru_graphics`\n\nGeneral-purpose 2D drawing and pixel-pushing library. Provides sprite blitting, rotation, scaling, etc.\n\n## `toaru_hashmap`\n\nGeneric hashmap implementation. Also used by the kernel.\n\n## `toaru_iconcache`\n\nConvenience library for loading icons at specific sizes.\n\n## `toaru_inflate`\n\nDecompression library for DEFLATE payloads.\n\n## `toaru_jpeg`\n\nMinimal, incomplete JPEG decoder. Mostly used for providing wallpapers. Doesn't support most JPEG features.\n\n## `toaru_kbd`\n\nKeyboard scancode parser.\n\n## `toaru_list`\n\nGeneric expandable linked list implementation.\n\n## `toaru_markup`\n\nXML-like syntax parser.\n\n## `toaru_menu`\n\nMenu widget library. Used for the \"Applications\" menu, context menus, etc.\n\n## `toaru_pex`\n\nUserspace library for using the ToaruOS \"packetfs\" subsystem, which provides packet-based IPC.\n\n## `toaru_png`\n\nDecoder for Portable Network Graphics images.\n\n## `toaru_rline`\n\nRich line editor for terminal applications, with support for tab completion and syntax highlighting.\n\n## `toaru_termemu`\n\nTerminal ANSI escape processor.\n\n## `toaru_text`\n\nTrueType font parser and text renderer.\n\n## `toaru_tree`\n\nGeneric tree implementation. Also used by the kernel.\n\n## `toaru_yutani`\n\nCompositor client library, used to build GUI applications.\n\n"
  },
  {
    "path": "lib/auth.c",
    "content": "/**\n * @brief Authentication routines.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <pwd.h>\n\n#ifndef fgetpwent\nextern struct passwd *fgetpwent(FILE *stream);\n#endif\n\nextern int setgroups(int size, const gid_t list[]);\n\n#define MASTER_PASSWD \"/etc/master.passwd\"\n\nint toaru_auth_check_pass(char * user, char * pass) {\n\n\t/* XXX DO something useful */\n\n\t/* Open up /etc/master.passwd */\n\n\tFILE * master = fopen(MASTER_PASSWD, \"r\");\n\tstruct passwd * p;\n\n\twhile ((p = fgetpwent(master))) {\n\t\tif (!strcmp(p->pw_name, user) && !strcmp(p->pw_passwd, pass)) {\n\t\t\tfclose(master);\n\t\t\treturn p->pw_uid;\n\t\t}\n\t}\n\n\tfclose(master);\n\treturn -1;\n\n}\n\nvoid toaru_auth_set_vars(void) {\n\tint uid = getuid();\n\n\tstruct passwd * p = getpwuid(uid);\n\n\tif (!p) {\n\t\tchar tmp[10];\n\t\tsprintf(tmp, \"%d\", uid);\n\t\tsetenv(\"USER\", strdup(tmp), 1);\n\t\tsetenv(\"HOME\", \"/\", 1);\n\t\tsetenv(\"SHELL\", \"/bin/sh\", 1);\n\t} else {\n\t\tsetenv(\"USER\", strdup(p->pw_name), 1);\n\t\tsetenv(\"HOME\", strdup(p->pw_dir), 1);\n\t\tsetenv(\"SHELL\", strdup(p->pw_shell), 1);\n\t\tsetenv(\"WM_THEME\", strdup(p->pw_comment), 1);\n\t}\n\tendpwent();\n\n\tsetenv(\"PATH\", \"/usr/bin:/bin\", 0);\n\tchdir(getenv(\"HOME\"));\n}\n\nvoid toaru_auth_set_groups(uid_t uid) {\n\t/* Get the username for this uid */\n\tstruct passwd * pwd = getpwuid(uid);\n\n\t/* No username? No group memberships! */\n\tif (!pwd) goto no_groups;\n\n\t/* Open the group file */\n\tFILE * groupList = fopen(\"/etc/group\",\"r\");\n\n\t/* No groups? No membership. */\n\tif (!groupList) goto no_groups;\n\n\t/* Scan through lines of groups. */\n#define LINE_LEN 2048\n\tchar * pw_blob = malloc(LINE_LEN);\n\n\tint groupCount = 0;\n\tgid_t myGroups[32] = {0};\n\n\twhile (!feof(groupList)) {\n\t\tmemset(pw_blob, 0x00, LINE_LEN);\n\t\tfgets(pw_blob, LINE_LEN, groupList);\n\t\tif (pw_blob[strlen(pw_blob)-1] == '\\n') {\n\t\t\tpw_blob[strlen(pw_blob)-1] = '\\0'; /* erase newline */\n\t\t}\n\n\t\t/* Tokenize */\n\t\tchar * memberlist = NULL;\n\t\tchar *p, *last;\n\t\tgid_t groupNumber = -1;\n\t\tint i = 0;\n\t\tfor ((p = strtok_r(pw_blob, \":\", &last)); p;\n\t\t\t\t(p = strtok_r(NULL, \":\", &last)), i++) {\n\t\t\tif (i == 2) {\n\t\t\t\tgroupNumber = atoi(p);\n\t\t\t} else if (i == 3) {\n\t\t\t\tmemberlist = p;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (groupNumber == -1) continue;\n\t\tif (!memberlist) continue;\n\n\t\tfor ((p = strtok_r(memberlist, \",\", &last)); p;\n\t\t\t\t(p = strtok_r(NULL, \",\", &last))) {\n\t\t\tif (!strcmp(p, pwd->pw_name)) {\n\t\t\t\tif (groupCount < 32) {\n\t\t\t\t\tmyGroups[groupCount] = groupNumber;\n\t\t\t\t\tgroupCount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tsetgroups(groupCount, myGroups);\n\tfree(pw_blob);\n\tfclose(groupList);\n\treturn;\n\nno_groups:\n\tsetgroups(0, NULL);\n}\n\nvoid toaru_set_credentials(uid_t uid) {\n\ttoaru_auth_set_groups(uid);\n\tsetgid(uid);\n\tsetuid(uid);\n\ttoaru_auth_set_vars();\n}\n"
  },
  {
    "path": "lib/button.c",
    "content": "/**\n * @brief Button \"widget\"\n *\n * Really just a function to render a button...\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <toaru/graphics.h>\n#include <toaru/button.h>\n#include <toaru/text.h>\n#include <toaru/icon_cache.h>\n\nstatic struct TT_Font * _tt_font_thin = NULL;\n\nvoid ttk_button_draw(gfx_context_t * ctx, struct TTKButton * button) {\n\tif (button->width == 0) {\n\t\treturn;\n\t}\n\n\tint hilight = button->hilight & 0xFF;\n\tint disabled = button->hilight & 0x100;\n\n\t/* Dark edge */\n\tif (hilight < 3) {\n\t\tstruct gradient_definition edge = {button->height, button->y, rgb(166,166,166), rgb(136,136,136)};\n\t\tdraw_rounded_rectangle_pattern(ctx, button->x, button->y, button->width, button->height, 4, gfx_vertical_gradient_pattern, &edge);\n\t}\n\n\t/* Sheen */\n\tif (hilight < 2) {\n\t\tdraw_rounded_rectangle(ctx, button->x + 1, button->y + 1, button->width - 2, button->height - 2, 3, rgb(238,238,238));\n\t/* Button face - this should normally be a gradient */\n\t\tif (hilight == 1) {\n\t\t\tstruct gradient_definition face = {button->height-3, button->y + 2, rgb(240,240,240), rgb(230,230,230)};\n\t\t\tdraw_rounded_rectangle_pattern(ctx, button->x + 2, button->y + 2, button->width - 4, button->height - 3, 2, gfx_vertical_gradient_pattern, &face);\n\t\t} else {\n\t\t\tstruct gradient_definition face = {button->height-3, button->y + 2, rgb(219,219,219), rgb(204,204,204)};\n\t\t\tdraw_rounded_rectangle_pattern(ctx, button->x + 2, button->y + 2, button->width - 4, button->height - 3, 2, gfx_vertical_gradient_pattern, &face);\n\t\t}\n\t} else if (hilight == 2) {\n\t\tstruct gradient_definition face = {button->height-2, button->y + 1, rgb(180,180,180), rgb(160,160,160)};\n\t\tdraw_rounded_rectangle_pattern(ctx, button->x + 1, button->y + 1, button->width - 2, button->height - 2, 3, gfx_vertical_gradient_pattern, &face);\n\t}\n\n\tif (button->title[0] != '\\033') {\n\t\tif (!_tt_font_thin) {\n\t\t\t_tt_font_thin = tt_font_from_shm(\"sans-serif\");\n\t\t}\n\t\ttt_set_size(_tt_font_thin, 13);\n\t\tint label_width = tt_string_width(_tt_font_thin, button->title);\n\t\tint centered = (button->width - label_width) / 2;\n\n\t\tint centered_y = (button->height - 16) / 2;\n\t\ttt_draw_string(ctx, _tt_font_thin, button->x + centered + (hilight == 2), button->y + centered_y + (hilight == 2) + 13, button->title, disabled ? rgb(120,120,120) : rgb(0,0,0));\n\t} else {\n\t\tsprite_t * icon = icon_get_16(button->title+1);\n\t\tint centered = button->x + (button->width - icon->width) / 2 + (hilight == 2);\n\t\tint centered_y = button->y + (button->height - icon->height) / 2 + (hilight == 2);\n\t\tif (disabled) {\n\t\t\tdraw_sprite_alpha(ctx, icon, centered, centered_y, 0.5);\n\t\t} else {\n\t\t\tdraw_sprite(ctx, icon, centered, centered_y);\n\t\t}\n\t}\n\n}\n\n"
  },
  {
    "path": "lib/confreader.c",
    "content": "/**\n * @brief Configuration File Reader\n *\n * Reads an implementation of the INI \"standard\". Note that INI\n * isn't actually a standard. We support the following:\n * - ; comments\n * - foo=bar keyword assignment\n * - [sections]\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <toaru/hashmap.h>\n#include <toaru/confreader.h>\n\n#define TRACE_APP_NAME \"confreader\"\n#define TRACE(...)\n//#include <toaru/trace.h>\n\nstatic void free_hashmap(void * h) {\n\thashmap_free(h);\n\tfree(h);\n}\n\nstatic int write_section(FILE * f, hashmap_t * section) {\n\tlist_t * keys = hashmap_keys(section);\n\tforeach(node, keys) {\n\t\tchar * key = (char*)node->value;\n\t\tchar * value = hashmap_get(section, key);\n\t\tfprintf(f, \"%s=%s\\n\", key, value);\n\t}\n\tlist_free(keys);\n\tfree(keys);\n\treturn 0;\n}\n\nint confreader_write(confreader_t * config, const char * file) {\n\n\tFILE * f = fopen(file, \"w\");\n\n\tif (!f) return 1;\n\n\thashmap_t * base = hashmap_get(config->sections, \"\");\n\tif (base) {\n\t\twrite_section(f, base);\n\t}\n\n\tlist_t * sections = hashmap_keys(config->sections);\n\tforeach(node, sections) {\n\t\tchar * section = (char*)node->value;\n\t\tif (strcmp(section,\"\")) {\n\t\t\thashmap_t * data = hashmap_get(config->sections, section);\n\t\t\tfprintf(f, \"[%s]\\n\", section);\n\t\t\twrite_section(f, data);\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nconfreader_t * confreader_create_empty(void) {\n\tconfreader_t * out = malloc(sizeof(confreader_t));\n\tout->sections = hashmap_create(10);\n\treturn out;\n}\n\nconfreader_t * confreader_load(const char * file) {\n\n\tFILE * f = fopen(file, \"r\");\n\n\tif (!f) return NULL;\n\n\tconfreader_t * out = confreader_create_empty();\n\n\thashmap_t * current_section = hashmap_create(10);\n\tcurrent_section->hash_val_free = free_hashmap;\n\n\thashmap_set(out->sections, \"\", current_section);\n\n\tif (!f) {\n\t\t/* File does not exist, no configuration values, but continue normally. */\n\t\treturn out;\n\t}\n\n\tchar tmp[1024];\n\tchar tmp2[1024];\n\n\twhile (!feof(f)) {\n\t\tint c = fgetc(f);\n\t\ttmp[0] = '\\0';\n\t\ttmp2[0] = '\\0';\n\t\tif (c == ';') {\n\t\t\tTRACE(\"Comment\");\n\t\t\twhile (!feof(f) && fgetc(f) != '\\n');\n\t\t\tTRACE(\"Done\");\n\t\t} else if (c == '\\n' || c == EOF) {\n\t\t\tTRACE(\"blank line or EOF: %d\", c);\n\t\t\tcontinue;\n\t\t} else if (c == '[') {\n\t\t\tTRACE(\"section\");\n\t\t\tchar * foo = tmp;\n\t\t\tint i;\n\t\t\twhile ((i = fgetc(f)) >= 0) {\n\t\t\t\tif (i == ']') break;\n\t\t\t\t*foo = i;\n\t\t\t\tfoo++;\n\t\t\t\t*foo = '\\0';\n\t\t\t}\n\t\t\twhile (!feof(f) && fgetc(f) != '\\n');\n\t\t\tcurrent_section = hashmap_create(10);\n\t\t\tTRACE(\"adding section %s\", tmp);\n\t\t\thashmap_set(out->sections, tmp, current_section);\n\t\t\tTRACE(\"section is over\");\n\t\t} else {\n\t\t\tTRACE(\"value\");\n\t\t\tchar * foo = tmp;\n\t\t\t*foo = c;\n\t\t\tfoo++;\n\t\t\tint i;\n\t\t\twhile ((i = fgetc(f)) >= 0) {\n\t\t\t\tif (i == '=') break;\n\t\t\t\t*foo = i;\n\t\t\t\tfoo++;\n\t\t\t\t*foo = '\\0';\n\t\t\t}\n\t\t\tif (i != '=') {\n\t\t\t\tTRACE(\"no equals sign\");\n\t\t\t\twhile (!feof(f) && fgetc(f) != '\\n');\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tTRACE(\"=\");\n\t\t\tfoo = tmp2;\n\t\t\twhile ((i = fgetc(f)) >= 0) {\n\t\t\t\tif (i == '\\n') break;\n\t\t\t\t*foo = i;\n\t\t\t\tfoo++;\n\t\t\t\t*foo = '\\0';\n\t\t\t}\n\t\t\tTRACE(\"setting value %s to %s\", tmp, tmp2);\n\t\t\thashmap_set(current_section, tmp, strdup(tmp2));\n\t\t}\n\t}\n\n\tfclose(f);\n\n\tTRACE(\"done reading\");\n\n\treturn out;\n}\n\nvoid confreader_free(confreader_t * conf) {\n\tfree_hashmap(conf->sections);\n\tfree(conf);\n}\n\nchar * confreader_get(confreader_t * ctx, char * section, char * value) {\n\tif (!ctx) return NULL;\n\n\thashmap_t * s = hashmap_get(ctx->sections, section);\n\n\tif (!s) {\n\t\tTRACE(\"section doesn't exist: %s\", section);\n\t\treturn NULL;\n\t}\n\n\treturn hashmap_get(s, value);\n}\n\nchar * confreader_getd(confreader_t * ctx, char * section, char * value, char * def) {\n\tchar * result = confreader_get(ctx, section, value);\n\treturn result ? result : def;\n}\n\nint confreader_int(confreader_t * ctx, char * section, char * value) {\n\tchar * result = confreader_get(ctx, section, value);\n\tif (!result) return 0;\n\treturn atoi(result);\n}\n\nint confreader_intd(confreader_t * ctx, char * section, char * value, int def) {\n\tchar * result = confreader_get(ctx, section, value);\n\tif (!result) return def;\n\treturn atoi(result);\n}\n\n\n\n"
  },
  {
    "path": "lib/decor-fancy.c",
    "content": "/**\n * @file lib/decor-fancy.c\n * @brief \"Fancy\" decoration theme; the default.\n *\n * This is based on an old GTK theme I used to use back in ~2010.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2021 K. Lange\n */\n#include <stdint.h>\n#include <dlfcn.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/decorations.h>\n#include <toaru/text.h>\n\n#define TTK_FANCY_PATH \"/usr/share/ttk/fancy/\"\n\n#define TITLEBAR_HEIGHT 33\n#define BASE_SIZE 10\n#define TOTAL_SCALE 1\n#define OUTER_SIZE 6\n\n/* Color for the extra border lines drawn in tiled mode. */\n#define BORDER_COLOR   rgb(62,62,62)\n\n/* Button and title colors */\n#define ACTIVE_COLOR   rgb(226,226,226)\n#define INACTIVE_COLOR rgb(147,147,147)\n\nstatic int u_height = TITLEBAR_HEIGHT * TOTAL_SCALE;\nstatic int ul_width = BASE_SIZE * TOTAL_SCALE;\nstatic int ur_width = BASE_SIZE * TOTAL_SCALE;\nstatic int ml_width = BASE_SIZE * TOTAL_SCALE;\nstatic int mr_width = BASE_SIZE * TOTAL_SCALE;\nstatic int l_height = BASE_SIZE * TOTAL_SCALE;\nstatic int ll_width = BASE_SIZE * TOTAL_SCALE;\nstatic int lr_width = BASE_SIZE * TOTAL_SCALE;\n\nstatic struct TT_Font * _tt_font = NULL;\n\n#define BUTTON_CLOSE 0\n#define BUTTON_MAXIMIZE 1\n#define BUTTON_MINIMIZE 2\n#define BUTTON_UNMAXIMIZE 3\n#define ACTIVE   4\n#define INACTIVE 13\nstatic sprite_t * sprites[22];\n\n#define TEXT_OFFSET ((window->decorator_flags & DECOR_FLAG_TILED) ? 5 : 10)\n#define BUTTON_OFFSET ((window->decorator_flags & DECOR_FLAG_TILED) ? 5 : 0)\n\n/**\n * Replaces an old graphics API function from the\n * very early days of ToaruOS...\n */\nstatic void init_sprite(int id, char * path) {\n\tsprites[id] = malloc(sizeof(sprite_t));\n\tload_sprite(sprites[id], path);\n}\n\n/**\n * Make a new sprite by cropping down @p from.\n */\nstatic sprite_t * sprite_crop(sprite_t * from, int x, int y, int w, int h) {\n\tsprite_t * dest = create_sprite(w,h,ALPHA_EMBEDDED);\n\tgfx_context_t * sctx = init_graphics_sprite(dest);\n\tdraw_fill(sctx, rgba(0,0,0,0));\n\tdraw_sprite(sctx, from, -x, -y);\n\tfree(sctx);\n\treturn dest;\n}\n\n/**\n * Chop up a spritesheet into edge/corner pieces.\n */\nstatic void create_borders_from_spritesheet(int spriteIndex, const char * path) {\n\tsprite_t tmp;\n\tload_sprite(&tmp, path);\n\n\tint um_width = 1; /* These need to always be 1... tmp.width - ul_width - ur_width; */\n\tint m_height = 1; /* These need to always be 1... tmp.height - u_height - l_height; */\n\tint lm_width = 1; /* These need to always be 1... tmp.width - ll_width - lr_width; */\n\n\tint c = ul_width;\n\tint r = tmp.width - ur_width;\n\tint m = u_height;\n\tint l = tmp.height - l_height;\n\n\tsprites[spriteIndex + 0] = sprite_crop(&tmp, 0, 0, ul_width, u_height);\n\tsprites[spriteIndex + 1] = sprite_crop(&tmp, c, 0, um_width, u_height);\n\tsprites[spriteIndex + 2] = sprite_crop(&tmp, r, 0, ur_width, u_height);\n\tsprites[spriteIndex + 3] = sprite_crop(&tmp, 0, m, ml_width, m_height);\n\tsprites[spriteIndex + 4] = sprite_crop(&tmp, r, m, mr_width, m_height);\n\tsprites[spriteIndex + 5] = sprite_crop(&tmp, 0, l, ll_width, l_height);\n\tsprites[spriteIndex + 6] = sprite_crop(&tmp, c, l, lm_width, l_height);\n\tsprites[spriteIndex + 7] = sprite_crop(&tmp, r, l, lr_width, l_height);\n\n\tfree(tmp.bitmap);\n}\n\nstatic int get_bounds_fancy(yutani_window_t * window, struct decor_bounds * bounds) {\n\tif (window == NULL || !(window->decorator_flags & DECOR_FLAG_TILED)) {\n\t\tbounds->top_height    = TITLEBAR_HEIGHT * TOTAL_SCALE;\n\t\tbounds->bottom_height = OUTER_SIZE * TOTAL_SCALE;\n\t\tbounds->left_width    = OUTER_SIZE * TOTAL_SCALE;\n\t\tbounds->right_width   = OUTER_SIZE * TOTAL_SCALE;\n\t} else {\n\t\t/* Any \"exposed\" edge gets an extra pixel. */\n\t\tbounds->top_height = 27 * TOTAL_SCALE + !(window->decorator_flags & DECOR_FLAG_TILE_UP);\n\t\tbounds->bottom_height   = !(window->decorator_flags & DECOR_FLAG_TILE_DOWN);\n\t\tbounds->left_width      = !(window->decorator_flags & DECOR_FLAG_TILE_LEFT);\n\t\tbounds->right_width     = !(window->decorator_flags & DECOR_FLAG_TILE_RIGHT);\n\t}\n\n\tbounds->width = bounds->left_width + bounds->right_width;\n\tbounds->height = bounds->top_height + bounds->bottom_height;\n\treturn 0;\n}\n\n#define BUTTON_PAD 5\n\nstatic void render_decorations_fancy(yutani_window_t * window, gfx_context_t * ctx, char * title, int decors_active) {\n\tint width = window->width;\n\tint height = window->height;\n\n\tstruct decor_bounds bounds;\n\tget_bounds_fancy(window, &bounds);\n\n\tfor (int j = 0; j < (int)bounds.top_height; ++j) {\n\t\tfor (int i = 0; i < width; ++i) {\n\t\t\tGFX(ctx,i,j) = 0;\n\t\t}\n\t}\n\n\tdecors_active = (decors_active == DECOR_INACTIVE) ? INACTIVE : ACTIVE;\n\n\tif ((window->decorator_flags & DECOR_FLAG_TILED)) {\n\t\tfor (int i = 0; i < width; ++i) {\n\t\t\tdraw_sprite(ctx, sprites[decors_active + 1], i, -6 * TOTAL_SCALE + !(window->decorator_flags & DECOR_FLAG_TILE_UP));\n\t\t}\n\n\t\tuint32_t clear_color = BORDER_COLOR;\n\t\tif (!(window->decorator_flags & DECOR_FLAG_TILE_DOWN)) {\n\t\t\t/* Draw bottom line */\n\t\t\tfor (int i = 0; i < (int)window->width; ++i) {\n\t\t\t\tGFX(ctx,i,window->height-1) = clear_color;\n\t\t\t}\n\t\t}\n\n\t\tif (!(window->decorator_flags & DECOR_FLAG_TILE_LEFT)) {\n\t\t\t/* Draw left line */\n\t\t\tfor (int i = 0; i < (int)window->height; ++i) {\n\t\t\t\tGFX(ctx,0,i) = clear_color;\n\t\t\t}\n\t\t}\n\n\t\tif (!(window->decorator_flags & DECOR_FLAG_TILE_RIGHT)) {\n\t\t\t/* Draw right line */\n\t\t\tfor (int i = 0; i < (int)window->height; ++i) {\n\t\t\t\tGFX(ctx,window->width-1,i) = clear_color;\n\t\t\t}\n\t\t}\n\n\t} else {\n\n\t\tuint32_t clear_color = 0x000000;\n\n\t\tfor (int j = (int)bounds.top_height; j < height - (int)bounds.bottom_height; ++j) {\n\t\t\tfor (int i = 0; i < (int)bounds.left_width; ++i) {\n\t\t\t\tGFX(ctx,i,j) = clear_color;\n\t\t\t}\n\t\t\tfor (int i = width - (int)bounds.right_width; i < width; ++i) {\n\t\t\t\tGFX(ctx,i,j) = clear_color;\n\t\t\t}\n\t\t}\n\n\t\tfor (int j = height - (int)bounds.bottom_height; j < height; ++j) {\n\t\t\tfor (int i = 0; i < width; ++i) {\n\t\t\t\tGFX(ctx,i,j) = clear_color;\n\t\t\t}\n\t\t}\n\n\t\tdraw_sprite(ctx, sprites[decors_active + 0], 0, 0);\n\t\tfor (int i = 0; i < width - (ul_width + ur_width); ++i) {\n\t\t\tdraw_sprite(ctx, sprites[decors_active + 1], i + ul_width, 0);\n\t\t}\n\t\tdraw_sprite(ctx, sprites[decors_active + 2], width - ur_width, 0);\n\t\tfor (int i = 0; i < height - (u_height + l_height); ++i) {\n\t\t\tdraw_sprite(ctx, sprites[decors_active + 3], 0, i + u_height);\n\t\t\tdraw_sprite(ctx, sprites[decors_active + 4], width - mr_width, i + u_height);\n\t\t}\n\t\tdraw_sprite(ctx, sprites[decors_active + 5], 0, height - l_height);\n\t\tfor (int i = 0; i < width - (ll_width + lr_width); ++i) {\n\t\t\tdraw_sprite(ctx, sprites[decors_active + 6], i + ll_width, height - l_height);\n\t\t}\n\t\tdraw_sprite(ctx, sprites[decors_active + 7], width - lr_width, height - l_height);\n\t}\n\n#define EXTRA_SPACE 120\n\n\tuint32_t title_color = (decors_active == ACTIVE) ? ACTIVE_COLOR : INACTIVE_COLOR;\n\n\tint buttons_width = (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) ? 72 : 28;\n\tint usable_width = width - bounds.width - (2 * buttons_width + 10) * TOTAL_SCALE;\n\n\ttt_set_size(_tt_font, 12 * TOTAL_SCALE);\n\tint title_width = tt_string_width(_tt_font, title);\n\tif (title_width > usable_width) {\n\t\tusable_width += buttons_width * TOTAL_SCALE;\n\t\tif (usable_width > 0) {\n\t\t\tchar * tmp_title = tt_ellipsify(title, 12 * TOTAL_SCALE, _tt_font, usable_width, &title_width);\n\t\t\tint title_offset = bounds.left_width + 10 * TOTAL_SCALE;\n\t\t\ttt_draw_string(ctx, _tt_font, title_offset, (TEXT_OFFSET + 14) * TOTAL_SCALE, tmp_title, title_color);\n\t\t\tfree(tmp_title);\n\t\t}\n\t} else {\n\t\tint title_offset = buttons_width * TOTAL_SCALE + bounds.left_width + 10 * TOTAL_SCALE + (usable_width / 2) - (title_width / 2);\n\t\ttt_draw_string(ctx, _tt_font, title_offset, (TEXT_OFFSET + 14) * TOTAL_SCALE, title, title_color);\n\t}\n\n\tuint32_t h_color = rgb(100,100,100);\n\tuint32_t i_color = (decor_hover_window == window && decor_hover_button) ? ACTIVE_COLOR : title_color;\n\n\tif (width + (BUTTON_OFFSET - 28) * TOTAL_SCALE > bounds.left_width) {\n\t\tif (decor_hover_window == window && decor_hover_button == DECOR_CLOSE) {\n\t\t\tdraw_rounded_rectangle(ctx,\n\t\t\t\twidth + (BUTTON_OFFSET - 28 - BUTTON_PAD) * TOTAL_SCALE,\n\t\t\t\t(16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color);\n\t\t}\n\t\tdraw_sprite_alpha_paint(ctx, sprites[BUTTON_CLOSE],\n\t\t\twidth + (BUTTON_OFFSET - 28) * TOTAL_SCALE,\n\t\t\t(16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color);\n\n\t\tif (width + (BUTTON_OFFSET - 50) * TOTAL_SCALE > bounds.left_width) {\n\t\t\tif (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) {\n\t\t\t\tif (decor_hover_window == window && decor_hover_button == DECOR_MAXIMIZE) {\n\t\t\t\t\tdraw_rounded_rectangle(ctx,\n\t\t\t\t\t\twidth + (BUTTON_OFFSET - 50 - BUTTON_PAD) * TOTAL_SCALE,\n\t\t\t\t\t\t(16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color);\n\t\t\t\t}\n\t\t\t\tdraw_sprite_alpha_paint(ctx, sprites[(window->decorator_flags & DECOR_FLAG_TILED) ? BUTTON_UNMAXIMIZE : BUTTON_MAXIMIZE],\n\t\t\t\t\twidth + (BUTTON_OFFSET - 50) * TOTAL_SCALE,\n\t\t\t\t\t(16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color);\n\n\t\t\t\tif (width + (BUTTON_OFFSET - 72) * TOTAL_SCALE > bounds.left_width) {\n\t\t\t\t\tif (decor_hover_window == window && decor_hover_button == DECOR_MINIMIZE) {\n\t\t\t\t\t\tdraw_rounded_rectangle(ctx,\n\t\t\t\t\t\t\twidth + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE,\n\t\t\t\t\t\t\t(16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color);\n\t\t\t\t\t}\n\t\t\t\t\tdraw_sprite_alpha_paint(ctx, sprites[BUTTON_MINIMIZE],\n\t\t\t\t\t\twidth + (BUTTON_OFFSET - 72) * TOTAL_SCALE,\n\t\t\t\t\t\t(16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic int check_button_press_fancy(yutani_window_t * window, int x, int y) {\n\tif (y >= (16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE && y <= (16 - BUTTON_OFFSET + 8 + BUTTON_PAD) * TOTAL_SCALE ) {\n\t\tif (x >= (int)window->width + (BUTTON_OFFSET - 28 - BUTTON_PAD) * TOTAL_SCALE &&\n\t\t\tx <= (int)window->width + (BUTTON_OFFSET - 28 + 8 + BUTTON_PAD) * TOTAL_SCALE) {\n\t\t\treturn DECOR_CLOSE;\n\t\t}\n\n\t\tif (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) {\n\t\t\tif (x >= (int)window->width + (BUTTON_OFFSET - 50 - BUTTON_PAD) * TOTAL_SCALE &&\n\t\t\t\tx <= (int)window->width + (BUTTON_OFFSET - 50 + 8 + BUTTON_PAD) * TOTAL_SCALE) {\n\t\t\t\treturn DECOR_MAXIMIZE;\n\t\t\t}\n\n\t\t\tif (x >= (int)window->width + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE &&\n\t\t\t\tx <= (int)window->width + (BUTTON_OFFSET - 72 + 8 + BUTTON_PAD) * TOTAL_SCALE) {\n\t\t\t\treturn DECOR_MINIMIZE;\n\t\t\t}\n\t\t}\n\n\t\tif (x >= (int)window->width + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE &&\n\t\t\tx <= (int)window->width + (BUTTON_OFFSET - 28 + 8 + BUTTON_PAD) * TOTAL_SCALE) {\n\t\t\treturn DECOR_OTHER;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nvoid decor_init() {\n\tinit_sprite(BUTTON_CLOSE, TTK_FANCY_PATH \"button-close.png\");\n\tinit_sprite(BUTTON_MAXIMIZE, TTK_FANCY_PATH \"button-maximize.png\");\n\tinit_sprite(BUTTON_MINIMIZE, TTK_FANCY_PATH \"button-minimize.png\");\n\tinit_sprite(BUTTON_UNMAXIMIZE, TTK_FANCY_PATH \"button-unmaximize.png\");\n\n\tcreate_borders_from_spritesheet(ACTIVE, TTK_FANCY_PATH \"borders-active.png\");\n\tcreate_borders_from_spritesheet(INACTIVE, TTK_FANCY_PATH \"borders-inactive.png\");\n\n\tdecor_render_decorations = render_decorations_fancy;\n\tdecor_check_button_press = check_button_press_fancy;\n\tdecor_get_bounds = get_bounds_fancy;\n\n\t_tt_font = tt_font_from_shm(\"sans-serif.bold\");\n}\n\n"
  },
  {
    "path": "lib/decorations.c",
    "content": "/**\n * @brief Client-side Window Decoration library\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n#include <stdint.h>\n#include <math.h>\n#include <dlfcn.h>\n\n#include <toaru/graphics.h>\n#include <toaru/yutani.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n\n#define TEXT_OFFSET_X 10\n#define TEXT_OFFSET_Y 3\n\n#define BORDERCOLOR rgb(59,59,59)\n#define BORDERCOLOR_INACTIVE rgb(30,30,30)\n#define TEXTCOLOR rgb(230,230,230)\n#define TEXTCOLOR_INACTIVE rgb(140,140,140)\n\nvoid (*decor_render_decorations)(yutani_window_t *, gfx_context_t *, char *, int) = NULL;\nint  (*decor_check_button_press)(yutani_window_t *, int x, int y) = NULL;\nint  (*decor_get_bounds)(yutani_window_t *, struct decor_bounds *) = NULL;\n\nstatic void (*callback_close)(yutani_window_t *) = NULL;\nstatic void (*callback_resize)(yutani_window_t *) = NULL;\nstatic void (*callback_maximize)(yutani_window_t *) = NULL;\n\nstatic int close_enough(struct yutani_msg_window_mouse_event * me) {\n\treturn (me->command == YUTANI_MOUSE_EVENT_RAISE &&\n\t\t\tsqrt(pow(me->new_x - me->old_x, 2.0) + pow(me->new_y - me->old_y, 2.0)) < 10.0);\n}\n\nstatic struct TT_Font * tt_font = NULL;\n\nstatic void render_decorations_simple(yutani_window_t * window, gfx_context_t * ctx, char * title, int decors_active) {\n\n\tuint32_t color = BORDERCOLOR;\n\tif (decors_active == DECOR_INACTIVE) {\n\t\tcolor = BORDERCOLOR_INACTIVE;\n\t}\n\n\n\tfor (int i = 0; i < (int)window->height; ++i) {\n\t\tGFX(ctx, 0, i) = color;\n\t\tGFX(ctx, window->width - 1, i) = color;\n\t}\n\n\tfor (int i = 1; i < (int)24; ++i) {\n\t\tfor (int j = 1; j < (int)window->width - 1; ++j) {\n\t\t\tGFX(ctx, j, i) = color;\n\t\t}\n\t}\n\n\ttt_set_size(tt_font, 12);\n\tuint32_t textcolor = (decors_active == DECOR_INACTIVE) ? TEXTCOLOR_INACTIVE : TEXTCOLOR;\n\ttt_draw_string(ctx, tt_font, TEXT_OFFSET_X, TEXT_OFFSET_Y + 12, title, textcolor);\n\ttt_draw_string(ctx, tt_font, window->width - 20, TEXT_OFFSET_Y + 12, \"x\", textcolor);\n\n\tfor (uint32_t i = 0; i < window->width; ++i) {\n\t\tGFX(ctx, i, 0) = color;\n\t\tGFX(ctx, i, 24 - 1) = color;\n\t\tGFX(ctx, i, window->height - 1) = color;\n\t}\n}\n\nstatic int check_button_press_simple(yutani_window_t * window, int x, int y) {\n\tif (x >= (int)window->width - 20 && x <= (int)window->width - 2 && y >= 2) {\n\t\treturn DECOR_CLOSE;\n\t}\n\n\treturn 0;\n}\n\nstatic int get_bounds_simple(yutani_window_t * window, struct decor_bounds * bounds) {\n\t/* Does not change with window state */\n\tbounds->top_height = 24;\n\tbounds->bottom_height = 1;\n\tbounds->left_width = 1;\n\tbounds->right_width = 1;\n\n\tbounds->width = bounds->left_width + bounds->right_width;\n\tbounds->height = bounds->top_height + bounds->bottom_height;\n\n\treturn 0;\n}\n\nstatic void initialize_simple() {\n\tdecor_render_decorations = render_decorations_simple;\n\tdecor_check_button_press = check_button_press_simple;\n\tdecor_get_bounds         = get_bounds_simple;\n\ttt_font = tt_font_from_shm(\"sans-serif\");\n}\n\nvoid render_decorations(yutani_window_t * window, gfx_context_t * ctx, char * title) {\n\tif (!window) return;\n\twindow->decorator_flags |= DECOR_FLAG_DECORATED;\n\tif (window->focused || !hashmap_is_empty(menu_get_windows_hash())) {\n\t\tdecor_render_decorations(window, ctx, title, DECOR_ACTIVE);\n\t} else {\n\t\tdecor_render_decorations(window, ctx, title, DECOR_INACTIVE);\n\t}\n}\n\nvoid render_decorations_inactive(yutani_window_t * window, gfx_context_t * ctx, char * title) {\n\tif (!window) return;\n\twindow->decorator_flags |= DECOR_FLAG_DECORATED;\n\tdecor_render_decorations(window, ctx, title, DECOR_INACTIVE);\n}\n\nstatic void _decor_maximize(yutani_t * yctx, yutani_window_t * window) {\n\tif (callback_maximize) {\n\t\tcallback_maximize(window);\n\t} else {\n\t\tyutani_special_request(yctx, window, YUTANI_SPECIAL_REQUEST_MAXIMIZE);\n\t}\n}\n\nstatic void _decor_minimize(yutani_t * yctx, yutani_window_t * window) {\n\tyutani_special_request(yctx, window, YUTANI_SPECIAL_REQUEST_MINIMIZE);\n}\n\nstatic yutani_window_t * _decor_menu_owner_window = NULL;\nstatic struct MenuList * _decor_menu = NULL;\n\nstatic void _decor_start_move(struct MenuEntry * self) {\n\tif (!_decor_menu_owner_window)\n\t\treturn;\n\tyutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid);\n\tyutani_window_drag_start(_decor_menu_owner_window->ctx, _decor_menu_owner_window);\n}\n\nstatic void _decor_start_maximize(struct MenuEntry * self) {\n\tif (!_decor_menu_owner_window)\n\t\treturn;\n\t_decor_maximize(_decor_menu_owner_window->ctx, _decor_menu_owner_window);\n\tyutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid);\n}\n\nstatic void _decor_start_minimize(struct MenuEntry * self) {\n\tif (!_decor_menu_owner_window)\n\t\treturn;\n\t_decor_minimize(_decor_menu_owner_window->ctx, _decor_menu_owner_window);\n}\n\nstatic void _decor_close(struct MenuEntry * self) {\n\tif (!_decor_menu_owner_window)\n\t\treturn;\n\n\tyutani_special_request(_decor_menu_owner_window->ctx, _decor_menu_owner_window, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE);\n}\n\nyutani_window_t * decor_show_default_menu(yutani_window_t * window, int x, int y) {\n\tif (_decor_menu->window) return NULL;\n\t_decor_menu_owner_window = window;\n\tmenu_show_at(_decor_menu, window, x - window->x, y - window->y);\n\treturn _decor_menu->window;\n}\n\nvoid init_decorations() {\n\tchar * tmp = getenv(\"WM_THEME\");\n\tchar * theme = tmp ? strdup(tmp) : NULL;\n\n\t_decor_menu = menu_create();\n\tmenu_insert(_decor_menu, menu_create_normal(NULL, NULL, \"Maximize\", _decor_start_maximize));\n\tmenu_insert(_decor_menu, menu_create_normal(NULL, NULL, \"Minimize\", _decor_start_minimize));\n\tmenu_insert(_decor_menu, menu_create_normal(NULL, NULL, \"Move\", _decor_start_move));\n\tmenu_insert(_decor_menu, menu_create_separator());\n\tmenu_insert(_decor_menu, menu_create_normal(NULL, NULL, \"Close\", _decor_close));\n\n\tif (!theme || !strcmp(theme, \"simple\")) {\n\t\tinitialize_simple();\n\t} else {\n\t\tchar * options = strchr(theme,',');\n\t\tif (options) {\n\t\t\t*options = '\\0';\n\t\t\toptions++;\n\t\t}\n\t\tchar lib_name[100];\n\t\tsprintf(lib_name, \"libtoaru_decor-%s.so\", theme);\n\t\tvoid * theme_lib = dlopen(lib_name, 0);\n\t\tif (!theme_lib) {\n\t\t\tgoto _theme_error;\n\t\t}\n\t\tvoid (*theme_init)(char *) = dlsym(theme_lib, \"decor_init\");\n\t\tif (!theme_init) {\n\t\t\tgoto _theme_error;\n\t\t}\n\t\ttheme_init(options);\n\t\treturn;\n\n_theme_error:\n\t\t\tfprintf(stderr, \"decorations: could not load theme `%s`: %s\\n\", theme, dlerror());\n\t\t\tinitialize_simple();\n\t}\n}\n\nvoid decor_set_close_callback(void (*callback)(yutani_window_t *)) {\n\tcallback_close = callback;\n}\n\nvoid decor_set_resize_callback(void (*callback)(yutani_window_t *)) {\n\tcallback_resize = callback;\n}\n\nvoid decor_set_maximize_callback(void (*callback)(yutani_window_t *)) {\n\tcallback_maximize = callback;\n}\n\nstatic int within_decors(yutani_window_t * window, int x, int y) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\n\tif ((x <= (int)bounds.left_width || x >= (int)window->width - (int)bounds.right_width) && (x > 0 && x < (int)window->width)) return 1;\n\tif ((y <= (int)bounds.top_height || y >= (int)window->height - (int)bounds.bottom_height) && (y > 0 && y < (int)window->height)) return 1;\n\treturn 0;\n}\n\n#define LEFT_SIDE (me->new_x <= (int)bounds.left_width)\n#define RIGHT_SIDE (me->new_x >= (int)window->width - (int)bounds.right_width)\n#define TOP_SIDE (me->new_y <= (int)bounds.top_height)\n#define BOTTOM_SIDE (me->new_y >= (int)window->height - (int)bounds.bottom_height)\n\nstatic yutani_scale_direction_t check_resize_direction(struct yutani_msg_window_mouse_event * me, yutani_window_t * window) {\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window, &bounds);\n\tyutani_scale_direction_t resize_direction = SCALE_NONE;\n\n\tif (LEFT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) {\n\t\tresize_direction = SCALE_LEFT;\n\t} else if (RIGHT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) {\n\t\tresize_direction = SCALE_RIGHT;\n\t} else if (BOTTOM_SIDE && !LEFT_SIDE && !RIGHT_SIDE) {\n\t\tresize_direction = SCALE_DOWN;\n\t} else if (BOTTOM_SIDE && LEFT_SIDE) {\n\t\tresize_direction = SCALE_DOWN_LEFT;\n\t} else if (BOTTOM_SIDE && RIGHT_SIDE) {\n\t\tresize_direction = SCALE_DOWN_RIGHT;\n\t} else if (TOP_SIDE && LEFT_SIDE) {\n\t\tresize_direction = SCALE_UP_LEFT;\n\t} else if (TOP_SIDE && RIGHT_SIDE) {\n\t\tresize_direction = SCALE_UP_RIGHT;\n\t} else if (TOP_SIDE && (me->new_y < 5)) {\n\t\tresize_direction = SCALE_UP;\n\t}\n\treturn resize_direction;\n}\n\nstatic yutani_scale_direction_t old_resize_direction = SCALE_NONE;\nint decor_hover_button = 0;\nyutani_window_t * decor_hover_window = NULL;\n\nint decor_handle_event(yutani_t * yctx, yutani_msg_t * m) {\n\tif (m) {\n\t\tswitch (m->type) {\n\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\tyutani_window_t * window = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid);\n\t\t\t\t\tstruct decor_bounds bounds;\n\t\t\t\t\tdecor_get_bounds(window, &bounds);\n\t\t\t\t\tif (!window) return 0;\n\t\t\t\t\tif (!(window->decorator_flags & DECOR_FLAG_DECORATED)) return 0;\n\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_LEAVE && decor_hover_window == window) {\n\t\t\t\t\t\tdecor_hover_window = NULL;\n\t\t\t\t\t\tdecor_hover_button = 0;\n\t\t\t\t\t\tyutani_internal_refocus(yctx, window);\n\t\t\t\t\t\treturn DECOR_REDRAW;\n\t\t\t\t\t}\n\t\t\t\t\tif (within_decors(window, me->new_x, me->new_y)) {\n\t\t\t\t\t\tint button = decor_check_button_press(window, me->new_x, me->new_y);\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t\t\t\t\t\tif (!button || button == DECOR_OTHER) {\n\t\t\t\t\t\t\t\t/* Resize edges */\n\t\t\t\t\t\t\t\tyutani_scale_direction_t resize_direction = check_resize_direction(me, window);\n\n\t\t\t\t\t\t\t\tif (resize_direction != SCALE_NONE) {\n\t\t\t\t\t\t\t\t\tyutani_window_resize_start(yctx, window, resize_direction);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (me->new_y < (int)bounds.top_height && resize_direction == SCALE_NONE) {\n\t\t\t\t\t\t\t\t\tyutani_window_drag_start(yctx, window);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn DECOR_OTHER;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!button && (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT)) {\n\t\t\t\t\t\t\treturn DECOR_RIGHT;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_MOVE) {\n\t\t\t\t\t\t\tif (!button) {\n\t\t\t\t\t\t\t\t/* Resize edges */\n\t\t\t\t\t\t\t\tyutani_scale_direction_t resize_direction = check_resize_direction(me, window);\n\t\t\t\t\t\t\t\tif (resize_direction != old_resize_direction) {\n\t\t\t\t\t\t\t\t\tif (resize_direction == SCALE_NONE) {\n\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tswitch (resize_direction) {\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_UP:\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_DOWN:\n\t\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_VERTICAL);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_LEFT:\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_RIGHT:\n\t\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_DOWN_RIGHT:\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_UP_LEFT:\n\t\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_DOWN_LEFT:\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_UP_RIGHT:\n\t\t\t\t\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP);\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_AUTO:\n\t\t\t\t\t\t\t\t\t\t\tcase SCALE_NONE:\n\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\told_resize_direction = resize_direction;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (old_resize_direction != SCALE_NONE) {\n\t\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t\t\told_resize_direction = SCALE_NONE;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (me->command == YUTANI_MOUSE_EVENT_CLICK || close_enough(me)) {\n\t\t\t\t\t\t\t/* Determine if we clicked on a button */\n\t\t\t\t\t\t\tswitch (button) {\n\t\t\t\t\t\t\t\tcase DECOR_CLOSE:\n\t\t\t\t\t\t\t\t\tif (callback_close) callback_close(window);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_RESIZE:\n\t\t\t\t\t\t\t\t\tif (callback_resize) callback_resize(window);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_MAXIMIZE:\n\t\t\t\t\t\t\t\t\t_decor_maximize(yctx, window);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase DECOR_MINIMIZE:\n\t\t\t\t\t\t\t\t\t_decor_minimize(yctx, window);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdecor_hover_window = NULL;\n\t\t\t\t\t\t\tdecor_hover_button = 0;\n\t\t\t\t\t\t\tyutani_internal_refocus(yctx, window);\n\t\t\t\t\t\t\treturn button;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (button != decor_hover_button || window != decor_hover_window) {\n\t\t\t\t\t\t\tdecor_hover_button = button;\n\t\t\t\t\t\t\tdecor_hover_window = window;\n\t\t\t\t\t\t\tyutani_internal_refocus(yctx, window);\n\t\t\t\t\t\t\treturn DECOR_REDRAW;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (old_resize_direction != SCALE_NONE) {\n\t\t\t\t\t\t\tyutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET);\n\t\t\t\t\t\t\told_resize_direction = SCALE_NONE;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (decor_hover_window == window) {\n\t\t\t\t\t\t\tdecor_hover_button = 0;\n\t\t\t\t\t\t\tdecor_hover_window = NULL;\n\t\t\t\t\t\t\tyutani_internal_refocus(yctx, window);\n\t\t\t\t\t\t\treturn DECOR_REDRAW;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "lib/graphics.c",
    "content": "/**\n * @brief Generic Graphics library for ToaruOS\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2021 K. Lange\n */\n#include <stdint.h>\n#include <string.h>\n#include <stdio.h>\n#include <math.h>\n#include <fcntl.h>\n#include <dlfcn.h>\n\n#include <sys/ioctl.h>\n\n#if !defined(NO_SSE) && defined(__x86_64__)\n#include <xmmintrin.h>\n#include <emmintrin.h>\n#endif\n\n#include <kernel/video.h>\n\n#include <toaru/graphics.h>\n\nstatic inline int32_t min(int32_t a, int32_t b) {\n\treturn (a < b) ? a : b;\n}\n\nstatic inline int32_t max(int32_t a, int32_t b) {\n\treturn (a > b) ? a : b;\n}\n\nstatic inline uint16_t min16(uint16_t a, uint16_t b) {\n\treturn (a < b) ? a : b;\n}\n\nstatic inline uint16_t max16(uint16_t a, uint16_t b) {\n\treturn (a > b) ? a : b;\n}\n\n#define fmax(a,b) ((a) > (b) ? (a) : (b))\n#define fmin(a,b) ((a) < (b) ? (a) : (b))\n\nstatic inline int _is_in_clip(gfx_context_t * ctx, int32_t y) {\n\tif (!ctx->clips) return 1;\n\tif (y < 0 || y >= ctx->clips_size) return 1;\n\treturn ctx->clips[y];\n}\n\n\nvoid gfx_add_clip(gfx_context_t * ctx, int32_t x, int32_t y, int32_t w, int32_t h) {\n\t(void)x;\n\t(void)w; // TODO Horizontal clipping\n\tif (!ctx->clips) {\n\t\tctx->clips = malloc(ctx->height);\n\t\tmemset(ctx->clips, 0, ctx->height);\n\t\tctx->clips_size = ctx->height;\n\t}\n\tfor (int i = max(y,0); i < min(y+h,ctx->clips_size); ++i) {\n\t\tctx->clips[i] = 1;\n\t}\n}\n\nvoid gfx_clear_clip(gfx_context_t * ctx) {\n\tif (ctx->clips) {\n\t\tmemset(ctx->clips, 0, ctx->clips_size);\n\t}\n}\n\nvoid gfx_no_clip(gfx_context_t * ctx) {\n\tvoid * tmp = ctx->clips;\n\tif (!tmp) return;\n\tctx->clips = NULL;\n\tfree(tmp);\n}\n\n/* Pointer to graphics memory */\nvoid flip(gfx_context_t * ctx) {\n\tif (ctx->clips) {\n\t\tfor (size_t i = 0; i < ctx->height; ++i) {\n\t\t\tif (_is_in_clip(ctx,i)) {\n\t\t\t\tmemcpy(&ctx->buffer[i*GFX_S(ctx)], &ctx->backbuffer[i*GFX_S(ctx)], 4 * ctx->width);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tmemcpy(ctx->buffer, ctx->backbuffer, ctx->size);\n\t}\n}\n\nvoid gfx_flip_24bit(gfx_context_t * ctx) {\n\tfor (size_t y = 0; y < ctx->height; ++y) {\n\t\tif (_is_in_clip(ctx,y)) {\n\t\t\tfor (size_t x = 0; x < ctx->width; ++x) {\n\t\t\t\t((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4];\n\t\t\t\t((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3+1] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4+1];\n\t\t\t\t((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3+2] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4+2];\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid clearbuffer(gfx_context_t * ctx) {\n\tmemset(ctx->backbuffer, 0, ctx->size);\n}\n\n/* Deprecated */\nstatic int framebuffer_fd = 0;\ngfx_context_t * init_graphics_fullscreen() {\n\tgfx_context_t * out = malloc(sizeof(gfx_context_t));\n\tout->clips = NULL;\n\tout->buffer = NULL;\n\n\tif (!framebuffer_fd) {\n\t\tframebuffer_fd = open(\"/dev/fb0\", 0, 0);\n\t}\n\tif (framebuffer_fd < 0) {\n\t\t/* oh shit */\n\t\tfree(out);\n\t\treturn NULL;\n\t}\n\n\tioctl(framebuffer_fd, IO_VID_WIDTH,  &out->width);\n\tioctl(framebuffer_fd, IO_VID_HEIGHT, &out->height);\n\tioctl(framebuffer_fd, IO_VID_DEPTH,  &out->depth);\n\tioctl(framebuffer_fd, IO_VID_STRIDE, &out->stride);\n\tioctl(framebuffer_fd, IO_VID_ADDR,   &out->buffer);\n\tioctl(framebuffer_fd, IO_VID_SIGNAL, NULL);\n\n\tout->size   = GFX_H(out) * GFX_S(out);\n\n\tif (out->depth == 24) {\n\t\tout->depth = 32;\n\t\tout->_true_stride = out->stride;\n\t\tout->stride = 4 * GFX_W(out);\n\t\tout->size = 0;\n\t}\n\n\tout->backbuffer = out->buffer;\n\treturn out;\n}\n\nuint32_t framebuffer_stride(void) {\n\tuint32_t stride;\n\tioctl(framebuffer_fd, IO_VID_STRIDE, &stride);\n\treturn stride;\n}\n\ngfx_context_t * init_graphics_fullscreen_double_buffer() {\n\tgfx_context_t * out = init_graphics_fullscreen();\n\tif (!out) return NULL;\n\tout->backbuffer = malloc(GFX_S(out) * GFX_H(out));\n\treturn out;\n}\n\ngfx_context_t * init_graphics_subregion(gfx_context_t * base, int x, int y, int width, int height) {\n\tgfx_context_t * out = malloc(sizeof(gfx_context_t));\n\n\tout->clips = NULL;\n\tout->depth = 32;\n\n\tout->width = width;\n\tout->height = height;\n\tout->stride = base->stride;\n\tout->backbuffer = base->backbuffer + (base->stride * y) + x * 4;\n\tout->buffer = base->buffer + (base->stride * y) + x * 4;\n\n\tif (base->clips) {\n\t\tfor (int _y = 0; _y < height; ++_y) {\n\t\t\tif (_is_in_clip(base, y + _y)) {\n\t\t\t\tgfx_add_clip(out,0,_y,width,1);\n\t\t\t}\n\t\t}\n\t}\n\n\tout->size = 0; /* don't allow flip or clear operations */\n\treturn out;\n}\n\nvoid reinit_graphics_fullscreen(gfx_context_t * out) {\n\n\tioctl(framebuffer_fd, IO_VID_WIDTH,  &out->width);\n\tioctl(framebuffer_fd, IO_VID_HEIGHT, &out->height);\n\tioctl(framebuffer_fd, IO_VID_DEPTH,  &out->depth);\n\tioctl(framebuffer_fd, IO_VID_STRIDE, &out->stride);\n\n\tout->size   = GFX_H(out) * GFX_S(out);\n\n\tif (out->clips && out->clips_size != out->height) {\n\t\tfree(out->clips);\n\t\tout->clips = NULL;\n\t\tout->clips_size = 0;\n\t}\n\n\tif (out->buffer != out->backbuffer) {\n\t\tioctl(framebuffer_fd, IO_VID_ADDR,   &out->buffer);\n\t\tout->backbuffer = realloc(out->backbuffer, GFX_S(out) * GFX_H(out));\n\t} else {\n\t\tioctl(framebuffer_fd, IO_VID_ADDR,   &out->buffer);\n\t\tout->backbuffer = out->buffer;\n\t}\n\n}\n\ngfx_context_t * init_graphics_sprite(sprite_t * sprite) {\n\tgfx_context_t * out = malloc(sizeof(gfx_context_t));\n\tout->clips = NULL;\n\n\tout->width  = sprite->width;\n\tout->stride = sprite->width * sizeof(uint32_t);\n\tout->height = sprite->height;\n\tout->depth  = 32;\n\tout->size   = GFX_H(out) * GFX_W(out) * GFX_B(out);\n\tout->buffer = (char *)sprite->bitmap;\n\tout->backbuffer = out->buffer;\n\n\treturn out;\n}\n\nsprite_t * create_sprite(size_t width, size_t height, int alpha) {\n\tsprite_t * out = malloc(sizeof(sprite_t));\n\n\t/*\n\tuint16_t width;\n\tuint16_t height;\n\tuint32_t * bitmap;\n\tuint32_t * masks;\n\tuint32_t blank;\n\tuint8_t  alpha;\n\t*/\n\n\tout->width  = width;\n\tout->height = height;\n\tout->bitmap = malloc(sizeof(uint32_t) * out->width * out->height);\n\tout->masks  = NULL;\n\tout->blank  = 0x00000000;\n\tout->alpha  = alpha;\n\n\treturn out;\n}\n\nvoid sprite_free(sprite_t * sprite) {\n\tif (sprite->masks) {\n\t\tfree(sprite->masks);\n\t}\n\tfree(sprite->bitmap);\n\tfree(sprite);\n}\n\ninline uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) {\n\treturn 0xFF000000 | (r << 16) | (g << 8) | (b);\n}\n\ninline uint32_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {\n\treturn (a << 24U) | (r << 16) | (g << 8) | (b);\n}\n\nuint32_t alpha_blend(uint32_t bottom, uint32_t top, uint32_t mask) {\n\tuint8_t a = _RED(mask);\n\tuint8_t red = (_RED(bottom) * (255 - a) + _RED(top) * a) / 255;\n\tuint8_t gre = (_GRE(bottom) * (255 - a) + _GRE(top) * a) / 255;\n\tuint8_t blu = (_BLU(bottom) * (255 - a) + _BLU(top) * a) / 255;\n\tuint8_t alp = (int)a + (int)_ALP(bottom) > 255 ? 255 : a + _ALP(bottom);\n\treturn rgba(red,gre,blu, alp);\n}\n\ninline uint32_t alpha_blend_rgba(uint32_t bottom, uint32_t top) {\n\tif (_ALP(bottom) == 0) return top;\n\tif (_ALP(top) == 255) return top;\n\tif (_ALP(top) == 0) return bottom;\n\tuint8_t a = _ALP(top);\n\tuint16_t t = 0xFF ^ a;\n\tuint8_t d_r = _RED(top) + (((uint32_t)(_RED(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_g = _GRE(top) + (((uint32_t)(_GRE(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_b = _BLU(top) + (((uint32_t)(_BLU(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_a = _ALP(top) + (((uint32_t)(_ALP(bottom) * t + 0x80) * 0x101) >> 16UL);\n\treturn rgba(d_r, d_g, d_b, d_a);\n}\n\n\nuint32_t premultiply(uint32_t color) {\n\tuint16_t a = _ALP(color);\n\tuint16_t r = _RED(color);\n\tuint16_t g = _GRE(color);\n\tuint16_t b = _BLU(color);\n\n\tr = r * a / 255;\n\tg = g * a / 255;\n\tb = b * a / 255;\n\treturn rgba(r,g,b,a);\n}\n\nstatic inline int clamp(int a, int l, int h) {\n\treturn a < l ? l : (a > h ? h : a);\n}\n\nstatic void _box_blur_horizontal(gfx_context_t * _src, int radius) {\n\tint w = _src->width;\n\tint h = _src->height;\n\tint half_radius = radius / 2;\n\tuint32_t * out_color = calloc(sizeof(uint32_t), w);\n\n\tfor (int y = 0; y < h; y++) {\n\t\tint hits = 0;\n\t\tint r = 0;\n\t\tint g = 0;\n\t\tint b = 0;\n\t\tint a = 0;\n\t\tfor (int x = -half_radius; x < w; x++) {\n\t\t\tint old_p = x - half_radius - 1;\n\t\t\tif (old_p >= 0)\n\t\t\t{\n\t\t\t\tuint32_t col = GFX(_src, clamp(old_p,0,w-1), y);\n\t\t\t\tif (col) {\n\t\t\t\t\tr -= _RED(col);\n\t\t\t\t\tg -= _GRE(col);\n\t\t\t\t\tb -= _BLU(col);\n\t\t\t\t\ta -= _ALP(col);\n\t\t\t\t}\n\t\t\t\thits--;\n\t\t\t}\n\n\t\t\tint newPixel = x + half_radius;\n\t\t\tif (newPixel < w) {\n\t\t\t\tuint32_t col = GFX(_src, clamp(newPixel,0,w-1), y);\n\t\t\t\tif (col != 0) {\n\t\t\t\t\tr += _RED(col);\n\t\t\t\t\tg += _GRE(col);\n\t\t\t\t\tb += _BLU(col);\n\t\t\t\t\ta += _ALP(col);\n\t\t\t\t}\n\t\t\t\thits++;\n\t\t\t}\n\n\t\t\tif (x >= 0 && x < w) {\n\t\t\t\tout_color[x] = rgba(r / hits, g / hits, b / hits, a / hits);\n\t\t\t}\n\t\t}\n\n\t\tif (!_is_in_clip(_src, y)) continue;\n\t\tfor (int x = 0; x < w; x++) {\n\t\t\tGFX(_src,x,y) = out_color[x];\n\t\t}\n\t}\n\n\tfree(out_color);\n}\n\nstatic void _box_blur_vertical(gfx_context_t * _src, int radius) {\n\tint w = _src->width;\n\tint h = _src->height;\n\tint half_radius = radius / 2;\n\n\tuint32_t * out_color = calloc(sizeof(uint32_t), h);\n\n\tfor (int x = 0; x < w; x++) {\n\t\tint hits = 0;\n\t\tint r = 0;\n\t\tint g = 0;\n\t\tint b = 0;\n\t\tint a = 0;\n\t\tfor (int y = -half_radius; y < h; y++) {\n\t\t\tint old_p = y - half_radius - 1;\n\t\t\tif (old_p >= 0) {\n\t\t\t\tuint32_t col = GFX(_src,x,clamp(old_p,0,h-1));\n\t\t\t\tif (col != 0) {\n\t\t\t\t\tr -= _RED(col);\n\t\t\t\t\tg -= _GRE(col);\n\t\t\t\t\tb -= _BLU(col);\n\t\t\t\t\ta -= _ALP(col);\n\t\t\t\t}\n\t\t\t\thits--;\n\t\t\t}\n\n\t\t\tint newPixel = y + half_radius;\n\t\t\tif (newPixel < h) {\n\t\t\t\tuint32_t col = GFX(_src,x,clamp(newPixel,0,h-1));\n\t\t\t\tif (col != 0)\n\t\t\t\t{\n\t\t\t\t\tr += _RED(col);\n\t\t\t\t\tg += _GRE(col);\n\t\t\t\t\tb += _BLU(col);\n\t\t\t\t\ta += _ALP(col);\n\t\t\t\t}\n\t\t\t\thits++;\n\t\t\t}\n\n\t\t\tif (y >= 0 && y < h) {\n\t\t\t\tout_color[y] = rgba(r / hits, g / hits, b / hits, a / hits);\n\t\t\t}\n\t\t}\n\n\t\tfor (int y = 0; y < h; y++) {\n\t\t\tif (!_is_in_clip(_src, y)) continue;\n\t\t\tGFX(_src,x,y) = out_color[y];\n\t\t}\n\t}\n\n\tfree(out_color);\n}\n\nvoid blur_context_box(gfx_context_t * _src, int radius) {\n\t_box_blur_horizontal(_src,radius);\n\t_box_blur_vertical(_src,radius);\n}\n\nvoid blur_from_into(gfx_context_t * _src, gfx_context_t * _dest, int radius) {\n\n\tdraw_fill(_dest, rgb(255,0,0));\n\n}\n\nstatic int (*load_sprite_jpg)(sprite_t *, const char *) = NULL;\nstatic int (*load_sprite_png)(sprite_t *, const char *) = NULL;\n\nstatic void _load_format_libraries() {\n\tvoid * _lib_jpeg = dlopen(\"libtoaru_jpeg.so\", 0);\n\tif (_lib_jpeg) load_sprite_jpg = dlsym(_lib_jpeg, \"load_sprite_jpg\");\n\tvoid * _lib_png = dlopen(\"libtoaru_png.so\", 0);\n\tif (_lib_png) load_sprite_png = dlsym(_lib_png, \"load_sprite_png\");\n}\n\nstatic const char * extension_from_filename(const char * filename) {\n\tconst char * ext = strrchr(filename, '.');\n\tif (ext && *ext == '.') return ext + 1;\n\treturn \"\";\n}\n\nint load_sprite(sprite_t * sprite, const char * filename) {\n\tstatic int librariesLoaded = 0;\n\tif (!librariesLoaded) {\n\t\t_load_format_libraries();\n\t\tlibrariesLoaded = 1;\n\t}\n\n\tconst char * ext = extension_from_filename(filename);\n\n\tif (!strcmp(ext,\"png\") || !strcmp(ext,\"sdf\")) return load_sprite_png ? load_sprite_png(sprite, filename) : 1;\n\tif (!strcmp(ext,\"jpg\") || !strcmp(ext,\"jpeg\")) return load_sprite_jpg ? load_sprite_jpg(sprite, filename) : 1;\n\n\t/* Fall back to bitmap */\n\treturn load_sprite_bmp(sprite, filename);\n}\n\nint load_sprite_bmp(sprite_t * sprite, const char * filename) {\n\t/* Open the requested binary */\n\tFILE * image = fopen(filename, \"r\");\n\n\tif (!image) return 1;\n\n\tlong image_size= 0;\n\n\tfseek(image, 0, SEEK_END);\n\timage_size = ftell(image);\n\tfseek(image, 0, SEEK_SET);\n\n\tif (image_size < 16) {\n\t\tfclose(image);\n\t\treturn 1;\n\t}\n\n\t/* Alright, we have the length */\n\tchar * bufferb = malloc(image_size);\n\tfread(bufferb, image_size, 1, image);\n\n\tif (bufferb[0] == 'B' && bufferb[1] == 'M') {\n\t\t/* Bitmaps */\n\t\tuint16_t x = 0; /* -> 212 */\n\t\tuint16_t y = 0; /* -> 68 */\n\t\t/* Get the width / height of the image */\n\t\tsigned int *bufferi = (signed int *)((uintptr_t)bufferb + 2);\n\t\tuint32_t width  = bufferi[4];\n\t\tuint32_t height = bufferi[5];\n\t\tuint16_t bpp    = bufferi[6] / 0x10000;\n\t\tuint32_t row_width = (bpp * width + 31) / 32 * 4;\n\t\t/* Skip right to the important part */\n\t\tsize_t i = bufferi[2];\n\n\t\tsprite->width = width;\n\t\tsprite->height = height;\n\t\tsprite->bitmap = malloc(sizeof(uint32_t) * width * height);\n\t\tsprite->masks = NULL;\n\n\t\tint alpha_after = ((unsigned char *)&bufferi[13])[2] == 0xFF;\n\n\t\t#define _BMP_A 0x1000000\n\t\t#define _BMP_R 0x1\n\t\t#define _BMP_G 0x100\n\t\t#define _BMP_B 0x10000\n\n\t\tif (bpp == 32) {\n\t\t\tsprite->alpha = ALPHA_EMBEDDED;\n\t\t}\n\n\t\tfor (y = 0; y < height; ++y) {\n\t\t\tfor (x = 0; x < width; ++x) {\n\t\t\t\tif (i > (size_t)image_size) goto _cleanup_sprite;\n\t\t\t\t/* Extract the color */\n\t\t\t\tuint32_t color;\n\t\t\t\tif (bpp == 24) {\n\t\t\t\t\tcolor =\t(bufferb[i   + 3 * x] & 0xFF) +\n\t\t\t\t\t\t\t(bufferb[i+1 + 3 * x] & 0xFF) * 0x100 +\n\t\t\t\t\t\t\t(bufferb[i+2 + 3 * x] & 0xFF) * 0x10000 + 0xFF000000;\n\t\t\t\t} else if (bpp == 32 && alpha_after == 0) {\n\t\t\t\t\tcolor =\t(bufferb[i   + 4 * x] & 0xFF) * _BMP_A +\n\t\t\t\t\t\t\t(bufferb[i+1 + 4 * x] & 0xFF) * _BMP_R +\n\t\t\t\t\t\t\t(bufferb[i+2 + 4 * x] & 0xFF) * _BMP_G +\n\t\t\t\t\t\t\t(bufferb[i+3 + 4 * x] & 0xFF) * _BMP_B;\n\t\t\t\t\tcolor = premultiply(color);\n\t\t\t\t} else if (bpp == 32 && alpha_after == 1) {\n\t\t\t\t\tcolor =\t(bufferb[i   + 4 * x] & 0xFF) * _BMP_R +\n\t\t\t\t\t\t\t(bufferb[i+1 + 4 * x] & 0xFF) * _BMP_G +\n\t\t\t\t\t\t\t(bufferb[i+2 + 4 * x] & 0xFF) * _BMP_B +\n\t\t\t\t\t\t\t(bufferb[i+3 + 4 * x] & 0xFF) * _BMP_A;\n\t\t\t\t\tcolor = premultiply(color);\n\t\t\t\t} else {\n\t\t\t\t\tcolor = rgb(bufferb[i + x],bufferb[i + x],bufferb[i + x]); /* Unsupported */\n\t\t\t\t}\n\t\t\t\t/* Set our point */\n\t\t\t\tsprite->bitmap[(height - y - 1) * width + x] = color;\n\t\t\t}\n\t\t\ti += row_width;\n\t\t}\n\t} else {\n\t\t/* Assume targa; limited support */\n\t\tstruct Header {\n\t\t\tuint8_t id_length;\n\t\t\tuint8_t color_map_type;\n\t\t\tuint8_t image_type;\n\n\t\t\tuint16_t color_map_first_entry;\n\t\t\tuint16_t color_map_length;\n\t\t\tuint8_t color_map_entry_size;\n\n\t\t\tuint16_t x_origin;\n\t\t\tuint16_t y_origin;\n\t\t\tuint16_t width;\n\t\t\tuint16_t height;\n\t\t\tuint8_t  depth;\n\t\t\tuint8_t  descriptor;\n\t\t} __attribute__((packed));\n\t\tstruct Header * header = (struct Header *)bufferb;\n\n\t\tif (header->id_length || header->color_map_type || (header->image_type != 2)) {\n\t\t\t/* Possibly valid but unsupported Targa file */\n\t\t\tfclose(image);\n\t\t\tfree(bufferb);\n\t\t\treturn 1;\n\t\t}\n\n\t\tsprite->width = header->width;\n\t\tsprite->height = header->height;\n\n\t\tsize_t size = sizeof(uint32_t) * sprite->width * sprite->height;\n\t\tif (size > image_size - sizeof(struct Header)) {\n\t\t\t/* sus */\n\t\t\tfclose(image);\n\t\t\tfree(bufferb);\n\t\t\treturn 1;\n\t\t}\n\n\t\tsprite->bitmap = malloc(size);\n\t\tsprite->masks = NULL;\n\n\t\tuint16_t x = 0;\n\t\tuint16_t y = 0;\n\n\t\tint i = sizeof(struct Header);\n\t\tif (header->depth == 24) {\n\t\t\tfor (y = 0; y < sprite->height; ++y) {\n\t\t\t\tfor (x = 0; x < sprite->width; ++x) {\n\t\t\t\t\tuint32_t color = rgb(\n\t\t\t\t\t\t\t\tbufferb[i+2 + 3 * x],\n\t\t\t\t\t\t\t\tbufferb[i+1 + 3 * x],\n\t\t\t\t\t\t\t\tbufferb[i   + 3 * x]);\n\t\t\t\t\tsprite->bitmap[(sprite->height - y - 1) * sprite->width + x] = color;\n\t\t\t\t}\n\t\t\t\ti += sprite->width * 3;\n\t\t\t}\n\t\t} else if (header->depth == 32) {\n\t\t\tfor (y = 0; y < sprite->height; ++y) {\n\t\t\t\tfor (x = 0; x < sprite->width; ++x) {\n\t\t\t\t\tuint32_t color = rgba(\n\t\t\t\t\t\t\t\tbufferb[i+2 + 4 * x],\n\t\t\t\t\t\t\t\tbufferb[i+1 + 4 * x],\n\t\t\t\t\t\t\t\tbufferb[i   + 4 * x],\n\t\t\t\t\t\t\t\tbufferb[i+3 + 4 * x]);\n\t\t\t\t\tsprite->bitmap[(sprite->height - y - 1) * sprite->width + x] = color;\n\t\t\t\t}\n\t\t\t\ti += sprite->width * 4;\n\t\t\t}\n\t\t}\n\n\t}\n\n_cleanup_sprite:\n\tfclose(image);\n\tfree(bufferb);\n\treturn 0;\n}\n\n#if !defined(NO_SSE) && defined(__x86_64__)\nstatic __m128i mask00ff;\nstatic __m128i mask0080;\nstatic __m128i mask0101;\n\n__attribute__((constructor)) static void _masks(void) {\n\tmask00ff = _mm_set1_epi16(0x00FF);\n\tmask0080 = _mm_set1_epi16(0x0080);\n\tmask0101 = _mm_set1_epi16(0x0101);\n}\n\n__attribute__((__force_align_arg_pointer__))\n#endif\nvoid draw_sprite(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y) {\n\n\tint32_t _left   = max(x, 0);\n\tint32_t _top    = max(y, 0);\n\tint32_t _right  = min(x + sprite->width,  ctx->width - 1);\n\tint32_t _bottom = min(y + sprite->height, ctx->height - 1);\n\tif (sprite->alpha == ALPHA_EMBEDDED) {\n\t\t/* Alpha embedded is the most important step. */\n\t\tfor (uint16_t _y = 0; _y < sprite->height; ++_y) {\n\t\t\tif (y + _y < _top) continue;\n\t\t\tif (y + _y > _bottom) break;\n\t\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n#if defined(NO_SSE) || !defined(__x86_64__)\n\t\t\tfor (uint16_t _x = 0; _x < sprite->width; ++_x) {\n\t\t\t\tif (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom)\n\t\t\t\t\tcontinue;\n\t\t\t\tGFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y));\n\t\t\t}\n#else\n\t\t\tuint16_t _x = (x < _left) ? _left - x : 0;\n\n\t\t\t/* Ensure alignment */\n\t\t\tfor (; _x < sprite->width && x + _x <= _right; ++_x) {\n\t\t\t\tif (!((uintptr_t)&GFX(ctx, x + _x, y + _y) & 15)) break;\n\t\t\t\tGFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y));\n\t\t\t}\n\t\t\tfor (; _x < sprite->width - 3 && x + _x + 3 <= _right; _x += 4) {\n\t\t\t\t__m128i d = _mm_load_si128((void *)&GFX(ctx, x + _x, y + _y));\n\t\t\t\t__m128i s = _mm_loadu_si128((void *)&SPRITE(sprite, _x, _y));\n\n\t\t\t\t__m128i d_l, d_h;\n\t\t\t\t__m128i s_l, s_h;\n\n\t\t\t\t// unpack destination\n\t\t\t\td_l = _mm_unpacklo_epi8(d, _mm_setzero_si128());\n\t\t\t\td_h = _mm_unpackhi_epi8(d, _mm_setzero_si128());\n\n\t\t\t\t// unpack source\n\t\t\t\ts_l = _mm_unpacklo_epi8(s, _mm_setzero_si128());\n\t\t\t\ts_h = _mm_unpackhi_epi8(s, _mm_setzero_si128());\n\n\t\t\t\t__m128i a_l, a_h;\n\t\t\t\t__m128i t_l, t_h;\n\n\t\t\t\t// extract source alpha RGBA → AAAA\n\t\t\t\ta_l = _mm_shufflehi_epi16(_mm_shufflelo_epi16(s_l, _MM_SHUFFLE(3,3,3,3)), _MM_SHUFFLE(3,3,3,3));\n\t\t\t\ta_h = _mm_shufflehi_epi16(_mm_shufflelo_epi16(s_h, _MM_SHUFFLE(3,3,3,3)), _MM_SHUFFLE(3,3,3,3));\n\n\t\t\t\t// negate source alpha\n\t\t\t\tt_l = _mm_xor_si128(a_l, mask00ff);\n\t\t\t\tt_h = _mm_xor_si128(a_h, mask00ff);\n\n\t\t\t\t// apply source alpha to destination\n\t\t\t\td_l = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(d_l,t_l),mask0080),mask0101);\n\t\t\t\td_h = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(d_h,t_h),mask0080),mask0101);\n\n\t\t\t\t// combine source and destination\n\t\t\t\td_l = _mm_adds_epu8(s_l,d_l);\n\t\t\t\td_h = _mm_adds_epu8(s_h,d_h);\n\n\t\t\t\t// pack low + high and write back to memory\n\t\t\t\t_mm_storeu_si128((void*)&GFX(ctx, x + _x, y + _y), _mm_packus_epi16(d_l,d_h));\n\t\t\t}\n\t\t\tfor (; _x < sprite->width && x + _x <= _right; ++_x) {\n\t\t\t\tGFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y));\n\t\t\t}\n#endif\n\t\t}\n\t} else if (sprite->alpha == ALPHA_OPAQUE) {\n\t\tfor (uint16_t _y = 0; _y < sprite->height; ++_y) {\n\t\t\tif (y + _y < _top) continue;\n\t\t\tif (y + _y > _bottom) break;\n\t\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n\t\t\tfor (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x <= _right; ++_x) {\n\t\t\t\tGFX(ctx, x + _x, y + _y) = SPRITE(sprite, _x, _y) | 0xFF000000;\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid draw_line(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color) {\n\tint deltax = abs(x1 - x0);\n\tint deltay = abs(y1 - y0);\n\tint sx = (x0 < x1) ? 1 : -1;\n\tint sy = (y0 < y1) ? 1 : -1;\n\tint error = deltax - deltay;\n\twhile (1) {\n\t\tif (x0 >= 0 && y0 >= 0 && x0 < ctx->width && y0 < ctx->height) {\n\t\t\tGFX(ctx, x0, y0) = color;\n\t\t}\n\t\tif (x0 == x1 && y0 == y1) break;\n\t\tint e2 = 2 * error;\n\t\tif (e2 > -deltay) {\n\t\t\terror -= deltay;\n\t\t\tx0 += sx;\n\t\t}\n\t\tif (e2 < deltax) {\n\t\t\terror += deltax;\n\t\t\ty0 += sy;\n\t\t}\n\t}\n}\n\nvoid draw_line_thick(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color, char thickness) {\n\tint deltax = abs(x1 - x0);\n\tint deltay = abs(y1 - y0);\n\tint sx = (x0 < x1) ? 1 : -1;\n\tint sy = (y0 < y1) ? 1 : -1;\n\tint error = deltax - deltay;\n\twhile (1) {\n\t\tfor (char j = -thickness; j <= thickness; ++j) {\n\t\t\tfor (char i = -thickness; i <= thickness; ++i) {\n\t\t\t\tif (x0 + i >= 0 && x0 + i < ctx->width && y0 + j >= 0 && y0 + j < ctx->height) {\n\t\t\t\t\tGFX(ctx, x0 + i, y0 + j) = color;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (x0 == x1 && y0 == y1) break;\n\t\tint e2 = 2 * error;\n\t\tif (e2 > -deltay) {\n\t\t\terror -= deltay;\n\t\t\tx0 += sx;\n\t\t}\n\t\tif (e2 < deltax) {\n\t\t\terror += deltax;\n\t\t\ty0 += sy;\n\t\t}\n\t}\n}\n\n\nvoid draw_fill(gfx_context_t * ctx, uint32_t color) {\n\tfor (uint16_t y = 0; y < ctx->height; ++y) {\n\t\tfor (uint16_t x = 0; x < ctx->width; ++x) {\n\t\t\tGFX(ctx, x, y) = color;\n\t\t}\n\t}\n}\n\nstatic inline int out_of_bounds(const sprite_t * tex, int x, int y) {\n\treturn x < 0 || y < 0 || x >= tex->width || y >= tex->height;\n}\n\n/**\n * @brief Use bilinear interpolation to get a blended color at the point u,v\n */\n#if 1\nstatic inline uint32_t linear_interp(uint32_t left, uint32_t right, uint16_t pr) {\n\tuint16_t pl = 0xFF ^ pr;\n\tuint8_t d_r = (((uint32_t)(_RED(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_RED(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_g = (((uint32_t)(_GRE(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_GRE(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_b = (((uint32_t)(_BLU(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_BLU(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_a = (((uint32_t)(_ALP(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_ALP(left) * pl + 0x80) * 0x101) >> 16UL);\n\treturn rgba(d_r, d_g, d_b, d_a);\n}\n\n__attribute__((hot))\nstatic inline uint32_t gfx_bilinear_interpolation(const sprite_t * tex, double u, double v) {\n\tint x = (int)(u + 2.0) - 2;\n\tint y = (int)(v + 2.0) - 2;\n\tuint32_t ul = out_of_bounds(tex,x,y)     ? 0 : SPRITE(tex,x,y);\n\tuint32_t ur = out_of_bounds(tex,x+1,y)   ? 0 : SPRITE(tex,x+1,y);\n\tuint32_t ll = out_of_bounds(tex,x,y+1)   ? 0 : SPRITE(tex,x,y+1);\n\tuint32_t lr = out_of_bounds(tex,x+1,y+1) ? 0 : SPRITE(tex,x+1,y+1);\n\tif ((ul | ur | ll | lr) == 0) return 0;\n\tuint8_t u_ratio = (u - x) * 0xFF;\n\tuint8_t v_ratio = (v - y) * 0xFF;\n\tuint32_t top = linear_interp(ul,ur,u_ratio);\n\tuint32_t bot = linear_interp(ll,lr,u_ratio);\n\treturn linear_interp(top,bot,v_ratio);\n}\n#else\nstatic uint32_t gfx_bilinear_interpolation(const sprite_t * tex, double u, double v) {\n\treturn out_of_bounds(tex,u,v) ? 0 : SPRITE(tex,(unsigned int)u,(unsigned int)v);\n}\n#endif\n\nstatic inline void apply_alpha_vector(uint32_t * pixels, size_t width, uint8_t alpha) {\n\tsize_t i = 0;\n#if !defined(NO_SSE) && defined(__x86_64__)\n\t__m128i alp = _mm_set_epi16(alpha,alpha,alpha,alpha,alpha,alpha,alpha,alpha);\n\twhile (i + 3 < width) {\n\t\t__m128i p = _mm_load_si128((void*)&pixels[i]);\n\t\t__m128i d_l, d_h;\n\n\t\td_l = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(_mm_unpacklo_epi8(p, _mm_setzero_si128()),alp),mask0080),mask0101);\n\t\td_h = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(_mm_unpackhi_epi8(p, _mm_setzero_si128()),alp),mask0080),mask0101);\n\n\t\t_mm_storeu_si128((void*)&pixels[i], _mm_packus_epi16(d_l,d_h));\n\n\t\ti += 4;\n\t}\n#endif\n\twhile (i < width) {\n\t\tuint8_t r = _RED(pixels[i]);\n\t\tuint8_t g = _GRE(pixels[i]);\n\t\tuint8_t b = _BLU(pixels[i]);\n\t\tuint8_t a = _ALP(pixels[i]);\n\n\t\tr = (((uint16_t)r * alpha + 0x80) * 0x101) >> 16UL;\n\t\tg = (((uint16_t)g * alpha + 0x80) * 0x101) >> 16UL;\n\t\tb = (((uint16_t)b * alpha + 0x80) * 0x101) >> 16UL;\n\t\ta = (((uint16_t)a * alpha + 0x80) * 0x101) >> 16UL;\n\n\t\tpixels[i] = rgba(r,g,b,a);\n\t\ti++;\n\t}\n}\n\nvoid draw_sprite_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha) {\n\tint32_t _left   = max(x, 0);\n\tint32_t _top    = max(y, 0);\n\tint32_t _right  = min(x + sprite->width,  ctx->width);\n\tint32_t _bottom = min(y + sprite->height, ctx->height);\n\tsprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED);\n\tuint8_t alp = alpha * 255;\n\n\tfor (uint16_t _y = 0; _y < sprite->height; ++_y) {\n\t\tif (y + _y < _top) continue;\n\t\tif (y + _y >= _bottom) break;\n\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n\t\tfor (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x < _right; ++_x) {\n\t\t\tSPRITE(scanline,_x + x - _left,0) = SPRITE(sprite, _x, _y);\n\t\t}\n\t\tapply_alpha_vector(scanline->bitmap, scanline->width, alp);\n\t\tdraw_sprite(ctx,scanline,_left,y + _y);\n\t}\n\n\tsprite_free(scanline);\n}\n\nvoid draw_sprite_alpha_paint(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha, uint32_t c) {\n\tint32_t _left   = max(x, 0);\n\tint32_t _top    = max(y, 0);\n\tint32_t _right  = min(x + sprite->width,  ctx->width);\n\tint32_t _bottom = min(y + sprite->height, ctx->height);\n\tfor (uint16_t _y = 0; _y < sprite->height; ++_y) {\n\t\tif (y + _y < _top) continue;\n\t\tif (y + _y >= _bottom) break;\n\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n\t\tfor (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x < _right; ++_x) {\n\t\t\t/* Get the alpha from the sprite at this pixel */\n\t\t\tfloat n_alpha = alpha * ((float)_ALP(SPRITE(sprite, _x, _y)) / 255.0);\n\t\t\tuint32_t f_color = premultiply((c & 0xFFFFFF) | ((uint32_t)(255 * n_alpha) << 24));\n\t\t\tf_color = (f_color & 0xFFFFFF) | ((uint32_t)(n_alpha * _ALP(c)) << 24);\n\t\t\tGFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), f_color);\n\t\t}\n\t}\n}\n\nstatic void apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y) {\n\t*out_x = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2];\n\t*out_y = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2];\n}\n\nvoid gfx_apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y) {\n\tapply_matrix(x,y,matrix,out_x,out_y);\n}\n\nstatic void multiply_matrix(gfx_matrix_t x, gfx_matrix_t y) {\n\tdouble a = x[0][0];\n\tdouble b = x[0][1];\n\tdouble c = x[0][2];\n\tdouble d = x[1][0];\n\tdouble e = x[1][1];\n\tdouble f = x[1][2];\n\n\tdouble g = y[0][0];\n\tdouble h = y[0][1];\n\tdouble i = y[0][2];\n\tdouble j = y[1][0];\n\tdouble k = y[1][1];\n\tdouble l = y[1][2];\n\n\tx[0][0] = a * g + b * j;\n\tx[0][1] = a * h + b * k;\n\tx[0][2] = a * i + b * l + c;\n\n\tx[1][0] = d * g + e * j;\n\tx[1][1] = d * h + e * k;\n\tx[1][2] = d * i + e * l + f;\n}\n\nvoid gfx_matrix_identity(gfx_matrix_t matrix) {\n\tmatrix[0][0] = 1;\n\tmatrix[0][1] = 0;\n\tmatrix[0][2] = 0;\n\tmatrix[1][0] = 0;\n\tmatrix[1][1] = 1;\n\tmatrix[1][2] = 0;\n}\n\nvoid gfx_matrix_scale(gfx_matrix_t matrix, double x, double y) {\n\tmultiply_matrix(matrix, (gfx_matrix_t){\n\t\t{x, 0.0, 0.0},\n\t\t{0.0, y, 0.0},\n\t});\n}\n\nvoid gfx_matrix_shear(gfx_matrix_t matrix, double x, double y) {\n\tmultiply_matrix(matrix, (gfx_matrix_t){\n\t\t{1.0, x, 0.0},\n\t\t{y, 1.0, 0.0},\n\t});\n}\n\nvoid gfx_matrix_rotate(gfx_matrix_t matrix, double r) {\n\tmultiply_matrix(matrix, (gfx_matrix_t){\n\t\t{ cos(r), -sin(r), 0.0},\n\t\t{ sin(r),  cos(r), 0.0},\n\t});\n}\n\nvoid gfx_matrix_translate(gfx_matrix_t matrix, double x, double y) {\n\tmultiply_matrix(matrix, (gfx_matrix_t){\n\t\t{ 1.0, 0.0, x },\n\t\t{ 0.0, 1.0, y },\n\t});\n}\n\nstatic double matrix_det(gfx_matrix_t matrix) {\n\tdouble a = matrix[0][0];\n\tdouble b = matrix[0][1];\n\tdouble d = matrix[1][0];\n\tdouble e = matrix[1][1];\n\treturn a * e - b * d;\n}\n\nint gfx_matrix_invert(gfx_matrix_t m, gfx_matrix_t inverse) {\n\n\tdouble det = matrix_det(m);\n\tif (det == 0.0) return 1;\n\n\tdouble a = m[0][0];\n\tdouble b = m[0][1];\n\tdouble c = m[1][0];\n\tdouble d = m[1][1];\n\n\tdouble tx = m[0][2];\n\tdouble ty = m[1][2];\n\n\tinverse[0][0] = d * (1.0 / det);\n\tinverse[0][1] = -b * (1.0 / det);\n\tinverse[1][0] = -c * (1.0 / det);\n\tinverse[1][1] = a * (1.0 / det);\n\n\tinverse[0][2] = (b * ty - d * tx) / det;\n\tinverse[1][2] = (c * tx - a * ty) / det;\n\n\treturn 0;\n}\n\n/**\n * @brief Draw a sprite into a context, applying a transformation matrix.\n *\n * Uses the affine transformaton matrix @p matrix to draw @p sprite into @p ctx.\n */\nvoid draw_sprite_transform(gfx_context_t * ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha) {\n\tdouble inverse[2][3];\n\n\t/* Calculate the inverse matrix for use in calculating sprite\n\t * coordinate from screen coordinate. */\n\tgfx_matrix_invert(matrix, inverse);\n\n\t/* Use primary matrix to obtain corners of the transformed\n\t * sprite in screen coordinates. */\n\tdouble ul_x, ul_y;\n\tdouble ll_x, ll_y;\n\tdouble ur_x, ur_y;\n\tdouble lr_x, lr_y;\n\n\tapply_matrix(0, 0, matrix, &ul_x, &ul_y);\n\tapply_matrix(0, sprite->height,  matrix, &ll_x, &ll_y);\n\tapply_matrix(sprite->width, 0,  matrix, &ur_x, &ur_y);\n\tapply_matrix(sprite->width, sprite->height,   matrix, &lr_x, &lr_y);\n\n\t/* Use the corners to calculate bounds within the target context. */\n\tint32_t _left   = clamp(fmin(fmin(ul_x, ll_x), fmin(ur_x, lr_x)), 0, ctx->width);\n\tint32_t _top    = clamp(fmin(fmin(ul_y, ll_y), fmin(ur_y, lr_y)), 0, ctx->height);\n\tint32_t _right  = clamp(fmax(fmax(ul_x+2, ll_x+2), fmax(ur_x+2, lr_x+2)), 0, ctx->width);\n\tint32_t _bottom = clamp(fmax(fmax(ul_y+2, ll_y+2), fmax(ur_y+2, lr_y+2)), 0, ctx->height);\n\n\tsprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED);\n\tuint8_t alp = alpha * 255;\n\n\tdouble filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy;\n\tgfx_apply_matrix(_left, _top, inverse, &filter_x, &filter_y);\n\tgfx_apply_matrix(_left+1, _top, inverse, &filter_dxx, &filter_dxy);\n\tfilter_dxx -= filter_x;\n\tfilter_dxy -= filter_y;\n\tgfx_apply_matrix(_left, _top+1, inverse, &filter_dyx, &filter_dyy);\n\tfilter_dyx -= filter_x;\n\tfilter_dyy -= filter_y;\n\n\tfor (int32_t _y = _top; _y < _bottom; ++_y) {\n\t\tfloat u = filter_x;\n\t\tfloat v = filter_y;\n\t\tfilter_x += filter_dyx;\n\t\tfilter_y += filter_dyy;\n\t\tif (!_is_in_clip(ctx, _y)) continue;\n\t\tfor (int32_t _x = _left; _x < _right; ++_x) {\n\t\t\tSPRITE(scanline,_x - _left,0) = gfx_bilinear_interpolation(sprite, u, v);\n\t\t\tu += filter_dxx;\n\t\t\tv += filter_dxy;\n\t\t}\n\t\tapply_alpha_vector(scanline->bitmap, scanline->width, alp);\n\t\tdraw_sprite(ctx,scanline,_left,_y);\n\t}\n\n\tsprite_free(scanline);\n}\n\nvoid draw_sprite_transform_blur(gfx_context_t * ctx, gfx_context_t * blur_ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha, uint8_t threshold) {\n\tdouble inverse[2][3];\n\n\t/* Calculate the inverse matrix for use in calculating sprite\n\t * coordinate from screen coordinate. */\n\tgfx_matrix_invert(matrix, inverse);\n\n\t/* Use primary matrix to obtain corners of the transformed\n\t * sprite in screen coordinates. */\n\tdouble ul_x, ul_y;\n\tdouble ll_x, ll_y;\n\tdouble ur_x, ur_y;\n\tdouble lr_x, lr_y;\n\n\tapply_matrix(0, 0, matrix, &ul_x, &ul_y);\n\tapply_matrix(0, sprite->height,  matrix, &ll_x, &ll_y);\n\tapply_matrix(sprite->width, 0,  matrix, &ur_x, &ur_y);\n\tapply_matrix(sprite->width, sprite->height,   matrix, &lr_x, &lr_y);\n\n\t/* Use the corners to calculate bounds within the target context. */\n\tint32_t _left   = clamp(fmin(fmin(ul_x, ll_x), fmin(ur_x, lr_x)), 0, ctx->width);\n\tint32_t _top    = clamp(fmin(fmin(ul_y, ll_y), fmin(ur_y, lr_y)), 0, ctx->height);\n\tint32_t _right  = clamp(fmax(fmax(ul_x+2, ll_x+2), fmax(ur_x+2, lr_x+2)), 0, ctx->width);\n\tint32_t _bottom = clamp(fmax(fmax(ul_y+2, ll_y+2), fmax(ur_y+2, lr_y+2)), 0, ctx->height);\n\n\tblur_ctx->clips_size = ctx->clips_size;\n\tblur_ctx->clips = ctx->clips;\n\tblur_ctx->backbuffer = ctx->backbuffer;\n\tgfx_context_t * f = init_graphics_subregion(blur_ctx, _left, _top, _right - _left, _bottom - _top);\n\tflip(f);\n\tf->backbuffer = f->buffer;\n\tblur_context_box(f, 10);\n\tfree(f);\n\tblur_ctx->backbuffer = blur_ctx->buffer;\n\tblur_ctx->clips_size = 0;\n\tblur_ctx->clips = NULL;\n\n\tsprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED);\n\tsprite_t * blurline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED);\n\tuint8_t alp = alpha * 255;\n\n\tdouble filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy;\n\tgfx_apply_matrix(_left, _top, inverse, &filter_x, &filter_y);\n\tgfx_apply_matrix(_left+1, _top, inverse, &filter_dxx, &filter_dxy);\n\tfilter_dxx -= filter_x;\n\tfilter_dxy -= filter_y;\n\tgfx_apply_matrix(_left, _top+1, inverse, &filter_dyx, &filter_dyy);\n\tfilter_dyx -= filter_x;\n\tfilter_dyy -= filter_y;\n\n\tfor (int32_t _y = _top; _y < _bottom; ++_y) {\n\t\tfloat u = filter_x;\n\t\tfloat v = filter_y;\n\t\tfilter_x += filter_dyx;\n\t\tfilter_y += filter_dyy;\n\t\tif (!_is_in_clip(ctx, _y)) continue;\n\t\tfor (int32_t _x = _left; _x < _right; ++_x) {\n\t\t\tSPRITE(scanline,_x - _left,0) = gfx_bilinear_interpolation(sprite, u, v);\n\t\t\tSPRITE(blurline,_x - _left,0) = (_ALP(SPRITE(scanline,_x - _left,0)) > threshold) ? GFX(blur_ctx,_x,_y) : 0;\n\t\t\tu += filter_dxx;\n\t\t\tv += filter_dxy;\n\t\t}\n\t\tapply_alpha_vector(blurline->bitmap, blurline->width, alp);\n\t\tapply_alpha_vector(scanline->bitmap, scanline->width, alp);\n\t\tdraw_sprite(ctx,blurline,_left,_y);\n\t\tdraw_sprite(ctx,scanline,_left,_y);\n\t}\n\n\tsprite_free(scanline);\n\tsprite_free(blurline);\n\n}\n\nvoid draw_sprite_rotate(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float rotation, float alpha) {\n\tgfx_matrix_t m;\n\tgfx_matrix_identity(m);\n\tgfx_matrix_translate(m, x + sprite->width / 2, y + sprite->height / 2);\n\tgfx_matrix_rotate(m, rotation);\n\tgfx_matrix_translate(m, -sprite->width / 2, -sprite->height / 2);\n\tdraw_sprite_transform(ctx,sprite,m,alpha);\n}\n\nvoid draw_sprite_scaled(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height) {\n\tgfx_matrix_t m;\n\tgfx_matrix_identity(m);\n\tgfx_matrix_translate(m, x, y);\n\tgfx_matrix_scale(m, (double)width / (double)sprite->width, (double)height / (double)sprite->height);\n\tdraw_sprite_transform(ctx,sprite,m,1.0);\n}\n\nvoid draw_sprite_scaled_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height, float alpha) {\n\tgfx_matrix_t m;\n\tgfx_matrix_identity(m);\n\tgfx_matrix_translate(m, x, y);\n\tgfx_matrix_scale(m, (double)width / (double)sprite->width, (double)height / (double)sprite->height);\n\tdraw_sprite_transform(ctx,sprite,m,alpha);\n}\n\nuint32_t interp_colors(uint32_t bottom, uint32_t top, uint8_t interp) {\n\tuint8_t red = (_RED(bottom) * (255 - interp) + _RED(top) * interp) / 255;\n\tuint8_t gre = (_GRE(bottom) * (255 - interp) + _GRE(top) * interp) / 255;\n\tuint8_t blu = (_BLU(bottom) * (255 - interp) + _BLU(top) * interp) / 255;\n\tuint8_t alp = (_ALP(bottom) * (255 - interp) + _ALP(top) * interp) / 255;\n\treturn rgba(red,gre,blu, alp);\n}\n\nvoid draw_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color) {\n\tint32_t _left   = max(x, 0);\n\tint32_t _top    = max(y, 0);\n\tint32_t _right  = min(x + width,  ctx->width - 1);\n\tint32_t _bottom = min(y + height, ctx->height - 1);\n\tfor (uint16_t _y = 0; _y < height; ++_y) {\n\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n\t\tfor (uint16_t _x = 0; _x < width; ++_x) {\n\t\t\tif (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom)\n\t\t\t\tcontinue;\n\t\t\tGFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), color);\n\t\t}\n\t}\n}\n\nvoid draw_rectangle_solid(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color) {\n\tint32_t _left   = max(x, 0);\n\tint32_t _top    = max(y, 0);\n\tint32_t _right  = min(x + width,  ctx->width - 1);\n\tint32_t _bottom = min(y + height, ctx->height - 1);\n\tfor (uint16_t _y = 0; _y < height; ++_y) {\n\t\tif (!_is_in_clip(ctx, y + _y)) continue;\n\t\tfor (uint16_t _x = 0; _x < width; ++_x) {\n\t\t\tif (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom)\n\t\t\t\tcontinue;\n\t\t\tGFX(ctx, x + _x, y + _y) = color;\n\t\t}\n\t}\n}\n\nuint32_t gfx_vertical_gradient_pattern(int32_t x, int32_t y, double alpha, void * extra) {\n\tstruct gradient_definition * gradient = extra;\n\tint base_r = _RED(gradient->top), base_g = _GRE(gradient->top), base_b = _BLU(gradient->top);\n\tint last_r = _RED(gradient->bottom), last_g = _GRE(gradient->bottom), last_b = _BLU(gradient->bottom);\n\tdouble gradpoint = (double)(y - (gradient->y)) / (double)gradient->height;\n\n\tif (alpha > 1.0) alpha = 1.0;\n\tif (alpha < 0.0) alpha = 0.0;\n\n\treturn premultiply(rgba(\n\t\tbase_r * (1.0 - gradpoint) + last_r * (gradpoint),\n\t\tbase_g * (1.0 - gradpoint) + last_g * (gradpoint),\n\t\tbase_b * (1.0 - gradpoint) + last_b * (gradpoint),\n\t\talpha * 255));\n}\n\nfloat gfx_point_distance(const struct gfx_point * a, const struct gfx_point * b) {\n\treturn sqrt((a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y));\n}\n\nvoid draw_rounded_rectangle_pattern(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, int radius, uint32_t (*pattern)(int32_t x, int32_t y, double alpha, void * extra), void * extra) {\n\t/* Draw a rounded rectangle */\n\n\tif (radius > width / 2) {\n\t\tradius = width / 2;\n\t}\n\n\tif (radius > height / 2) {\n\t\tradius = height / 2;\n\t}\n\n\tfor (int row = y; row < y + height; row++){\n\t\tif (row < 0) continue;\n\t\tif (row >= ctx->height) break;\n\t\tfor (int col = x; col < x + width; col++) {\n\t\t\tif (col < 0) continue;\n\t\t\tif (col >= ctx->width) break;\n\n\t\t\tif ((col < x + radius || col > x + width - radius - 1) &&\n\t\t\t\t(row < y + radius || row > y + height - radius - 1)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tGFX(ctx, col, row) = alpha_blend_rgba(GFX(ctx, col, row), pattern(col,row,1.0,extra));\n\t\t}\n\t}\n\n\tstruct gfx_point origin = {0.0,0.0};\n\n\tfor (int py = 0; py < radius + 1; ++py) {\n\t\tfor (int px = 0; px < radius + 1; ++px) {\n\t\t\tstruct gfx_point this = {px,py};\n\t\t\tfloat dist = gfx_point_distance(&origin,&this);\n\t\t\tif (dist > (double)radius) continue;\n\t\t\tfloat alpha = 1.0;\n\t\t\tif (dist > (double)(radius-1)) {\n\t\t\t\talpha = 1.0 - (dist - (double)(radius-1));\n\t\t\t}\n\t\t\tint _x = clamp(x + width - radius + px, 0, ctx->width-1);\n\t\t\tint _y = clamp(y + height - radius + py, 0, ctx->height-1);\n\t\t\tint _z = clamp(y + radius - py - 1, 0, ctx->height-1);\n\t\t\tGFX(ctx, _x, _y) = alpha_blend_rgba(GFX(ctx, _x, _y), pattern(_x,_y,alpha,extra));\n\t\t\tGFX(ctx, _x, _z) = alpha_blend_rgba(GFX(ctx, _x, _z), pattern(_x,_z,alpha,extra));\n\t\t\t_x = clamp(x + radius - px - 1, 0, ctx->width-1);\n\t\t\tGFX(ctx, _x, _y) = alpha_blend_rgba(GFX(ctx, _x, _y), pattern(_x,_y,alpha,extra));\n\t\t\tGFX(ctx, _x, _z) = alpha_blend_rgba(GFX(ctx, _x, _z), pattern(_x,_z,alpha,extra));\n\t\t}\n\t}\n}\n\nuint32_t gfx_fill_pattern(int32_t x, int32_t y, double alpha, void * extra) {\n\tif (alpha > 1.0) alpha = 1.0;\n\tif (alpha < 0.0) alpha = 0.0;\n\tuint32_t c = *(uint32_t*)extra;\n\treturn premultiply(rgba(_RED(c),_GRE(c),_BLU(c),(int)((double)_ALP(c) * alpha)));\n}\n\nvoid draw_rounded_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, int radius, uint32_t color) {\n\tdraw_rounded_rectangle_pattern(ctx,x,y,width,height,radius,gfx_fill_pattern,&color);\n}\n\nfloat gfx_point_distance_squared(const struct gfx_point * a, const struct gfx_point * b) {\n\treturn (a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y);\n}\n\nfloat gfx_point_dot(const struct gfx_point * a, const struct gfx_point * b) {\n\treturn (a->x * b->x) + (a->y * b->y);\n}\n\nstruct gfx_point gfx_point_sub(const struct gfx_point * a, const struct gfx_point * b) {\n\tstruct gfx_point p = {a->x - b->x, a->y - b->y};\n\treturn p;\n}\n\nstruct gfx_point gfx_point_add(const struct gfx_point * a, const struct gfx_point * b) {\n\tstruct gfx_point p = {a->x + b->x, a->y + b->y};\n\treturn p;\n}\n\nfloat gfx_line_distance(const struct gfx_point * p, const struct gfx_point * v, const struct gfx_point * w) {\n\tfloat lengthlength = gfx_point_distance_squared(v,w);\n\n\tif (lengthlength == 0.0) return gfx_point_distance(p, v); /* point */\n\n\tstruct gfx_point p_v = gfx_point_sub(p,v);\n\tstruct gfx_point w_v = gfx_point_sub(w,v);\n\tfloat tmp = gfx_point_dot(&p_v,&w_v) / lengthlength;\n\ttmp = fmin(1.0,tmp);\n\tfloat t = fmax(0.0, tmp);\n\n\tw_v.x *= t;\n\tw_v.y *= t;\n\n\tstruct gfx_point v_t = gfx_point_add(v, &w_v);\n\treturn gfx_point_distance(p, &v_t);\n}\n\n/**\n * This is slow, but it works...\n *\n * Maybe acceptable for baked UI elements?\n */\nvoid draw_line_aa_points(gfx_context_t * ctx, struct gfx_point *v, struct gfx_point *w, uint32_t color, float thickness) {\n\n\t/* Calculate viable bounds */\n\tint x_0 = max(min(v->x - thickness - 1, w->x - thickness - 1), 0);\n\tint x_1 = min(max(v->x + thickness + 1, w->x + thickness + 1), ctx->width);\n\tint y_0 = max(min(v->y - thickness - 1, w->y - thickness - 1), 0);\n\tint y_1 = min(max(v->y + thickness + 1, w->y + thickness + 1), ctx->height);\n\n\tfor (int y = y_0; y < y_1; ++y) {\n\t\tfor (int x = x_0; x < x_1; ++x) {\n\t\t\tstruct gfx_point p = {x,y};\n\t\t\tfloat d = gfx_line_distance(&p,v,w);\n\t\t\tif (d < thickness + 0.5) {\n\t\t\t\tif (d < thickness - 0.5) {\n\t\t\t\t\tGFX(ctx,x,y) = alpha_blend_rgba(GFX(ctx,x,y), color);\n\t\t\t\t} else {\n\t\t\t\t\tfloat alpha = 1.0 - (d - thickness + 0.5);\n\t\t\t\t\tGFX(ctx,x,y) = alpha_blend_rgba(GFX(ctx,x,y), premultiply(rgba(_RED(color),_GRE(color),_BLU(color),(int)((double)_ALP(color) * alpha))));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid draw_line_aa(gfx_context_t * ctx, int x_1, int x_2, int y_1, int y_2, uint32_t color, float thickness) {\n\tstruct gfx_point v = {(float)x_1, (float)y_1};\n\tstruct gfx_point w = {(float)x_2, (float)y_2};\n\tdraw_line_aa_points(ctx,&v,&w,color,thickness);\n}\n\n"
  },
  {
    "path": "lib/hashmap.c",
    "content": "/**\n * @brief Generic hashmap implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n#include <toaru/list.h>\n#include <toaru/hashmap.h>\n\nunsigned int hashmap_string_hash(const void * _key) {\n\tunsigned int hash = 0;\n\tchar * key = (char *)_key;\n\tint c;\n\t/* This is the so-called \"sdbm\" hash. It comes from a piece of\n\t * public domain code from a clone of ndbm. */\n\twhile ((c = *key++)) {\n\t\thash = c + (hash << 6) + (hash << 16) - hash;\n\t}\n\treturn hash;\n}\n\nint hashmap_string_comp(const void * a, const void * b) {\n\treturn !strcmp(a,b);\n}\n\nvoid * hashmap_string_dupe(const void * key) {\n\treturn strdup(key);\n}\n\nunsigned int hashmap_int_hash(const void * key) {\n\treturn (uintptr_t)key;\n}\n\nint hashmap_int_comp(const void * a, const void * b) {\n\treturn (uintptr_t)a == (uintptr_t)b;\n}\n\nvoid * hashmap_int_dupe(const void * key) {\n\treturn (void*)key;\n}\n\nstatic void hashmap_int_free(void * ptr) {\n\t(void)ptr;\n\treturn;\n}\n\n\nhashmap_t * hashmap_create(int size) {\n\thashmap_t * map = malloc(sizeof(hashmap_t));\n\n\tmap->hash_func     = &hashmap_string_hash;\n\tmap->hash_comp     = &hashmap_string_comp;\n\tmap->hash_key_dup  = &hashmap_string_dupe;\n\tmap->hash_key_free = &free;\n\tmap->hash_val_free = &free;\n\n\tmap->size = size;\n\tmap->entries = malloc(sizeof(hashmap_entry_t *) * size);\n\tmemset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);\n\n\treturn map;\n}\n\nhashmap_t * hashmap_create_int(int size) {\n\thashmap_t * map = malloc(sizeof(hashmap_t));\n\n\tmap->hash_func     = &hashmap_int_hash;\n\tmap->hash_comp     = &hashmap_int_comp;\n\tmap->hash_key_dup  = &hashmap_int_dupe;\n\tmap->hash_key_free = &hashmap_int_free;\n\tmap->hash_val_free = &free;\n\n\tmap->size = size;\n\tmap->entries = malloc(sizeof(hashmap_entry_t *) * size);\n\tmemset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size);\n\n\treturn map;\n}\n\nvoid * hashmap_set(hashmap_t * map, const void * key, void * value) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\thashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));\n\t\te->key   = map->hash_key_dup(key);\n\t\te->value = value;\n\t\te->next = NULL;\n\t\tmap->entries[hash] = e;\n\t\treturn NULL;\n\t} else {\n\t\thashmap_entry_t * p = NULL;\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\tvoid * out = x->value;\n\t\t\t\tx->value = value;\n\t\t\t\treturn out;\n\t\t\t} else {\n\t\t\t\tp = x;\n\t\t\t\tx = x->next;\n\t\t\t}\n\t\t} while (x);\n\t\thashmap_entry_t * e = malloc(sizeof(hashmap_entry_t));\n\t\te->key   = map->hash_key_dup(key);\n\t\te->value = value;\n\t\te->next = NULL;\n\n\t\tp->next = e;\n\t\treturn NULL;\n\t}\n}\n\nvoid * hashmap_get(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn NULL;\n\t} else {\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\treturn x->value;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t} while (x);\n\t\treturn NULL;\n\t}\n}\n\nvoid * hashmap_remove(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn NULL;\n\t} else {\n\t\tif (map->hash_comp(x->key, key)) {\n\t\t\tvoid * out = x->value;\n\t\t\tmap->entries[hash] = x->next;\n\t\t\tmap->hash_key_free(x->key);\n\t\t\tmap->hash_val_free(x);\n\t\t\treturn out;\n\t\t} else {\n\t\t\thashmap_entry_t * p = x;\n\t\t\tx = x->next;\n\t\t\tdo {\n\t\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\t\tvoid * out = x->value;\n\t\t\t\t\tp->next = x->next;\n\t\t\t\t\tmap->hash_key_free(x->key);\n\t\t\t\t\tmap->hash_val_free(x);\n\t\t\t\t\treturn out;\n\t\t\t\t}\n\t\t\t\tp = x;\n\t\t\t\tx = x->next;\n\t\t\t} while (x);\n\t\t}\n\t\treturn NULL;\n\t}\n}\n\nint hashmap_has(hashmap_t * map, const void * key) {\n\tunsigned int hash = map->hash_func(key) % map->size;\n\n\thashmap_entry_t * x = map->entries[hash];\n\tif (!x) {\n\t\treturn 0;\n\t} else {\n\t\tdo {\n\t\t\tif (map->hash_comp(x->key, key)) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tx = x->next;\n\t\t} while (x);\n\t\treturn 0;\n\t}\n\n}\n\nlist_t * hashmap_keys(hashmap_t * map) {\n\tlist_t * l = list_create();\n\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i];\n\t\twhile (x) {\n\t\t\tlist_insert(l, x->key);\n\t\t\tx = x->next;\n\t\t}\n\t}\n\n\treturn l;\n}\n\nlist_t * hashmap_values(hashmap_t * map) {\n\tlist_t * l = list_create();\n\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i];\n\t\twhile (x) {\n\t\t\tlist_insert(l, x->value);\n\t\t\tx = x->next;\n\t\t}\n\t}\n\n\treturn l;\n}\n\nvoid hashmap_free(hashmap_t * map) {\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\thashmap_entry_t * x = map->entries[i], * p;\n\t\twhile (x) {\n\t\t\tp = x;\n\t\t\tx = x->next;\n\t\t\tmap->hash_key_free(p->key);\n\t\t\tmap->hash_val_free(p);\n\t\t}\n\t}\n\tfree(map->entries);\n}\n\nint hashmap_is_empty(hashmap_t * map) {\n\tfor (unsigned int i = 0; i < map->size; ++i) {\n\t\tif (map->entries[i]) return 0;\n\t}\n\treturn 1;\n}\n"
  },
  {
    "path": "lib/icon_cache.c",
    "content": "/**\n * @brief icon_cache - caches icons\n *\n * Used be a few different applications.\n * Probably needs scaling?\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdio.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n\nstatic hashmap_t * icon_cache_16;\nstatic hashmap_t * icon_cache_48;\n\nstatic char * icon_directories_16[] = {\n\t\"/usr/share/icons/16\",\n\t\"/usr/share/icons/24\",\n\t\"/usr/share/icons/48\",\n\t\"/usr/share/icons\",\n\t\"/usr/share/icons/external\",\n\tNULL\n};\n\nstatic char * icon_directories_48[] = {\n\t\"/usr/share/icons/48\",\n\t\"/usr/share/icons/24\",\n\t\"/usr/share/icons/16\",\n\t\"/usr/share/icons\",\n\t\"/usr/share/icons/external\",\n\tNULL\n};\n\nstatic char * prefixes[] = {\n\t\"png\",\n\t\"bmp\",\n\tNULL\n};\n\n__attribute__((constructor))\nstatic void _init_caches(void) {\n\ticon_cache_16 = hashmap_create(10);\n\t{ /* Generic fallback icon */\n\t\tsprite_t * app_icon = malloc(sizeof(sprite_t));\n\t\tload_sprite(app_icon, \"/usr/share/icons/16/applications-generic.png\");\n\t\thashmap_set(icon_cache_16, \"generic\", app_icon);\n\t}\n\n\ticon_cache_48 = hashmap_create(10);\n\t{ /* Generic fallback icon */\n\t\tsprite_t * app_icon = malloc(sizeof(sprite_t));\n\t\tload_sprite(app_icon, \"/usr/share/icons/48/applications-generic.png\");\n\t\thashmap_set(icon_cache_48, \"generic\", app_icon);\n\t}\n}\n\n\nstatic sprite_t * icon_get_int(const char * name, hashmap_t * icon_cache, char ** icon_directories) {\n\n\tif (!strcmp(name,\"\")) {\n\t\t/* If a window doesn't have an icon set, return the generic icon */\n\t\treturn hashmap_get(icon_cache, \"generic\");\n\t}\n\n\t/* Check the icon cache */\n\tsprite_t * icon = hashmap_get(icon_cache, (void*)name);\n\n\tif (!icon) {\n\t\t/* We don't have an icon cached for this identifier, try search */\n\t\tint i = 0;\n\t\tchar path[100];\n\t\twhile (icon_directories[i]) {\n\t\t\t/* Check each path... */\n\t\t\tchar ** prefix = prefixes;\n\t\t\twhile (*prefix) {\n\t\t\t\tsprintf(path, \"%s/%s.%s\", icon_directories[i], name, *prefix);\n\t\t\t\tif (access(path, R_OK) == 0) {\n\t\t\t\t\t/* And if we find one, cache it */\n\t\t\t\t\ticon = malloc(sizeof(sprite_t));\n\t\t\t\t\tload_sprite(icon, path);\n\t\t\t\t\thashmap_set(icon_cache, (void*)name, icon);\n\t\t\t\t\treturn icon;\n\t\t\t\t}\n\t\t\t\tprefix++;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\n\t\t/* If we've exhausted our search paths, just return the generic icon */\n\t\ticon = hashmap_get(icon_cache, \"generic\");\n\t\thashmap_set(icon_cache, (void*)name, icon);\n\t}\n\n\t/* We have an icon, return it */\n\treturn icon;\n}\n\nsprite_t * icon_get_16(const char * name) {\n\treturn icon_get_int(name, icon_cache_16, icon_directories_16);\n}\n\nsprite_t * icon_get_48(const char * name) {\n\treturn icon_get_int(name, icon_cache_48, icon_directories_48);\n}\n"
  },
  {
    "path": "lib/inflate.c",
    "content": "/**\n * @brief libtoaru_inflate: Methods for decompressing DEFLATE and gzip payloads.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020 K. Lange\n */\n#include <stdint.h>\n#include <stddef.h>\n\n#ifndef _BOOT_LOADER\n#include <toaru/inflate.h>\n#endif\n\n/**\n * Decoded Huffman table\n */\nstruct huff {\n\tuint16_t counts[16];   /* Number of symbols of each length */\n\tuint16_t symbols[288]; /* Ordered symbols */\n};\n\n/**\n * 32K ringbuffer for backwards lookup\n */\nstruct huff_ring {\n\tsize_t pointer;\n\tuint8_t data[32768];\n};\n\n/**\n * Fixed Huffman code tables, generated later.\n */\nstruct huff fixed_lengths;\nstruct huff fixed_dists;\n\n/**\n * Read a little-endian short from the input.\n */\nstatic uint16_t read_16le(struct inflate_context * ctx) {\n\tuint16_t a, b;\n\ta = ctx->get_input(ctx);\n\tb = ctx->get_input(ctx);\n\treturn (a << 0) | (b << 8);\n}\n\n/**\n * Read a single bit from the source.\n * Fills the byte buffer with one byte when it runs out.\n */\nstatic uint8_t read_bit(struct inflate_context * ctx) {\n\n\t/* When we run out of bits... */\n\tif (ctx->buffer_size == 0) {\n\t\t/* Refill from the next input byte */\n\t\tctx->bit_buffer = ctx->get_input(ctx);\n\t\t/* And restore bit buffer size to 8 bits */\n\t\tctx->buffer_size = 8;\n\t}\n\n\t/* Get the next available bit */\n\tint out = ctx->bit_buffer & 1;\n\n\t/* Shift the bit buffer forward */\n\tctx->bit_buffer >>= 1;\n\n\t/* There is now one less bit available */\n\tctx->buffer_size--;\n\n\treturn out;\n}\n\n/**\n * Read multible bits, in bit order, from the source.\n */\nstatic uint32_t read_bits(struct inflate_context * ctx, unsigned int count) {\n\tuint32_t out = 0;\n\tfor (unsigned int bit = 0; bit < count; bit++) {\n\t\t/* Read one bit at a time, from least to most significant */\n\t\tout |= (read_bit(ctx) << bit);\n\t}\n\treturn out;\n}\n\n/**\n * Build a Huffman table from an array of lengths.\n */\nstatic void build_huffman(uint8_t * lengths, size_t size, struct huff * out) {\n\n\tuint16_t offsets[16];\n\tunsigned int count = 0;\n\n\t/* Zero symbol counts */\n\tfor (unsigned int i = 0; i < 16; ++i) out->counts[i] = 0;\n\n\t/* Count symbols */\n\tfor (unsigned int i = 0; i < size; ++i) out->counts[lengths[i]]++;\n\n\t/* Special case... */\n\tout->counts[0] = 0;\n\n\t/* Figure out offsets */\n\tfor (unsigned int i = 0; i < 16; ++i) {\n\t\toffsets[i] = count;\n\t\tcount += out->counts[i];\n\t}\n\n\t/* Build symbol ordering */\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\tif (lengths[i]) out->symbols[offsets[lengths[i]]++] = i;\n\t}\n}\n\n/**\n * Build the fixed Huffman tables\n */\nstatic void build_fixed(void) {\n\t/* From 3.2.6:\n\t * Lit Value    Bits        Codes\n\t * ---------    ----        -----\n\t *   0 - 143     8          00110000 through\n\t *                          10111111\n\t * 144 - 255     9          110010000 through\n\t *                          111111111\n\t * 256 - 279     7          0000000 through\n\t *                          0010111\n\t * 280 - 287     8          11000000 through\n\t *                          11000111\n\t */\n\tuint8_t lengths[288];\n\tfor (int i = 0; i < 144; ++i)   lengths[i] = 8;\n\tfor (int i = 144; i < 256; ++i) lengths[i] = 9;\n\tfor (int i = 256; i < 280; ++i) lengths[i] = 7;\n\tfor (int i = 280; i < 288; ++i) lengths[i] = 8;\n\tbuild_huffman(lengths, 288, &fixed_lengths);\n\n\t/* Continued from 3.2.6:\n\t * Distance codes 0-31 are represented by (fixed-length) 5-bit\n\t * codes, with possible additional bits as shown in the table\n\t * shown in Paragraph 3.2.5, above.  Note that distance codes 30-\n\t * 31 will never actually occur in the compressed data.\n\t */\n\tfor (int i = 0; i < 30; ++i) lengths[i] = 5;\n\tbuild_huffman(lengths, 30, &fixed_dists);\n}\n\n\n/**\n * Decode a symbol from the source using a Huffman table.\n */\nstatic int decode(struct inflate_context * ctx, struct huff * huff) {\n\tint count = 0, cur = 0;\n\tfor (int i = 1; cur >= 0; i++) {\n\t\tcur = (cur << 1) | read_bit(ctx); /* Shift */\n\t\tcount += huff->counts[i];\n\t\tcur -= huff->counts[i];\n\t}\n\treturn huff->symbols[count + cur];\n}\n\n/**\n * Emit one byte to the output, maintaining the ringbuffer.\n * The ringbuffer ensures we can always look back 32K bytes\n * while keeping output streaming.\n */\nstatic void emit(struct inflate_context * ctx, unsigned char byte) {\n\tif (ctx->ring->pointer == 32768) {\n\t\tctx->ring->pointer = 0;\n\t}\n\n\tctx->ring->data[ctx->ring->pointer] = byte;\n\tctx->write_output(ctx, byte);\n\tctx->ring->pointer++;\n}\n\n/**\n * Look backwards in the output ring buffer.\n */\nstatic uint8_t peek(struct inflate_context * ctx, int offset) {\n\treturn ctx->ring->data[(ctx->ring->pointer - offset) % 32768];\n}\n\n/**\n * Decompress a block of Huffman-encoded data.\n */\nstatic int inflate(struct inflate_context * ctx, struct huff * huff_len, struct huff * huff_dist) {\n\n\t/* These are the extra bits for lengths from the tables in section 3.2.5\n\t *           Extra               Extra               Extra\n\t *      Code Bits Length(s) Code Bits Lengths   Code Bits Length(s)\n\t *      ---- ---- ------     ---- ---- -------   ---- ---- -------\n\t *       257   0     3       267   1   15,16     277   4   67-82\n\t *       258   0     4       268   1   17,18     278   4   83-98\n\t *       259   0     5       269   2   19-22     279   4   99-114\n\t *       260   0     6       270   2   23-26     280   4  115-130\n\t *       261   0     7       271   2   27-30     281   5  131-162\n\t *       262   0     8       272   2   31-34     282   5  163-194\n\t *       263   0     9       273   3   35-42     283   5  195-226\n\t *       264   0    10       274   3   43-50     284   5  227-257\n\t *       265   1  11,12      275   3   51-58     285   0    258\n\t *       266   1  13,14      276   3   59-66\n\t */\n\tstatic const uint16_t lens[] = {\n\t\t3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51,\n\t\t59, 67, 83, 99, 115, 131, 163, 195, 227, 258\n\t};\n\tstatic const uint16_t lext[] = {\n\t\t0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,\n\t\t4, 5, 5, 5, 5, 0\n\t};\n\n\t/* Extra bits for distances....\n\t *            Extra           Extra               Extra\n\t *       Code Bits Dist  Code Bits   Dist     Code Bits Distance\n\t *       ---- ---- ----  ---- ----  ------    ---- ---- --------\n\t *         0   0    1     10   4     33-48    20    9   1025-1536\n\t *         1   0    2     11   4     49-64    21    9   1537-2048\n\t *         2   0    3     12   5     65-96    22   10   2049-3072\n\t *         3   0    4     13   5     97-128   23   10   3073-4096\n\t *         4   1   5,6    14   6    129-192   24   11   4097-6144\n\t *         5   1   7,8    15   6    193-256   25   11   6145-8192\n\t *         6   2   9-12   16   7    257-384   26   12  8193-12288\n\t *         7   2  13-16   17   7    385-512   27   12 12289-16384\n\t *         8   3  17-24   18   8    513-768   28   13 16385-24576\n\t *         9   3  25-32   19   8   769-1024   29   13 24577-32768\n\t */\n\tstatic const uint16_t dists[] = {\n\t\t1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385,\n\t\t513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577\n\t};\n\tstatic const uint16_t dext[] = {\n\t\t0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10,\n\t\t10, 11, 11, 12, 12, 13, 13\n\t};\n\n\twhile (1) {\n\t\tint symbol = decode(ctx, huff_len);\n\n\t\tif (symbol == 256) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (symbol < 256) {\n\t\t\temit(ctx, symbol);\n\t\t} else if (symbol == 256) {\n\t\t\t/* \"The literal/length symbol 256 (end of data), ...\" */\n\t\t\tbreak;\n\t\t} else {\n\t\t\tint length, distance, offset;\n\n\t\t\tsymbol -= 257;\n\t\t\tlength = read_bits(ctx, lext[symbol]) + lens[symbol];\n\t\t\tdistance = decode(ctx, huff_dist);\n\t\t\toffset = read_bits(ctx, dext[distance]) + dists[distance];\n\n\t\t\tfor (int i = 0; i < length; ++i) {\n\t\t\t\tuint8_t b = peek(ctx, offset);\n\t\t\t\temit(ctx, b);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Decode a dynamic Huffman block.\n */\nstatic void decode_huffman(struct inflate_context * ctx) {\n\n\t/* Ordering of code length codes:\n\t * (HCLEN + 4) x 3 bits: code lengths for the code length\n\t * alphabet given just above, in the order: ...\n\t */\n\tstatic const uint8_t clens[] = {\n\t\t16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15\n\t};\n\n\tunsigned int literals, distances, clengths;\n\tuint8_t lengths[320] = {0};\n\n\tliterals  = 257 + read_bits(ctx, 5); /* 5 Bits: HLIT ... 257 */\n\tdistances = 1 + read_bits(ctx, 5);   /* 5 Bits: HDIST ... 1 */\n\tclengths  = 4 + read_bits(ctx, 4);   /* 4 Bits: HCLEN ... 4 */\n\n\t/* (HCLEN + 4) x 3 bits... */\n\tfor (unsigned int i = 0; i < clengths; ++i) {\n\t\tlengths[clens[i]] = read_bits(ctx, 3);\n\t}\n\n\tstruct huff codes;\n\tbuild_huffman(lengths, 19, &codes);\n\n\t/* Decode symbols:\n\t * HLIT + 257 code lengths for the literal/length alphabet...\n\t * HDIST + 1 code lengths for the distance alphabet...\n\t */\n\tunsigned int count = 0;\n\twhile (count < literals + distances) {\n\t\tint symbol = decode(ctx, &codes);\n\n\t\tif (symbol < 16) {\n\t\t\t/* 0 - 15: Represent code lengths of 0-15 */\n\t\t\tlengths[count++] = symbol;\n\t\t} else if (symbol < 19) {\n\t\t\tint rep = 0, length;\n\t\t\tif (symbol == 16) {\n\t\t\t\t/* 16: Copy the previous code length 3-6 times */\n\t\t\t\trep = lengths[count-1];\n\t\t\t\tlength = read_bits(ctx, 2) + 3; /* The next 2 bits indicate repeat length */\n\t\t\t} else if (symbol == 17) {\n\t\t\t\t/* Repeat a code length of 0 for 3 - 10 times */\n\t\t\t\tlength = read_bits(ctx, 3) + 3; /* 3 bits of length */\n\t\t\t} else if (symbol == 18) {\n\t\t\t\t/* Repeat a code length of 0 for 11 - 138 times */\n\t\t\t\tlength = read_bits(ctx, 7) + 11; /* 7 bits of length */\n\t\t\t}\n\t\t\tdo {\n\t\t\t\tlengths[count++] = rep;\n\t\t\t\tlength--;\n\t\t\t} while (length);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Build tables from lenghts decoded above */\n\tstruct huff huff_len;\n\tbuild_huffman(lengths, literals, &huff_len);\n\tstruct huff huff_dist;\n\tbuild_huffman(lengths + literals, distances, &huff_dist);\n\n\tinflate(ctx, &huff_len, &huff_dist);\n}\n\n/**\n * Decode an uncompressed block.\n */\nstatic int uncompressed(struct inflate_context * ctx) {\n\t/* Reset byte alignment */\n\tctx->bit_buffer = 0;\n\tctx->buffer_size = 0;\n\n\t/* \"The rest of the block consists of the following information:\"\n\t *    0   1   2   3   4...\n\t *  +---+---+---+---+================================+\n\t *  |  LEN  | NLEN  |... LEN bytes of literal data...|\n\t *  +---+---+---+---+================================+\n\t */\n\tuint16_t len = read_16le(ctx); /* \"the number of data bytes in the block\" */\n\tuint16_t nlen = read_16le(ctx); /* \"the one's complement of LEN */\n\n\t/* Sanity check - does the ones-complement length actually match? */\n\tif ((nlen & 0xFFFF) != (~len & 0xFFFF)) {\n\t\treturn 1;\n\t}\n\n\t/* Emit LEN bytes from the source to the output */\n\tfor (int i = 0; i < len; ++i) {\n\t\temit(ctx, ctx->get_input(ctx));\n\t}\n\n\treturn 0;\n}\n\nstatic struct huff_ring data = {0, {0}};\n\n/**\n * Decompress DEFLATE-compressed data.\n */\nint deflate_decompress(struct inflate_context * ctx) {\n\tctx->bit_buffer = 0;\n\tctx->buffer_size = 0;\n\n\tbuild_fixed();\n\n\tif (!ctx->ring) {\n\t\tctx->ring = &data;\n\t}\n\n\t/* read compressed data */\n\twhile (1) {\n\t\t/* Read bit */\n\n\t\tint is_final = read_bit(ctx);\n\t\tint type = read_bits(ctx, 2);\n\n\t\tswitch (type) {\n\t\t\tcase 0x00: /* BTYPE=00 Non-compressed blocks */\n\t\t\t\tuncompressed(ctx);\n\t\t\t\tbreak;\n\t\t\tcase 0x01: /* BYTPE=01 Compressed with fixed Huffman codes */\n\t\t\t\tinflate(ctx, &fixed_lengths, &fixed_dists);\n\t\t\t\tbreak;\n\t\t\tcase 0x02: /* BTYPE=02 Compression with dynamic Huffman codes */\n\t\t\t\tdecode_huffman(ctx);\n\t\t\t\tbreak;\n\t\t\tcase 0x03:\n\t\t\t\treturn 1;\n\t\t}\n\n\t\tif (is_final) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n#define GZIP_FLAG_TEXT (1 << 0)\n#define GZIP_FLAG_HCRC (1 << 1)\n#define GZIP_FLAG_EXTR (1 << 2)\n#define GZIP_FLAG_NAME (1 << 3)\n#define GZIP_FLAG_COMM (1 << 4)\n\nstatic unsigned int read_32le(struct inflate_context * ctx) {\n\tunsigned int a, b, c, d;\n\ta = ctx->get_input(ctx);\n\tb = ctx->get_input(ctx);\n\tc = ctx->get_input(ctx);\n\td = ctx->get_input(ctx);\n\n\treturn (d << 24) | (c << 16) | (b << 8) | (a << 0);\n}\n\nint gzip_decompress(struct inflate_context * ctx) {\n\n\t/* Read gzip headers */\n\tif (ctx->get_input(ctx) != 0x1F) return 1;\n\tif (ctx->get_input(ctx) != 0x8B) return 1;\n\n\tunsigned int cm = ctx->get_input(ctx);\n\tif (cm != 8) return 1;\n\n\tunsigned int flags = ctx->get_input(ctx);\n\n\t/* Read mtime */\n\tunsigned int mtime = read_32le(ctx);\n\t(void)mtime;\n\n\t/* Read extra flags */\n\tunsigned int xflags = ctx->get_input(ctx);\n\t(void)xflags;\n\n\t/* Read and discord OS flag */\n\tunsigned int os = ctx->get_input(ctx);\n\t(void)os;\n\n\t/* Extra bytes */\n\tif (flags & GZIP_FLAG_EXTR) {\n\t\tunsigned short size = read_16le(ctx);\n\t\tfor (unsigned int i = 0; i < size; ++i) ctx->get_input(ctx);\n\t}\n\n\tif (flags & GZIP_FLAG_NAME) {\n\t\tunsigned int c;\n\t\twhile ((c = ctx->get_input(ctx)) != 0);\n\t}\n\n\tif (flags & GZIP_FLAG_COMM) {\n\t\tunsigned int c;\n\t\twhile ((c = ctx->get_input(ctx)) != 0);\n\t}\n\n\tunsigned int crc16 = 0;\n\tif (flags & GZIP_FLAG_HCRC) {\n\t\tcrc16 = read_16le(ctx);\n\t}\n\t(void)crc16;\n\n\tint status = deflate_decompress(ctx);\n\n\t/* Read CRC and decompressed size from end of input */\n\tunsigned int crc32 = read_32le(ctx);\n\tunsigned int dsize = read_32le(ctx);\n\n\t(void)crc32;\n\t(void)dsize;\n\n\treturn status;\n}\n"
  },
  {
    "path": "lib/jpeg.c",
    "content": "/**\n * @brief libtoaru_jpeg: Decode simple JPEGs.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n *\n * Adapted from Raul Aguaviva's Python \"micro JPEG visualizer\":\n *\n * MIT License\n *\n * Copyright (c) 2017 Raul Aguaviva\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n *\n */\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#include <toaru/graphics.h>\n\n#if !defined(NO_SSE) && defined(__x86_64__)\n#include <xmmintrin.h>\n#include <emmintrin.h>\n#endif\n\n#if 0\n#include <toaru/trace.h>\n#define TRACE_APP_NAME \"jpeg\"\n#else\n#define TRACE(...)\n#endif\n\nstatic sprite_t * sprite = NULL;\n\n/* Byte swap short (because JPEG uses big-endian values) */\nstatic void swap16(uint16_t * val) {\n\tchar * a = (char *)val;\n\tchar b = a[0];\n\ta[0] = a[1];\n\ta[1] = b;\n}\n\n/* JPEG compontent zig-zag ordering */\nstatic int zigzag[] = {\n\t 0,  1,  8, 16,  9,  2,  3, 10,\n\t17, 24, 32, 25, 18, 11,  4,  5,\n\t12, 19, 26, 33, 40, 48, 41, 34,\n\t27, 20, 13,  6,  7, 14, 21, 28,\n\t35, 42, 49, 56, 57, 50, 43, 36,\n\t29, 22, 15, 23, 30, 37, 44, 51,\n\t58, 59, 52, 45, 38, 31, 39, 46,\n\t53, 60, 61, 54, 47, 55, 62, 63\n};\n\nstatic uint8_t quant_mapping[3] = {0};\nstatic uint8_t quant[8][64];\n\nstatic int clamp(int col) {\n\tif (col > 255) return 255;\n\tif (col < 0) return 0;\n\treturn col;\n}\n\n/* YCbCr to RGB conversion */\nstatic void color_conversion(\n\t\tfloat Y, float Cb, float Cr,\n\t\tint *R, int *G, int *B\n\t) {\n\tfloat r = (Cr*(2.0-2.0*0.299) + Y);\n\tfloat b = (Cb*(2.0-2.0*0.114) + Y);\n\tfloat g = (Y - 0.144 * b - 0.229 * r) / 0.587;\n\n\t*R = clamp(r + 128);\n\t*G = clamp(g + 128);\n\t*B = clamp(b + 128);\n}\n\nstatic int xy_to_lin(int x, int y) {\n\treturn x + y * 8;\n}\n\nstruct huffman_table {\n\tuint8_t lengths[16];\n\tuint8_t elements[256];\n} huffman_tables[256] = {0};\n\nstruct stream {\n\tFILE * file;\n\tuint8_t byte;\n\tint have;\n\tint pos;\n};\n\nstatic void define_quant_table(FILE * f, int len) {\n\n\tTRACE(\"Defining quant table\");\n\twhile (len > 0) {\n\t\tuint8_t hdr;\n\t\tfread(&hdr, 1, 1, f);\n\t\tfread(&quant[(hdr) & 0xF], 64, 1, f);\n\t\tlen -= 65;\n\t}\n\tTRACE(\"Done\");\n}\n\nstatic void baseline_dct(FILE * f, int len) {\n\n\tstruct dct {\n\t\tuint8_t  hdr;\n\t\tuint16_t height;\n\t\tuint16_t width;\n\t\tuint8_t  components;\n\t} __attribute__((packed)) dct;\n\n\tfread(&dct, sizeof(struct dct), 1, f);\n\n\t/* Read image dimensions, each as big-endian 16-bit values */\n\tuint16_t h = dct.height;\n\tuint16_t w = dct.width;\n\tswap16(&h);\n\tswap16(&w);\n\tdct.height = h;\n\tdct.width = w;\n\n\t/* We read 7 bytes */\n\tlen -= sizeof(struct dct);\n\n\tTRACE(\"Image dimensions are %d×%d\", dct.width, dct.height);\n\tsprite->width  = dct.width;\n\tsprite->height = dct.height;\n\tsprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height);\n\tsprite->masks = NULL;\n\tsprite->alpha = 0;\n\tsprite->blank = 0;\n\n\tTRACE(\"Loading quantization mappings...\");\n\tfor (int i = 0; i < dct.components; ++i) {\n\t\t/* Quant mapping */\n\t\tstruct {\n\t\t\tuint8_t id;\n\t\t\tuint8_t samp;\n\t\t\tuint8_t qtb_id;\n\t\t} __attribute__((packed)) tmp;\n\n\t\tfread(&tmp, sizeof(tmp), 1, f);\n\n\t\t/* There should only be three of these for the images we support. */\n\t\tif (i > 3) {\n\t\t\tabort();\n\t\t}\n\n\t\tquant_mapping[i] = tmp.qtb_id;\n\n\t\t/* 3 bytes were read */\n\t\tlen -= 3;\n\t}\n\n\t/* Skip whatever else might be in this section */\n\tif (len > 0) {\n\t\tfseek(f, len, SEEK_CUR);\n\t}\n}\n\nstatic void define_huffman_table(FILE * f, int len) {\n\n\tTRACE(\"Loading Huffman tables...\");\n\twhile (len > 0) {\n\t\t/* Read header ID */\n\t\tuint8_t hdr;\n\t\tfread(&hdr, 1, 1, f);\n\t\tlen--;\n\n\t\t/* Read length table */\n\t\tfread(huffman_tables[hdr].lengths, 16, 1, f);\n\t\tlen -= 16;\n\n\t\t/* Read Huffman table entries */\n\t\tint o = 0;\n\t\tfor (int i = 0; i < 16; ++i) {\n\t\t\tint l = huffman_tables[hdr].lengths[i];\n\t\t\tfread(&huffman_tables[hdr].elements[o], l, 1, f);\n\t\t\to += l;\n\t\t\tlen -= l;\n\t\t}\n\t}\n\n\t/* Skip rest of section */\n\tif (len > 0) {\n\t\tfseek(f, len, SEEK_CUR);\n\t}\n}\n\nstruct idct {\n\tfloat base[64];\n};\n\n/**\n * norm_coeff[0] = 0.35355339059\n * norm_coeff[1] = 0.5\n */\nstatic float cosines[8][8] = {\n\t{ 0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059 },\n\t{ 0.490392640202,0.415734806151,0.27778511651,0.0975451610081,-0.0975451610081,-0.27778511651,-0.415734806151,-0.490392640202 },\n\t{ 0.461939766256,0.191341716183,-0.191341716183,-0.461939766256,-0.461939766256,-0.191341716183,0.191341716183,0.461939766256 },\n\t{ 0.415734806151,-0.0975451610081,-0.490392640202,-0.27778511651,0.27778511651,0.490392640202,0.0975451610081,-0.415734806151 },\n\t{ 0.353553390593,-0.353553390593,-0.353553390593,0.353553390593,0.353553390593,-0.353553390593,-0.353553390593,0.353553390593 },\n\t{ 0.27778511651,-0.490392640202,0.0975451610081,0.415734806151,-0.415734806151,-0.0975451610081,0.490392640202,-0.27778511651 },\n\t{ 0.191341716183,-0.461939766256,0.461939766256,-0.191341716183,-0.191341716183,0.461939766256,-0.461939766256,0.191341716183 },\n\t{ 0.0975451610081,-0.27778511651,0.415734806151,-0.490392640202,0.490392640202,-0.415734806151,0.27778511651,-0.0975451610081 },\n};\n\nstatic float premul[8][8][8][8]= {{{{0}}}};\n\nstatic void add_idc(struct idct * self, int n, int m, int coeff) {\n#if defined(NO_SSE) || !defined(__x86_64__)\n\tfor (int y = 0; y < 8; ++y) {\n\t\tfor (int x = 0; x < 8; ++x) {\n\t\t\tself->base[xy_to_lin(x, y)] += premul[n][m][y][x] * coeff;\n\t\t}\n\t}\n#else\n\t__m128 c = _mm_set_ps(coeff,coeff,coeff,coeff);\n\tfor (int y = 0; y < 8; ++y) {\n\t\t__m128 a, b;\n\t\t/* base[y][x] = base[y][x] + premul[n][m][y][x] * coeff */\n\n\t\t/* x = 0..3 */\n\t\ta = _mm_load_ps(&premul[n][m][y][0]);\n\t\ta = _mm_mul_ps(a,c);\n\t\tb = _mm_load_ps(&self->base[xy_to_lin(0,y)]);\n\t\ta = _mm_add_ps(a,b);\n\t\t_mm_store_ps(&self->base[xy_to_lin(0,y)], a);\n\n\t\t/* x = 4..7 */\n\t\ta = _mm_load_ps(&premul[n][m][y][4]);\n\t\ta = _mm_mul_ps(a,c);\n\t\tb = _mm_load_ps(&self->base[xy_to_lin(4,y)]);\n\t\ta = _mm_add_ps(a,b);\n\t\t_mm_store_ps(&self->base[xy_to_lin(4,y)], a);\n\t}\n#endif\n}\n\nstatic void add_zigzag(struct idct * self, int zi, int coeff) {\n\tint i = zigzag[zi];\n\tint n = i & 0x7;\n\tint m = i >> 3;\n\tadd_idc(self, n, m, coeff);\n}\n\n/* Read a bit from the stream */\nstatic int get_bit(struct stream * st) {\n\twhile ((st->pos >> 3) >= st->have) {\n\t\t/* We have finished using the current byte and need to read another one */\n\t\tint t = fgetc(st->file);\n\t\tif (t < 0) {\n\t\t\t/* EOF */\n\t\t\tst->byte = 0;\n\t\t} else {\n\t\t\tst->byte = t;\n\t\t}\n\n\t\tif (st->byte == 0xFF) {\n\t\t\t/*\n\t\t\t * If we see 0xFF, it's followed by a 0x00\n\t\t\t * that should be skipped.\n\t\t\t */\n\t\t\tint tmp = fgetc(st->file);\n\t\t\tif (tmp != 0) {\n\t\t\t\t/*\n\t\t\t\t * If it's *not*, we reached the end of the file - but\n\t\t\t\t * this shouldn't happen.\n\t\t\t\t */\n\t\t\t\tst->byte = 0;\n\t\t\t}\n\t\t}\n\n\t\t/* We've seen a new byte */\n\t\tst->have++;\n\t}\n\n\t/* Extract appropriate bit from this byte */\n\tuint8_t b = st->byte;\n\tint s = 7 - (st->pos & 0x7);\n\n\t/* We move forward one position in the bit stream */\n\tst->pos += 1;\n\treturn (b >> s) & 1;\n}\n\n/* Advance forward and get the n'th next bit */\nstatic int get_bitn(struct stream * st, int l) {\n\tint val = 0;\n\tfor (int i = 0; i < l; ++i) {\n\t\tval = val * 2 + get_bit(st);\n\t}\n\treturn val;\n}\n\n/*\n * Read a Huffman code by reading bits and using\n * the Huffman table.\n */\nstatic int get_code(struct huffman_table * table, struct stream * st) {\n\tint val = 0;\n\tint off = 0;\n\tint ini = 0;\n\n\tfor (int i = 0; i < 16; ++i) {\n\t\tval = val * 2 + get_bit(st);\n\t\tif (table->lengths[i] > 0) {\n\t\t\tif (val - ini < table->lengths[i]) {\n\t\t\t\treturn table->elements[off + val - ini];\n\t\t\t}\n\t\t\tini = ini + table->lengths[i];\n\t\t\toff += table->lengths[i];\n\t\t}\n\t\tini *= 2;\n\t}\n\n\t/* Invalid */\n\treturn -1;\n}\n\n/* Decode Huffman codes to values */\nstatic int decode(int code, int bits) {\n\tint l = 1L << (code - 1);\n\tif (bits >= l) {\n\t\treturn bits;\n\t} else {\n\t\treturn bits - (2 * l - 1);\n\t}\n}\n\n/* Build IDCT matrix */\nstatic struct idct * build_matrix(struct idct * i, struct stream * st, int idx, uint8_t * quant, int oldcoeff, int * outcoeff) {\n\tmemset(i, 0, sizeof(struct idct));\n\n\tint code = get_code(&huffman_tables[idx], st);\n\tint bits = get_bitn(st, code);\n\tint dccoeff = decode(code, bits) + oldcoeff;\n\n\tadd_zigzag(i, 0, dccoeff * quant[0]);\n\tint l = 1;\n\n\twhile (l < 64) {\n\t\tcode = get_code(&huffman_tables[16+idx], st);\n\t\tif (code == 0) break;\n\t\tif (code > 15) {\n\t\t\tl += (code >> 4);\n\t\t\tcode = code & 0xF;\n\t\t}\n\t\tbits = get_bitn(st, code);\n\t\tint coeff = decode(code, bits);\n\t\tadd_zigzag(i, l, coeff * quant[l]);\n\t\tl += 1;\n\t}\n\n\t*outcoeff = dccoeff;\n\treturn i;\n}\n\n/* Set pixel in sprite buffer with bounds checking */\nstatic void set_pixel(int x, int y, uint32_t color) {\n\tif ((x < sprite->width) && (y < sprite->height)) {\n\t\tSPRITE(sprite,x,y) = color;\n\t}\n}\n\n/* Concvert YCbCr values to RGB pixels */\nstatic void draw_matrix(int x, int y, struct idct * L, struct idct * cb, struct idct * cr) {\n\tfor (int yy = 0; yy < 8; ++yy) {\n\t\tfor (int xx = 0; xx < 8; ++xx) {\n\t\t\tint o = xy_to_lin(xx,yy);\n\t\t\tint r, g, b;\n\t\t\tcolor_conversion(L->base[o], cb->base[o], cr->base[o], &r, &g, &b);\n\t\t\tuint32_t c = 0xFF000000 | (r << 16) | (g << 8) | b;\n\t\t\tset_pixel((x * 8 + xx), (y * 8 + yy), c);\n\t\t}\n\t}\n}\n\nstatic void start_of_scan(FILE * f, int len) {\n\n\tTRACE(\"Reading image data\");\n\n\t/* Skip header */\n\tfseek(f, len, SEEK_CUR);\n\n\t/* Initialize bit stream */\n\tstruct stream _st = {0};\n\t_st.file = f;\n\tstruct stream * st = &_st;\n\n\tint old_lum = 0;\n\tint old_crd = 0;\n\tint old_cbd = 0;\n\tfor (int y = 0; y < sprite->height / 8 + !!(sprite->height & 0x7); ++y) {\n\t\tTRACE(\"Star row %d\", y );\n\t\tfor (int x = 0; x < sprite->width / 8 + !!(sprite->width & 0x7); ++x) {\n\t\t\tif (y >= 134) {\n\t\t\t\tTRACE(\"Start col %d\", x);\n\t\t\t}\n\n\t\t\t/* Build matrices */\n\t\t\tstruct idct matL, matCr, matCb;\n\t\t\tbuild_matrix(&matL,  st, 0, quant[quant_mapping[0]], old_lum, &old_lum);\n\t\t\tbuild_matrix(&matCb, st, 1, quant[quant_mapping[1]], old_cbd, &old_cbd);\n\t\t\tbuild_matrix(&matCr, st, 1, quant[quant_mapping[2]], old_crd, &old_crd);\n\n\t\t\tif (y >= 134) {\n\t\t\t\tTRACE(\"Draw col %d\", x);\n\t\t\t}\n\t\t\tdraw_matrix(x, y, &matL, &matCb, &matCr);\n\t\t}\n\t}\n\n\tTRACE(\"Done.\");\n}\n\nint load_sprite_jpg(sprite_t * tsprite, char * filename) {\n\tFILE * f = fopen(filename, \"r\");\n\tif (!f) {\n\t\treturn 1;\n\t}\n\n\tsprite = tsprite;\n\n\tmemset(huffman_tables, 0, sizeof(huffman_tables));\n\n\tif (premul[0][0][0][0] == 0.0) {\n\t\tfor (int n = 0; n < 8; ++n) {\n\t\t\tfor (int m = 0; m < 8; ++m) {\n\t\t\t\tfor (int y = 0; y < 8; ++y) {\n\t\t\t\t\tfor (int x = 0; x < 8; ++x) {\n\t\t\t\t\t\tpremul[n][m][y][x] = cosines[n][x] * cosines[m][y];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\twhile (1) {\n\n\t\t/* Read a header */\n\t\tuint16_t hdr;\n\t\tint r = fread(&hdr, 2, 1, f);\n\t\tif (r <= 0) {\n\t\t\t/* EOF */\n\t\t\tbreak;\n\t\t}\n\n\t\t/* These headers are stored big-endian */\n\t\tswap16(&hdr);\n\n\t\tif (hdr == 0xffd8) {\n\t\t\t/* No data */\n\t\t\tcontinue;\n\t\t} else if (hdr == 0xffd9) {\n\t\t\t/* End of file */\n\t\t\tbreak;\n\t\t} else {\n\t\t\t/* Regular sections with data start with a length */\n\t\t\tuint16_t len;\n\t\t\tfread(&len, 2, 1, f);\n\t\t\tswap16(&len);\n\n\t\t\t/* Subtract two because the length includes itself */\n\t\t\tlen -= 2;\n\n\t\t\tif (hdr == 0xffdb) {\n\t\t\t\tdefine_quant_table(f, len);\n\t\t\t} else if (hdr == 0xffc0) {\n\t\t\t\tbaseline_dct(f, len);\n\t\t\t} else if (hdr == 0xffc4) {\n\t\t\t\tdefine_huffman_table(f, len);\n\t\t\t} else if (hdr == 0xffda) {\n\t\t\t\tstart_of_scan(f, len);\n\t\t\t\t/* End immediately after reading the data */\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\tTRACE(\"Unknown header\\n\");\n\t\t\t\tfseek(f, len, SEEK_CUR);\n\t\t\t}\n\t\t}\n\t}\n\n\tfclose(f);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "lib/json.c",
    "content": "/**\n * @brief JSON parser.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <ctype.h>\n#include <math.h>\n\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n#include <toaru/json.h>\n\ntypedef struct JSON_Value Value;\n\n/* Internal usage */\nstruct JSON_Context {\n\tconst char * string;\n\tint c;\n\tconst char * error;\n};\n\nvoid json_free(Value * v) {\n\tif (v->type == JSON_TYPE_STRING) {\n\t\tfree(v->string);\n\t}\n\tif (v->type == JSON_TYPE_OBJECT) {\n\t\thashmap_free(v->object);\n\t\tfree(v->object);\n\t}\n\tif (v->type == JSON_TYPE_ARRAY) {\n\t\tforeach(node, v->array) {\n\t\t\tjson_free(node->value);\n\t\t}\n\t\tlist_free(v->array);\n\t\tfree(v->array);\n\t}\n\tfree(v);\n}\n\nstatic Value * value(struct JSON_Context * ctx);\n\nstatic int peek(struct JSON_Context * ctx) {\n\treturn ctx->string[ctx->c];\n}\n\nstatic void advance(struct JSON_Context * ctx) {\n\tctx->c++;\n}\n\nstatic void whitespace(struct JSON_Context * ctx) {\n\twhile (1) {\n\t\tint ch = peek(ctx);\n\t\tif (ch == ' ' || ch == '\\r' || ch == '\\n' || ch == '\\t') {\n\t\t\tadvance(ctx);\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nstatic Value * string(struct JSON_Context * ctx) {\n\tif (peek(ctx) != '\"') return NULL;\n\tadvance(ctx);\n\n\tint size = 4;\n\tchar * tmp = malloc(4);\n\ttmp[0] = 0;\n\tint used = 0;\n\n#define add(c) do { \\\n\tif (used + 1 == size) { \\\n\t\tsize *= 2; \\\n\t\ttmp = realloc(tmp, size); \\\n\t} \\\n\ttmp[used] = c; \\\n\ttmp[used+1] = 0; \\\n\tused++; \\\n} while (0)\n\n\twhile (1) {\n\t\tint ch = peek(ctx);\n\t\tif (ch == 0) goto string_error;\n\t\tif (ch == '\"') {\n\t\t\tbreak;\n\t\t} else if (ch == '\\\\') {\n\t\t\tadvance(ctx);\n\t\t\tch = peek(ctx);\n\t\t\tif (ch == '\"') add('\"');\n\t\t\telse if (ch == '\\\\') add('\\\\');\n\t\t\telse if (ch == '/') add('/');\n\t\t\telse if (ch == 'b') add('\\b');\n\t\t\telse if (ch == 'f') add('\\f');\n\t\t\telse if (ch == 'n') add('\\n');\n\t\t\telse if (ch == 'r') add('\\r');\n\t\t\telse if (ch == 't') add('\\t');\n\t\t\telse if (ch == 'u') {\n\t\t\t\t/* Parse hex */\n\t\t\t\tadvance(ctx);\n\t\t\t\tchar hex_digits[5];\n\t\t\t\tif (!isxdigit(peek(ctx))) goto string_error;\n\t\t\t\thex_digits[0] = peek(ctx); advance(ctx);\n\t\t\t\tif (!isxdigit(peek(ctx))) goto string_error;\n\t\t\t\thex_digits[1] = peek(ctx); advance(ctx);\n\t\t\t\tif (!isxdigit(peek(ctx))) goto string_error;\n\t\t\t\thex_digits[2] = peek(ctx); advance(ctx);\n\t\t\t\tif (!isxdigit(peek(ctx))) goto string_error;\n\t\t\t\thex_digits[3] = peek(ctx); /* will be advanced later */\n\t\t\t\thex_digits[4] = 0;\n\n\t\t\t\tuint32_t val = strtoul(hex_digits, NULL, 16);\n\t\t\t\tif (val < 0x0080) {\n\t\t\t\t\tadd(val);\n\t\t\t\t} else if (val < 0x0800) {\n\t\t\t\t\tadd(0xC0 | (val >> 6));\n\t\t\t\t\tadd(0x80 | (val & 0x3F));\n\t\t\t\t} else {\n\t\t\t\t\tadd(0xE0 | (val >> 12));\n\t\t\t\t\tadd(0x80 | ((val >> 6) & 0x3F));\n\t\t\t\t\tadd(0x80 | (val & 0x3F));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tgoto string_error;\n\t\t\t}\n\t\t\tadvance(ctx);\n\t\t} else {\n\t\t\tadd(ch);\n\t\t\tadvance(ctx);\n\t\t}\n\t}\n\n\tif (peek(ctx) != '\"') {\n\t\tctx->error = \"Unexpected EOF?\";\n\t\tgoto string_error;\n\t}\n\tadvance(ctx);\n\n\tValue * out = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_STRING;\n\tout->string = strdup(tmp);\n\tfree(tmp);\n\n\treturn out;\n\nstring_error:\n\tfree(tmp);\n\treturn NULL;\n}\n\nstatic Value * object(struct JSON_Context * ctx) {\n\tif (peek(ctx) != '{') {\n\t\tctx->error = \"Expected { (internal error)\";\n\t\treturn NULL;\n\t}\n\tadvance(ctx);\n\n\tValue * out;\n\thashmap_t * output = hashmap_create(10);\n\toutput->hash_val_free = (void (*)(void *))json_free;\n\twhitespace(ctx);\n\n\tif (peek(ctx) == '}') {\n\t\tadvance(ctx);\n\t\tgoto _object_done;\n\t}\n\n\twhile (1) {\n\t\twhitespace(ctx);\n\t\tValue * s = string(ctx);\n\n\t\tif (!s) {\n\t\t\tctx->error = \"Expected string\";\n\t\t\tbreak;\n\t\t}\n\n\t\twhitespace(ctx);\n\n\t\tif (peek(ctx) != ':') {\n\t\t\tctx->error = \"Expected :\";\n\t\t\tbreak;\n\t\t}\n\t\tadvance(ctx);\n\n\t\tValue * v = value(ctx);\n\n\t\thashmap_set(output, s->string, v);\n\t\tjson_free(s);\n\n\t\tif (peek(ctx) == '}') {\n\t\t\tadvance(ctx);\n\t\t\tgoto _object_done;\n\t\t}\n\n\t\tif (peek(ctx) != ',') {\n\t\t\tctx->error = \"Expected , or {\";\n\t\t\tbreak;\n\t\t}\n\n\t\tadvance(ctx);\n\t}\n\n\thashmap_free(output);\n\treturn NULL;\n\n_object_done:\n\tout = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_OBJECT;\n\tout->object = output;\n\treturn out;\n}\n\nstatic Value * boolean(struct JSON_Context * ctx) {\n\tint value = -1;\n\tif (peek(ctx) == 't') {\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'r') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'u') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'e') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tvalue = 1;\n\t} else if (peek(ctx) == 'f') {\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'a') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'l') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 's') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tif (peek(ctx) != 'e') { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\t\tadvance(ctx);\n\t\tvalue = 0;\n\t} else { ctx->error = \"Invalid literal while parsing bool\"; return NULL; }\n\n\tValue * out = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_BOOL;\n\tout->boolean = value;\n\treturn out;\n}\n\nstatic Value * null(struct JSON_Context * ctx) {\n\tif (peek(ctx) != 'n') { ctx->error = \"Invalid literal while parsing null\"; return NULL; }\n\tadvance(ctx);\n\tif (peek(ctx) != 'u') { ctx->error = \"Invalid literal while parsing null\"; return NULL; }\n\tadvance(ctx);\n\tif (peek(ctx) != 'l') { ctx->error = \"Invalid literal while parsing null\"; return NULL; }\n\tadvance(ctx);\n\tif (peek(ctx) != 'l') { ctx->error = \"Invalid literal while parsing null\"; return NULL; }\n\tadvance(ctx);\n\n\tValue * out = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_NULL;\n\treturn out;\n}\n\nstatic Value * number(struct JSON_Context * ctx) {\n\n\tdouble value = 0;\n\tint sign = 1;\n\tif (peek(ctx) == '-') {\n\t\t/* Negative */\n\t\tsign = -1;\n\t\tadvance(ctx);\n\t}\n\n\tif (peek(ctx) == '0') {\n\t\tadvance(ctx);\n\t} else if (isdigit(peek(ctx))) {\n\t\t/* Read any digit */\n\t\tvalue = peek(ctx) - '0';\n\t\tadvance(ctx);\n\t\twhile (isdigit(peek(ctx))) {\n\t\t\tvalue *= 10;\n\t\t\tvalue += peek(ctx) - '0';\n\t\t\tadvance(ctx);\n\t\t}\n\t} else {\n\t\tctx->error = \"Expected digit\";\n\t\treturn NULL;\n\t}\n\n\tif (peek(ctx) == '.') {\n\t\t/* Read fractional part */\n\t\tadvance(ctx);\n\n\t\tdouble multiplier = 0.1;\n\t\t/* read at least one digit */\n\t\tif (!isdigit(peek(ctx))) {\n\t\t\tctx->error = \"Expected digit\";\n\t\t\treturn NULL;\n\t\t}\n\t\twhile (isdigit(peek(ctx))) {\n\t\t\tvalue += multiplier * (peek(ctx) - '0');\n\t\t\tmultiplier *= 0.1;\n\t\t\tadvance(ctx);\n\t\t}\n\t}\n\n\tif (peek(ctx) == 'e' || peek(ctx) == 'E') {\n\t\t/* Read exponent */\n\t\tint exp_sign = 1;\n\t\tadvance(ctx);\n\t\tif (peek(ctx) == '+') advance(ctx);\n\t\telse if (peek(ctx) == '-') {\n\t\t\texp_sign = -1;\n\t\t\tadvance(ctx);\n\t\t}\n\n\t\t/* read digits */\n\t\tif (!isdigit(peek(ctx))) {\n\t\t\tctx->error = \"Expected digit\";\n\t\t\treturn NULL;\n\t\t}\n\t\tdouble exp = peek(ctx) - '0';\n\t\tadvance(ctx);\n\t\twhile (isdigit(peek(ctx))) {\n\t\t\texp *= 10;\n\t\t\texp += peek(ctx) - '0';\n\t\t\tadvance(ctx);\n\t\t}\n\n\t\tvalue = value * pow(10.0,exp * exp_sign);\n\t}\n\n\tValue * out = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_NUMBER;\n\tout->number = value * sign;\n\n\treturn out;\n}\n\nstatic Value * array(struct JSON_Context * ctx) {\n\tif (peek(ctx) != '[') return NULL;\n\tadvance(ctx);\n\n\twhitespace(ctx);\n\n\tlist_t * output = list_create();\n\tValue * out;\n\n\tif (peek(ctx) == ']') {\n\t\tadvance(ctx);\n\t\tgoto _array_done;\n\t}\n\n\twhile (1) {\n\t\tValue * next = value(ctx);\n\n\t\tif (!next) break;\n\n\t\tlist_insert(output, next);\n\n\t\tif (peek(ctx) == ']') {\n\t\t\tadvance(ctx);\n\t\t\tgoto _array_done;\n\t\t}\n\t\tif (peek(ctx) != ',') {\n\t\t\tctx->error = \"Expected ,\";\n\t\t\tbreak;\n\t\t}\n\n\t\tadvance(ctx);\n\t}\n\n\t/* uh oh */\n\tforeach(node, output) {\n\t\tjson_free(node->value);\n\t}\n\tlist_free(output);\n\tfree(output);\n\treturn NULL;\n\n_array_done:\n\tout = malloc(sizeof(Value));\n\tout->type = JSON_TYPE_ARRAY;\n\tout->array = output;\n\treturn out;\n}\n\n#define WHITE(c) { \\\n\tValue * out = c; \\\n\twhitespace(ctx); \\\n\treturn out; \\\n}\n\nstatic Value * value(struct JSON_Context * ctx) {\n\twhitespace(ctx);\n\tif (peek(ctx) == '\"') WHITE(string(ctx))\n\telse if (peek(ctx) == '{') WHITE(object(ctx))\n\telse if (peek(ctx) == '[') WHITE(array(ctx))\n\telse if (peek(ctx) == '-' || isdigit(peek(ctx))) WHITE(number(ctx))\n\telse if (peek(ctx) == 't') WHITE(boolean(ctx))\n\telse if (peek(ctx) == 'f') WHITE(boolean(ctx))\n\telse if (peek(ctx) == 'n') WHITE(null(ctx))\n\tctx->error = \"Unexpected value\";\n\treturn NULL;\n}\n\nValue * json_parse(const char * str) {\n\tstruct JSON_Context ctx;\n\tctx.string = str;\n\tctx.c = 0;\n\tctx.error = NULL;\n\tValue * out = value(&ctx);\n#if 0\n\tif (!out) {\n\t\tfprintf(stderr, \"JSON parse error at %d (%c)\\n\", ctx.c, ctx.string[ctx.c]);\n\t\tfprintf(stderr, \"%s\\n\", ctx.error);\n\t\tfprintf(stderr, \"%s\\n\", ctx.string);\n\t\tfor (int i = 0; i < ctx.c; ++i) { fprintf(stderr, \" \"); }\n\t\tfprintf(stderr, \"^\\n\");\n\t}\n#endif\n\treturn out;\n}\n\nValue * json_parse_file(const char * filename) {\n\tFILE * f = fopen(filename, \"r\");\n\n\tif (!f) return NULL;\n\n\tfseek(f, 0, SEEK_END);\n\tsize_t size = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\n\tchar * tmp = malloc(size + 1);\n\tfread(tmp, size, 1, f);\n\ttmp[size] = 0;\n\n\tfclose(f);\n\n\tValue * out = json_parse(tmp);\n\tfree(tmp);\n\treturn out;\n}\n"
  },
  {
    "path": "lib/kbd.c",
    "content": "/**\n * @brief General-purpose keyboard conversion library.\n *\n * This provides similar functionality to xkb:\n *   - It provides mappings for keyboards from locales\n *   - It translates incoming key presses to key names\n *   - It translates incoming keys to escape sequences\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n\n#include <stdio.h>\n#include <toaru/kbd.h>\n\n#define DEBUG_SCANCODES 0\n\n#define KEY_UP_MASK   0x80\n#define KEY_CODE_MASK 0x7F\n#define KEY_CTRL_MASK 0x40\n\n#define norm 0x01\n#define spec 0x02\n#define func 0x03\n\n#define SET_UNSET(a,b,c) (a) = (c) ? ((a) | (b)) : ((a) & ~(b))\n\nchar key_method[] = {\n\t/* 00 */ 0,    spec, norm, norm, norm, norm, norm, norm,\n\t/* 08 */ norm, norm, norm, norm, norm, norm, norm, norm,\n\t/* 10 */ norm, norm, norm, norm, norm, norm, norm, norm,\n\t/* 18 */ norm, norm, norm, norm, norm, spec, norm, norm,\n\t/* 20 */ norm, norm, norm, norm, norm, norm, norm, norm,\n\t/* 28 */ norm, norm, spec, norm, norm, norm, norm, norm,\n\t/* 30 */ norm, norm, norm, norm, norm, norm, spec, norm,\n\t/* 38 */ spec, norm, spec, func, func, func, func, func,\n\t/* 40 */ func, func, func, func, func, spec, spec, spec,\n\t/* 48 */ spec, spec, spec, spec, spec, spec, spec, spec,\n\t/* 50 */ spec, spec, spec, spec, spec, spec, spec, func,\n\t/* 58 */ func, spec, spec, spec, spec, spec, spec, spec,\n\t/* 60 */ spec, spec, spec, spec, spec, spec, spec, spec,\n\t/* 68 */ spec, spec, spec, spec, spec, spec, spec, spec,\n\t/* 70 */ spec, spec, spec, spec, spec, spec, spec, spec,\n\t/* 78 */ spec, spec, spec, spec, spec, spec, spec, spec,\n};\n\nchar kbd_us[128] = {\n\t0, 27,\n\t'1','2','3','4','5','6','7','8','9','0',\n\t'-','=','\\b',\n\t'\\t', /* tab */\n\t'q','w','e','r','t','y','u','i','o','p','[',']','\\n',\n\t0, /* control */\n\t'a','s','d','f','g','h','j','k','l',';','\\'', '`',\n\t0, /* left shift */\n\t'\\\\','z','x','c','v','b','n','m',',','.','/',\n\t0, /* right shift */\n\t'*',\n\t0, /* alt */\n\t' ', /* space */\n\t0, /* caps lock */\n\t0, /* F1 [59] */\n\t0, 0, 0, 0, 0, 0, 0, 0,\n\t0, /* ... F10 */\n\t0, /* 69 num lock */\n\t0, /* scroll lock */\n\t0, /* home */\n\t0, /* up */\n\t0, /* page up */\n\t'-',\n\t0, /* left arrow */\n\t0,\n\t0, /* right arrow */\n\t'+',\n\t0, /* 79 end */\n\t0, /* down */\n\t0, /* page down */\n\t0, /* insert */\n\t0, /* delete */\n\t0, 0, 0,\n\t0, /* F11 */\n\t0, /* F12 */\n\t0, /* everything else */\n};\n\nchar kbd_us_l2[128] = {\n\t0, 27,\n\t'!','@','#','$','%','^','&','*','(',')',\n\t'_','+','\\b',\n\t'\\t', /* tab */\n\t'Q','W','E','R','T','Y','U','I','O','P','{','}','\\n',\n\t0, /* control */\n\t'A','S','D','F','G','H','J','K','L',':','\"', '~',\n\t0, /* left shift */\n\t'|','Z','X','C','V','B','N','M','<','>','?',\n\t0, /* right shift */\n\t'*',\n\t0, /* alt */\n\t' ', /* space */\n\t0, /* caps lock */\n\t0, /* F1 [59] */\n\t0, 0, 0, 0, 0, 0, 0, 0,\n\t0, /* ... F10 */\n\t0, /* 69 num lock */\n\t0, /* scroll lock */\n\t0, /* home */\n\t0, /* up */\n\t0, /* page up */\n\t'-',\n\t0, /* left arrow */\n\t0,\n\t0, /* right arrow */\n\t'+',\n\t0, /* 79 end */\n\t0, /* down */\n\t0, /* page down */\n\t0, /* insert */\n\t0, /* delete */\n\t0, 0, 0,\n\t0, /* F11 */\n\t0, /* F12 */\n\t0, /* everything else */\n};\n\n#define KEY_SCANCODE_F1  0x3b\n#define KEY_SCANCODE_F2  0x3c\n#define KEY_SCANCODE_F3  0x3d\n#define KEY_SCANCODE_F4  0x3e\n#define KEY_SCANCODE_F5  0x3f\n#define KEY_SCANCODE_F6  0x40\n#define KEY_SCANCODE_F7  0x41\n#define KEY_SCANCODE_F8  0x42\n#define KEY_SCANCODE_F9  0x43\n#define KEY_SCANCODE_F10 0x44\n#define KEY_SCANCODE_F11 0x57\n#define KEY_SCANCODE_F12 0x58\n\n#define KEY_SCANCODE_NUM_1  0x4f\n#define KEY_SCANCODE_NUM_2  0x50\n#define KEY_SCANCODE_NUM_3  0x51\n#define KEY_SCANCODE_NUM_4  0x4B\n#define KEY_SCANCODE_NUM_5  0x4C\n#define KEY_SCANCODE_NUM_6  0x4D\n#define KEY_SCANCODE_NUM_7  0x47\n#define KEY_SCANCODE_NUM_8  0x48\n#define KEY_SCANCODE_NUM_9  0x49\n#define KEY_SCANCODE_NUM_0  0x52\n#define KEY_SCANCODE_NUM_DOT 0x53\n#define KEY_SCANCODE_NUM_MIN 0x4a\n#define KEY_SCANCODE_NUM_ADD 0x4e\n\n#define KEY_SCANCODE_NUM_LK 0x45\n#define KEY_SCANCODE_SCROLL 0x46\n\n\nint kbd_scancode(key_event_state_t * state, unsigned char c, key_event_t * event) {\n\t/* Convert scancodes to a series of keys */\n\n\tevent->keycode   = 0;\n\tevent->action    = 0;\n\tevent->modifiers = 0;\n\tevent->key       = 0;\n\n#if DEBUG_SCANCODES\n\tfprintf(stderr, \"[%d] %d\\n\", state->kbd_s_state, (int)c);\n#endif\n\n\tevent->modifiers |= state->kl_ctrl  ? KEY_MOD_LEFT_CTRL   : 0;\n\tevent->modifiers |= state->kl_shift ? KEY_MOD_LEFT_SHIFT  : 0;\n\tevent->modifiers |= state->kl_alt   ? KEY_MOD_LEFT_ALT    : 0;\n\tevent->modifiers |= state->kl_super ? KEY_MOD_LEFT_SUPER  : 0;\n\n\tevent->modifiers |= state->kr_ctrl  ? KEY_MOD_RIGHT_CTRL  : 0;\n\tevent->modifiers |= state->kr_shift ? KEY_MOD_RIGHT_SHIFT : 0;\n\tevent->modifiers |= state->kr_alt   ? KEY_MOD_RIGHT_ALT   : 0;\n\tevent->modifiers |= state->kr_super ? KEY_MOD_RIGHT_SUPER : 0;\n\n\tif (!state->kbd_s_state) {\n\t\tif (c == 0xE0) {\n\t\t\tstate->kbd_s_state = 1;\n\t\t\treturn 0;\n\t\t}\n\n\t\tevent->action = (c & KEY_UP_MASK) ? KEY_ACTION_UP : KEY_ACTION_DOWN;\n\t\tc &= 0x7F;\n\t\tint down = (event->action == KEY_ACTION_DOWN);\n\n\t\tswitch (key_method[c]) {\n\t\t\tcase norm:\n\t\t\t\t{\n\t\t\t\t\tevent->keycode = kbd_us[c];\n\t\t\t\t\tif (state->k_ctrl) {\n\t\t\t\t\t\tint s = kbd_us[c];\n\t\t\t\t\t\tif (s >= 'a' && s <= 'z') s -= 'a' - 'A';\n\t\t\t\t\t\tif (s == '-') s = '_';\n\t\t\t\t\t\tif (s == '`') s = '@';\n\t\t\t\t\t\tint out = (int)(s - KEY_CTRL_MASK);\n\t\t\t\t\t\tif (out < 0 || out > 0x1F) {\n\t\t\t\t\t\t\tevent->key = kbd_us[c];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tevent->key = out;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tevent->key = state->k_shift ? kbd_us_l2[c] : kbd_us[c];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn 1;\n\t\t\tcase spec:\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase 0x01:\n\t\t\t\t\t\tevent->key     = '\\033';\n\t\t\t\t\t\tevent->keycode = KEY_ESCAPE;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase 0x1D:\n\t\t\t\t\t\tstate->k_ctrl   = down;\n\t\t\t\t\t\tstate->kl_ctrl  = down;\n\t\t\t\t\t\tevent->keycode  = KEY_LEFT_CTRL;\n\t\t\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_LEFT_CTRL, down);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase 0x2A:\n\t\t\t\t\t\tstate->k_shift  = down;\n\t\t\t\t\t\tstate->kl_shift = down;\n\t\t\t\t\t\tevent->keycode  = KEY_LEFT_SHIFT;\n\t\t\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_LEFT_SHIFT, down);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase 0x36:\n\t\t\t\t\t\tstate->k_shift  = down;\n\t\t\t\t\t\tstate->kr_shift = down;\n\t\t\t\t\t\tevent->keycode  = KEY_RIGHT_SHIFT;\n\t\t\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_RIGHT_SHIFT, down);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase 0x38:\n\t\t\t\t\t\tstate->k_alt    = down;\n\t\t\t\t\t\tstate->kl_alt   = down;\n\t\t\t\t\t\tevent->keycode  = KEY_LEFT_ALT;\n\t\t\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_LEFT_ALT, down);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_0:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_0;\n\t\t\t\t\t\tevent->key = '0';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_1:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_1;\n\t\t\t\t\t\tevent->key = '1';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_2:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_2;\n\t\t\t\t\t\tevent->key = '2';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_3:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_3;\n\t\t\t\t\t\tevent->key = '3';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_4:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_4;\n\t\t\t\t\t\tevent->key = '4';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_5:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_5;\n\t\t\t\t\t\tevent->key = '5';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_6:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_6;\n\t\t\t\t\t\tevent->key = '6';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_7:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_7;\n\t\t\t\t\t\tevent->key = '7';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_8:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_8;\n\t\t\t\t\t\tevent->key = '8';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_9:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_9;\n\t\t\t\t\t\tevent->key = '9';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_DOT:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_DOT;\n\t\t\t\t\t\tevent->key = '.';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_MIN:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_MINUS;\n\t\t\t\t\t\tevent->key = '-';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_NUM_ADD:\n\t\t\t\t\t\tevent->keycode = KEY_NUM_PLUS;\n\t\t\t\t\t\tevent->key = '+';\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase func:\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase KEY_SCANCODE_F1:\n\t\t\t\t\t\tevent->keycode = KEY_F1;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F2:\n\t\t\t\t\t\tevent->keycode = KEY_F2;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F3:\n\t\t\t\t\t\tevent->keycode = KEY_F3;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F4:\n\t\t\t\t\t\tevent->keycode = KEY_F4;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F5:\n\t\t\t\t\t\tevent->keycode = KEY_F5;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F6:\n\t\t\t\t\t\tevent->keycode = KEY_F6;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F7:\n\t\t\t\t\t\tevent->keycode = KEY_F7;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F8:\n\t\t\t\t\t\tevent->keycode = KEY_F8;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F9:\n\t\t\t\t\t\tevent->keycode = KEY_F9;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F10:\n\t\t\t\t\t\tevent->keycode = KEY_F10;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F11:\n\t\t\t\t\t\tevent->keycode = KEY_F11;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase KEY_SCANCODE_F12:\n\t\t\t\t\t\tevent->keycode = KEY_F12;\n\t\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t\treturn 0;\n\t} else if (state->kbd_s_state == 1) {\n\t\tstate->kbd_s_state = 0;\n\t\tevent->action = (c & KEY_UP_MASK) ? KEY_ACTION_UP : KEY_ACTION_DOWN;\n\t\tc &= 0x7F;\n\n\t\tint down = (event->action == KEY_ACTION_DOWN);\n\t\tswitch (c) {\n\t\t\tcase 0x5B:\n\t\t\t\tstate->k_super  = down;\n\t\t\t\tstate->kl_super = down;\n\t\t\t\tevent->keycode  = KEY_LEFT_SUPER;\n\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_LEFT_SUPER, down);\n\t\t\t\treturn 1;\n\t\t\tcase 0x5C:\n\t\t\t\tstate->k_super  = down;\n\t\t\t\tstate->kr_super = down;\n\t\t\t\tevent->keycode  = KEY_RIGHT_SUPER;\n\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_RIGHT_SUPER, down);\n\t\t\t\treturn 1;\n\t\t\tcase 0x1D:\n\t\t\t\tstate->kr_ctrl  = down;\n\t\t\t\tstate->k_ctrl   = down;\n\t\t\t\tevent->keycode  = KEY_RIGHT_CTRL;\n\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_RIGHT_CTRL, down);\n\t\t\t\treturn 1;\n\t\t\tcase 0x38:\n\t\t\t\tstate->kr_alt   = down;\n\t\t\t\tstate->k_alt    = down;\n\t\t\t\tevent->keycode  = KEY_RIGHT_ALT;\n\t\t\t\tSET_UNSET(event->modifiers, KEY_MOD_RIGHT_ALT, down);\n\t\t\t\treturn 1;\n\t\t\tcase 0x48:\n\t\t\t\tevent->keycode = KEY_ARROW_UP;\n\t\t\t\treturn 1;\n\t\t\tcase 0x4D:\n\t\t\t\tevent->keycode = KEY_ARROW_RIGHT;\n\t\t\t\treturn 1;\n\t\t\tcase 0x47:\n\t\t\t\tevent->keycode = KEY_HOME;\n\t\t\t\treturn 1;\n\t\t\tcase 0x49:\n\t\t\t\tevent->keycode = KEY_PAGE_UP;\n\t\t\t\treturn 1;\n\t\t\tcase 0x4B:\n\t\t\t\tevent->keycode = KEY_ARROW_LEFT;\n\t\t\t\treturn 1;\n\t\t\tcase 0x4F:\n\t\t\t\tevent->keycode = KEY_END;\n\t\t\t\treturn 1;\n\t\t\tcase 0x50:\n\t\t\t\tevent->keycode = KEY_ARROW_DOWN;\n\t\t\t\treturn 1;\n\t\t\tcase 0x51:\n\t\t\t\tevent->keycode = KEY_PAGE_DOWN;\n\t\t\t\treturn 1;\n\t\t\tcase 0x52:\n\t\t\t\tevent->keycode = KEY_INSERT;\n\t\t\t\treturn 1;\n\t\t\tcase 0x53:\n\t\t\t\tevent->keycode = KEY_DEL;\n\t\t\t\treturn 1;\n\t\t\tcase 0x35:\n\t\t\t\tevent->keycode = KEY_NUM_DIV;\n\t\t\t\tevent->key = '/';\n\t\t\t\treturn 1;\n\t\t\tcase 0x1C:\n\t\t\t\tevent->keycode = KEY_NUM_ENTER;\n\t\t\t\tevent->key = '\\n';\n\t\t\t\treturn 1;\n\t\t\tcase 0x37:\n\t\t\t\tevent->keycode = KEY_PRINT_SCREEN;\n\t\t\t\treturn 1;\n\t\t\tcase 0x5D:\n\t\t\t\tevent->keycode = KEY_APP;\n\t\t\t\treturn 1;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "lib/kuroko/_waitpid.c",
    "content": "#include <errno.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <kuroko/kuroko.h>\n#include <kuroko/vm.h>\n#include <kuroko/util.h>\n\nKRK_Function(waitpid) {\n\tint pid = -1;\n\tint options = 0;\n\tif (!krk_parseArgs(\"|ii\",(const char*[]){\"pid\",\"options\"}, &pid, &options)) return NONE_VAL();\n\n\tint status = 0;\n\tpid_t result = waitpid(pid, &status, options);\n\n\tif (result == -1) {\n\t\treturn krk_runtimeError(KRK_EXC(OSError), \"%s\", strerror(errno));\n\t}\n\n\tkrk_push(OBJECT_VAL(krk_newTuple(2)));\n\tAS_TUPLE(krk_peek(0))->values.values[0] = INTEGER_VAL(result);\n\tAS_TUPLE(krk_peek(0))->values.values[1] = INTEGER_VAL(status);\n\tAS_TUPLE(krk_peek(0))->values.count = 2;\n\treturn krk_pop();\n}\n\nKRK_Module(_waitpid) {\n\tBIND_FUNC(module,waitpid);\n\n#define BIND_CONST(name) krk_attachNamedValue(&module->fields, #name, INTEGER_VAL(name))\n\n\tBIND_CONST(WNOHANG);\n\tBIND_CONST(WUNTRACED);\n\tBIND_CONST(WSTOPPED);\n\tBIND_CONST(WNOKERN);\n}\n"
  },
  {
    "path": "lib/kuroko/_yutani2.c",
    "content": "#include <errno.h>\n#include <sys/fswait.h>\n#include <toaru/yutani.h>\n#include <toaru/decorations.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/button.h>\n\n#include <kuroko/kuroko.h>\n#include <kuroko/vm.h>\n#include <kuroko/util.h>\n\nstatic KrkInstance * _module;\n\n#define CURRENT_NAME self\n\n#define WRAP_TYPE(kname,yname,pname,...) \\\n\tstatic KrkClass * kname; \\\n\tstruct _yutani_ ## kname { \\\n\t\tKrkInstance inst; \\\n\t\tyname * pname; \\\n\t\t__VA_ARGS__ \\\n\t}; \\\n\t__attribute__((unused)) \\\n\tstatic int _init_check_ ## kname (struct _yutani_ ## kname * self) { \\\n\t\treturn self->pname != NULL; \\\n\t}\n\n#define NO_REINIT(type) do { if (_init_check_ ## type (self)) return krk_runtimeError(vm.exceptions->typeError, \"Can not reinit \" #type); } while (0)\n#define INIT_CHECK(type) do { if (!_init_check_ ## type (self)) return krk_runtimeError(vm.exceptions->typeError, #type \" object uninitialized\"); } while (0)\n\nWRAP_TYPE(Message,yutani_msg_t,msg);\n#define IS_Message(o) (krk_isInstanceOf(o,Message))\n#define AS_Message(o) ((struct _yutani_Message*)AS_OBJECT(o))\nstatic void _Message_gcsweep(KrkInstance * _self) {\n\tstruct _yutani_Message * self = (void*)_self;\n\tfree(self->msg);\n}\n\n#define CURRENT_CTYPE struct _yutani_Message*\n\nKRK_StaticMethod(Message,__new__) {\n\treturn krk_runtimeError(vm.exceptions->typeError, \"can not instantiate Message\");\n}\n\n/* Base type stuff */\nKRK_Method(Message,msg_magic) {\n\tATTRIBUTE_NOT_ASSIGNABLE();\n\treturn INTEGER_VAL(self->msg->magic);\n}\n\nKRK_Method(Message,msg_type) {\n\tATTRIBUTE_NOT_ASSIGNABLE();\n\treturn INTEGER_VAL(self->msg->type);\n}\n\nKRK_Method(Message,msg_size) {\n\tATTRIBUTE_NOT_ASSIGNABLE();\n\treturn INTEGER_VAL(self->msg->size);\n}\n\nKRK_Method(Message,__repr__) {\n\tstruct StringBuilder sb = {0};\n\n\tpushStringBuilder(&sb,'<');\n\n\tconst char * typeName = krk_typeName(argv[0]);\n\tpushStringBuilderStr(&sb, typeName, strlen(typeName));\n\n\tpushStringBuilder(&sb, '>');\n\treturn finishStringBuilder(&sb);\n}\n\n#undef CURRENT_CTYPE\n\n/* Subclass stuff */\n#define MSG_CLS(type) Message_ ## type\n#define WRAP_PROP_INT(cls,kname) KRK_Method(cls,kname) { return INTEGER_VAL(self->kname); }\n#define WRAP_PROP_BOOL(cls,kname) KRK_Method(cls,kname) { return BOOLEAN_VAL(self->kname); }\n\nstatic KrkClass * MSG_CLS(Welcome);\n#define AS_Message_Welcome(o) ((struct yutani_msg_welcome*)AS_Message(o)->msg->data)\n#define IS_Message_Welcome(o) (krk_isInstanceOf(o,MSG_CLS(Welcome)))\n#define CURRENT_CTYPE struct yutani_msg_welcome *\nWRAP_PROP_INT(Message_Welcome,display_width)\nWRAP_PROP_INT(Message_Welcome,display_height)\n#undef CURRENT_CTYPE\n\nstatic KrkClass * MSG_CLS(WindowMouseEvent);\n#define AS_Message_WindowMouseEvent(o) ((struct yutani_msg_window_mouse_event*)AS_Message(o)->msg->data)\n#define IS_Message_WindowMouseEvent(o) (krk_isInstanceOf(o,MSG_CLS(WindowMouseEvent)))\n#define CURRENT_CTYPE struct yutani_msg_window_mouse_event *\nWRAP_PROP_INT(Message_WindowMouseEvent,wid)\nWRAP_PROP_INT(Message_WindowMouseEvent,new_x)\nWRAP_PROP_INT(Message_WindowMouseEvent,new_y)\nWRAP_PROP_INT(Message_WindowMouseEvent,old_x)\nWRAP_PROP_INT(Message_WindowMouseEvent,old_y)\nWRAP_PROP_INT(Message_WindowMouseEvent,buttons)\nWRAP_PROP_INT(Message_WindowMouseEvent,command)\nWRAP_PROP_INT(Message_WindowMouseEvent,modifiers)\n#undef CURRENT_CTYPE\n\n#define AS_Message_WindowFocusChange(o) ((struct yutani_msg_window_focus_change*)AS_Message(o)->msg->data)\n#define IS_Message_WindowFocusChange(o) (krk_isInstanceOf(o,MSG_CLS(WindowFocusChange)))\n#define CURRENT_CTYPE struct yutani_msg_window_focus_change *\nstatic KrkClass * MSG_CLS(WindowFocusChange);\nWRAP_PROP_INT(Message_WindowFocusChange,wid)\nWRAP_PROP_BOOL(Message_WindowFocusChange,focused)\n#undef CURRENT_CTYPE\n\n#define AS_Message_ResizeOffer(o) ((struct yutani_msg_window_resize*)AS_Message(o)->msg->data)\n#define IS_Message_ResizeOffer(o) (krk_isInstanceOf(o,MSG_CLS(ResizeOffer)))\n#define CURRENT_CTYPE struct yutani_msg_window_resize *\nstatic KrkClass * MSG_CLS(ResizeOffer);\nWRAP_PROP_INT(Message_ResizeOffer,wid)\nWRAP_PROP_INT(Message_ResizeOffer,width)\nWRAP_PROP_INT(Message_ResizeOffer,height)\nWRAP_PROP_INT(Message_ResizeOffer,bufid)\n#undef CURRENT_CTYPE\n\n#define AS_Message_WindowAdvertise(o) ((struct yutani_msg_window_advertise*)AS_Message(o)->msg->data)\n#define IS_Message_WindowAdvertise(o) (krk_isInstanceOf(o,MSG_CLS(WindowAdvertise)))\n#define CURRENT_CTYPE struct yutani_msg_window_advertise *\nstatic KrkClass * MSG_CLS(WindowAdvertise);\nWRAP_PROP_INT(Message_WindowAdvertise,wid)\nWRAP_PROP_INT(Message_WindowAdvertise,flags)\nWRAP_PROP_INT(Message_WindowAdvertise,size)\nWRAP_PROP_INT(Message_WindowAdvertise,width)\nWRAP_PROP_INT(Message_WindowAdvertise,height)\nWRAP_PROP_INT(Message_WindowAdvertise,bufid)\nKRK_Method(Message_WindowAdvertise,name) {\n\tchar * s = self->strings;\n\tsize_t l = strlen(s);\n\treturn OBJECT_VAL(krk_copyString(s,l));\n}\nKRK_Method(Message_WindowAdvertise,icon) {\n\tchar * s = self->strings + self->icon;\n\tsize_t l = strlen(s);\n\treturn OBJECT_VAL(krk_copyString(s,l));\n}\n#undef CURRENT_CTYPE\n\n#define AS_Message_WindowMove(o) ((struct yutani_msg_window_move*)AS_Message(o)->msg->data)\n#define IS_Message_WindowMove(o) (krk_isInstanceOf(o,MSG_CLS(WindowMove)))\n#define CURRENT_CTYPE struct yutani_msg_window_move *\nstatic KrkClass * MSG_CLS(WindowMove);\nWRAP_PROP_INT(Message_WindowMove,wid)\nWRAP_PROP_INT(Message_WindowMove,x)\nWRAP_PROP_INT(Message_WindowMove,y)\n#undef CURRENT_CTYPE\n\n#define AS_Message_KeyEvent(o) ((struct yutani_msg_key_event*)AS_Message(o)->msg->data)\n#define IS_Message_KeyEvent(o) (krk_isInstanceOf(o,MSG_CLS(KeyEvent)))\n#define CURRENT_CTYPE struct yutani_msg_key_event *\nstatic KrkClass * MSG_CLS(KeyEvent);\nWRAP_PROP_INT(Message_KeyEvent,wid)\n\n#define WRAP_PROP_FROM(f,p) KRK_Method(Message_KeyEvent,p) { return INTEGER_VAL(self->f.p); }\nWRAP_PROP_FROM(event,keycode)\nWRAP_PROP_FROM(event,modifiers)\nWRAP_PROP_FROM(event,action)\nWRAP_PROP_FROM(event,key)\nWRAP_PROP_FROM(state,kbd_state)\nWRAP_PROP_FROM(state,kbd_s_state)\nWRAP_PROP_FROM(state,k_ctrl)\nWRAP_PROP_FROM(state,k_shift)\nWRAP_PROP_FROM(state,k_alt)\nWRAP_PROP_FROM(state,k_super)\nWRAP_PROP_FROM(state,kl_ctrl)\nWRAP_PROP_FROM(state,kl_shift)\nWRAP_PROP_FROM(state,kl_alt)\nWRAP_PROP_FROM(state,kl_super)\nWRAP_PROP_FROM(state,kr_ctrl)\nWRAP_PROP_FROM(state,kr_shift)\nWRAP_PROP_FROM(state,kr_alt)\nWRAP_PROP_FROM(state,kr_super)\nWRAP_PROP_FROM(state,kbd_esc_buf)\n#undef CURRENT_CTYPE\n\n#define AS_Message_WindowClose(o) ((struct yutani_msg_key_event*)AS_Message(o)->msg->data)\n#define IS_Message_WindowClose(o) (krk_isInstanceOf(o,MSG_CLS(WindowClose)))\n#define CURRENT_CTYPE struct yutani_msg_key_event *\nstatic KrkClass * MSG_CLS(WindowClose);\nWRAP_PROP_INT(Message_WindowClose,wid)\n#undef CURRENT_CTYPE\n\nWRAP_TYPE(YutaniCtx,yutani_t,yctx);\n#define AS_YutaniCtx(o) ((struct _yutani_YutaniCtx*)AS_OBJECT(o))\n#define IS_YutaniCtx(o) (krk_isInstanceOf(o,YutaniCtx))\n#define CURRENT_CTYPE struct _yutani_YutaniCtx*\n\nstatic struct _yutani_YutaniCtx * yctxInstance;\nKRK_StaticMethod(YutaniCtx,__new__) {\n\tif (yctxInstance) return OBJECT_VAL(yctxInstance);\n\n\tKrkClass * cls;\n\tif (!krk_parseArgs(\"O!:YutaniCtx\", (const char*[]){\"cls\"}, KRK_BASE_CLASS(type), &cls)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)krk_newInstance(cls);\n\tkrk_push(OBJECT_VAL(self));\n\n\tyutani_t * yctx = yutani_init();\n\tif (!yctx) return krk_runtimeError(vm.exceptions->ioError, \"Failed to connect to compositor.\");\n\tyctxInstance = self;\n\tself->yctx = yctx;\n\tinit_decorations();\n\tkrk_attachNamedObject(&_module->fields, \"_yutani_t\", (KrkObj*)self);\n\n\treturn krk_pop();\n}\n\nKRK_Method(YutaniCtx,display_width) { return INTEGER_VAL(self->yctx->display_width); }\nKRK_Method(YutaniCtx,display_height) { return INTEGER_VAL(self->yctx->display_height); }\n\nstatic KrkValue makeMessage(yutani_msg_t * result) {\n\tif (!result) return NONE_VAL();\n\n\tKrkClass * msgType = Message;\n\tswitch (result->type) {\n\t\tcase YUTANI_MSG_WELCOME:\n\t\t\tmsgType = Message_Welcome;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\tmsgType = Message_WindowMouseEvent;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\tmsgType = Message_WindowFocusChange;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\tmsgType = Message_ResizeOffer;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_ADVERTISE:\n\t\t\tmsgType = Message_WindowAdvertise;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_MOVE:\n\t\t\tmsgType = Message_WindowMove;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\tmsgType = Message_KeyEvent;\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_CLOSE:\n\t\t\tmsgType = Message_WindowClose;\n\t\t\tbreak;\n\n\t\tdefault: break;\n\t}\n\n\tstruct _yutani_Message * out = (void*)krk_newInstance(msgType);\n\tout->msg = result;\n\treturn OBJECT_VAL(out);\n}\n\nKRK_Method(YutaniCtx,poll) {\n\tint sync = 1;\n\n\tif (!krk_parseArgs(\".|p\", (const char *[]){\"sync\"}, &sync)) return NONE_VAL();\n\n\tyutani_msg_t * result = sync ? yutani_poll(self->yctx) : yutani_poll_async(self->yctx);\n\n\treturn makeMessage(result);\n}\n\nKRK_Method(YutaniCtx,wait_for) {\n\tint msgtype;\n\tif (!krk_parseArgs(\".i\",(const char*[]){\"msgtype\"}, &msgtype)) return NONE_VAL();\n\tyutani_msg_t * result = yutani_wait_for(self->yctx, msgtype);\n\treturn makeMessage(result);\n}\n\nKRK_Method(YutaniCtx,subscribe) {\n\tyutani_subscribe_windows(self->yctx);\n\treturn NONE_VAL();\n}\n\nKRK_Method(YutaniCtx,unsubscribe) {\n\tyutani_unsubscribe_windows(self->yctx);\n\treturn NONE_VAL();\n}\n\nKRK_Method(YutaniCtx,query_windows) {\n\tyutani_query_windows(self->yctx);\n\treturn NONE_VAL();\n}\n\nKRK_Method(YutaniCtx,fileno) {\n\treturn INTEGER_VAL(fileno(self->yctx->sock));\n}\n\nKRK_Method(YutaniCtx,query) {\n\treturn INTEGER_VAL(yutani_query(self->yctx));\n}\n\nKRK_Method(YutaniCtx,menu_process_event) {\n\tstruct _yutani_Message * message = NULL;\n\tif (!krk_parseArgs(\".O!\",(const char *[]){\"message\"}, Message, &message)) return NONE_VAL();\n\treturn INTEGER_VAL(menu_process_event(self->yctx, message->msg));\n}\n\n#undef CURRENT_CTYPE\n\nWRAP_TYPE(GraphicsContext,gfx_context_t,ctx,\n\tint doubleBuffered;\n);\n\nWRAP_TYPE(Sprite,gfx_context_t,ctx,\n\tint doubleBuffered;\n\tsprite_t * sprite;\n);\n\nWRAP_TYPE(Window,gfx_context_t,ctx,\n\tint doubleBuffered;\n\tyutani_window_t * window;\n\tKrkValue title;\n\tKrkValue icon;\n\tint closed;\n);\n\nWRAP_TYPE(Subregion,gfx_context_t,ctx,\n\tint doubleBuffered;\n\tint x;\n\tint y;\n);\n\n#define IS_GraphicsContext(o) (krk_isInstanceOf(o,GraphicsContext))\n#define AS_GraphicsContext(o) ((struct _yutani_GraphicsContext*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_GraphicsContext*\n\n#define CHECK_GFX() do { if (!self->ctx) return krk_runtimeError(vm.exceptions->valueError, \"invalid context\"); } while (0)\n\nKRK_StaticMethod(GraphicsContext,__new__) {\n\tKrkClass * cls;\n\tint _argc;\n\tconst KrkValue * _argv;\n\n\tif (!krk_parseArgs(\"O!*~\",(const char*[]){\"cls\"},KRK_BASE_CLASS(type),&cls,&_argc,&_argv)) return NONE_VAL();\n\n\tif (!krk_isSubClass(cls, GraphicsContext)) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"%S is not a subclass of GraphicsContext\", cls->name);\n\t}\n\n\tif (cls == GraphicsContext) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"Can not create GraphicsContext\");\n\t}\n\n\treturn OBJECT_VAL(krk_newInstance(cls));\n}\n\nKRK_Method(GraphicsContext,fill) {\n\tuint32_t color;\n\tif (!krk_parseArgs(\".I\",(const char*[]){\"color\"},&color)) return NONE_VAL();\n\tCHECK_GFX();\n\tdraw_fill(self->ctx, color);\n\treturn NONE_VAL();\n}\n\nKRK_Method(GraphicsContext,flip) {\n\tCHECK_GFX();\n\tif (self->doubleBuffered) {\n\t\tflip(self->ctx);\n\t}\n\treturn NONE_VAL();\n}\n\nKRK_Method(GraphicsContext,blur) {\n\tint radius = 2;\n\tif (!krk_parseArgs(\".|I\",(const char*[]){\"radius\"}, &radius)) return NONE_VAL();\n\tCHECK_GFX();\n\tblur_context_box(self->ctx, radius);\n\treturn NONE_VAL();\n}\n\nKRK_Method(GraphicsContext,line) {\n\tint x0, x1, y0, y1;\n\tuint32_t color;\n\tKrkValue thickness = NONE_VAL();\n\n\tif (!krk_parseArgs(\".iiiiI|V\",(const char*[]){\"x0\",\"x1\",\"y0\",\"y1\",\"color\",\"thickness\"},\n\t\t&x0, &x1, &y0, &y1, &color, &thickness)) return NONE_VAL();\n\n\tif (IS_NONE(thickness)) {\n\t\tdraw_line(self->ctx, x0, x1, y0, y1, color);\n\t} else if (IS_INTEGER(thickness)) {\n\t\tdraw_line_thick(self->ctx, x0, x1, y0, y1, color, AS_INTEGER(thickness));\n\t} else if (IS_FLOATING(thickness)) {\n\t\tdraw_line_aa(self->ctx, x0, x1, y0, y1, color, AS_FLOATING(thickness));\n\t} else {\n\t\tTYPE_ERROR(int or float,thickness);\n\t}\n\treturn NONE_VAL();\n}\n\nKRK_Method(GraphicsContext,rect) {\n\tint x, y;\n\tunsigned int width, height;\n\tuint32_t color;\n\tint solid = 0;\n\tunsigned int radius = 0;\n\n\tif (!krk_parseArgs(\".iiIII|pI\",(const char*[]){\"x\",\"y\",\"width\",\"height\",\"color\",\"solid\",\"radius\"},\n\t\t&x, &y, &width, &height, &color, &solid, &radius)) return NONE_VAL();\n\n\tif (solid && radius) {\n\t\treturn krk_runtimeError(vm.exceptions->valueError, \"can not combine 'radius' and 'solid'\");\n\t}\n\n\tCHECK_GFX();\n\n\tif (radius) {\n\t\tdraw_rounded_rectangle(self->ctx, x, y, width, height, radius, color);\n\t} else if (solid) {\n\t\tdraw_rectangle_solid(self->ctx, x, y, width, height, color);\n\t} else {\n\t\tdraw_rectangle(self->ctx, x, y, width, height, color);\n\t}\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(GraphicsContext,width) { CHECK_GFX(); return INTEGER_VAL(self->ctx->width); }\nKRK_Method(GraphicsContext,height) { CHECK_GFX(); return INTEGER_VAL(self->ctx->height); }\nKRK_Method(GraphicsContext,isDoubleBuffered) { CHECK_GFX(); return BOOLEAN_VAL(self->doubleBuffered); }\n\nKRK_Method(GraphicsContext,__setitem__) {\n\tCHECK_GFX();\n\n\tKrkTuple * coord = NULL;\n\tuint32_t color = 0;\n\n\tif (!krk_parseArgs(\n\t\t\".O!I\",(const char*[]){\"coord\",\"color\"},\n\t\tKRK_BASE_CLASS(tuple), &coord, &color)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tif (!coord || coord->values.count != 2 || !IS_INTEGER(coord->values.values[0]) || !IS_INTEGER(coord->values.values[1])) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"coord must be (int,int)\");\n\t}\n\n\tint x = AS_INTEGER(coord->values.values[0]);\n\tint y = AS_INTEGER(coord->values.values[1]);\n\n\tif (x < 0 || x >= self->ctx->width || y < 0 || y >= self->ctx->height) {\n\t\treturn krk_runtimeError(vm.exceptions->indexError, \"coordinate out of bounds\");\n\t}\n\n\tGFX(self->ctx,x,y) = color;\n\n\treturn INTEGER_VAL(color);\n}\n\nKRK_Method(GraphicsContext,__getitem__) {\n\tCHECK_GFX();\n\n\tKrkTuple * coord = NULL;\n\n\tif (!krk_parseArgs(\n\t\t\".O!\",(const char*[]){\"coord\"},\n\t\tKRK_BASE_CLASS(tuple), &coord)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tif (!coord || coord->values.count != 2 || !IS_INTEGER(coord->values.values[0]) || !IS_INTEGER(coord->values.values[1])) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"coord must be (int,int)\");\n\t}\n\n\tint x = AS_INTEGER(coord->values.values[0]);\n\tint y = AS_INTEGER(coord->values.values[1]);\n\n\tif (x < 0 || x >= self->ctx->width || y < 0 || y >= self->ctx->height) {\n\t\treturn krk_runtimeError(vm.exceptions->indexError, \"coordinate out of bounds\");\n\t}\n\n\treturn INTEGER_VAL(GFX(self->ctx,x,y));\n}\n\nKRK_Method(GraphicsContext,draw_sprite) {\n\tCHECK_GFX();\n\n\tstruct _yutani_Sprite * sprite;\n\tint x = 0;\n\tint y = 0;\n\tdouble alpha = 1.0;\n\tdouble rotation = 0.0;\n\tKrkTuple * scale = NULL;\n\tuint32_t color = 0;\n\n\tif (!krk_parseArgs(\n\t\t\".O!|iiddO!I\",(const char*[]){\"sprite\",\"x\",\"y\",\"alpha\",\"rotation\",\"scale\",\"color\"},\n\t\tSprite, &sprite,\n\t\t&x, &y,\n\t\t&alpha, &rotation,\n\t\tKRK_BASE_CLASS(tuple), &scale,\n\t\t&color)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tif (scale) {\n\t\tif (scale->values.count != 2 || !IS_INTEGER(scale->values.values[0]) || !IS_INTEGER(scale->values.values[1])) {\n\t\t\treturn krk_runtimeError(vm.exceptions->typeError, \"scale must be (int,int)\");\n\t\t}\n\n\t\tint32_t width  = AS_INTEGER(scale->values.values[0]);\n\t\tint32_t height = AS_INTEGER(scale->values.values[1]);\n\n\t\tif (alpha == 1.0) {\n\t\t\tdraw_sprite_scaled(self->ctx, sprite->sprite, x, y, width, height);\n\t\t} else {\n\t\t\tdraw_sprite_scaled_alpha(self->ctx, sprite->sprite, x, y, width, height, alpha);\n\t\t}\n\t} else if (color) {\n\t\tdraw_sprite_alpha_paint(self->ctx, sprite->sprite, x, y, alpha, color);\n\t} else if (rotation != 0.0) {\n\t\tdraw_sprite_rotate(self->ctx, sprite->sprite, x, y, rotation, alpha);\n\t} else if (alpha == 1.0) {\n\t\tdraw_sprite(self->ctx, sprite->sprite, x, y);\n\t} else {\n\t\tdraw_sprite_alpha(self->ctx, sprite->sprite, x, y, alpha);\n\t}\n\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\nstatic void _yutani_Sprite_gcsweep(KrkInstance * _self) {\n\tstruct _yutani_Sprite * self = (void*)_self;\n\tif (self->sprite) sprite_free(self->sprite);\n\tif (self->ctx) release_graphics_yutani(self->ctx);\n}\n\n#define IS_Sprite(o) (krk_isInstanceOf(o,Sprite))\n#define AS_Sprite(o) ((struct _yutani_Sprite*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_Sprite*\n\n/**\n * Sprite(file=None,*,width=0,height=0)\n *\n * Either file or width/height need to be specified, but not both.\n */\nKRK_Method(Sprite,__init__) {\n\tconst char * filename = NULL;\n\tunsigned int width = 0;\n\tunsigned int height = 0;\n\n\tif (!krk_parseArgs(\n\t\t\".|z$II:Sprite\",(const char*[]){\"file\",\"width\",\"height\"},\n\t\t&filename,\n\t\t&width,\n\t\t&height\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tif ((!filename && (!width || !height)) || (filename && (width || height))) {\n\t\treturn krk_runtimeError(vm.exceptions->argumentError,\n\t\t\t\"Either 'file' or both of 'width' and 'height' must be provided, but not both.\");\n\t}\n\n\tNO_REINIT(Sprite);\n\n\tsprite_t * sprite;\n\n\t/* Set up sprite */\n\tif (!filename) {\n\t\t/* Want to build a new sprite */\n\t\tsprite = create_sprite(width, height, ALPHA_EMBEDDED);\n\t} else {\n\t\tsprite = malloc(sizeof(sprite_t));\n\t\tif (load_sprite(sprite, filename)) {\n\t\t\tfree(sprite);\n\t\t\treturn krk_runtimeError(vm.exceptions->ioError, \"could not load sprite from '%s'\", filename);\n\t\t}\n\t}\n\n\t/* Initialize representative graphics context */\n\tgfx_context_t * ctx = init_graphics_sprite(sprite);\n\n\tself->ctx = ctx;\n\tself->sprite = sprite;\n\n\t/* Keep the file if we had one */\n\tkrk_attachNamedValue(&self->inst.fields, \"file\",\n\t\tfilename ? OBJECT_VAL(krk_copyString(filename,strlen(filename))) : NONE_VAL());\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(Sprite,__repr__) {\n\tKrkValue file = NONE_VAL();\n\tkrk_tableGet_fast(&self->inst.fields, S(\"file\"), &file);\n\n\tINIT_CHECK(Sprite);\n\n\tif (!IS_NONE(file)) {\n\t\treturn krk_stringFromFormat(\"Sprite(file=%R,width=%u,height=%u)\",\n\t\t\tfile, self->sprite->width, self->sprite->height);\n\t} else {\n\t\treturn krk_stringFromFormat(\"Sprite(width=%u,height=%u)\",\n\t\t\tself->sprite->width, self->sprite->height);\n\t}\n}\n\nKRK_Method(Sprite,free) {\n\tINIT_CHECK(Sprite);\n\tif (self->sprite) sprite_free(self->sprite);\n\tif (self->ctx) release_graphics_yutani(self->ctx);\n\tself->sprite = NULL;\n\tself->ctx = NULL;\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\nstatic void _yutani_Window_gcscan(KrkInstance * _self) {\n\tstruct _yutani_Window * self = (void*)_self;\n\tkrk_markValue(self->title);\n\tkrk_markValue(self->icon);\n}\n\n#define IS_Window(o) (krk_isInstanceOf(o,Window))\n#define AS_Window(o) ((struct _yutani_Window*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_Window*\n\nstatic void update_window_title(struct _yutani_Window * self) {\n\tif (IS_STRING(self->title)) {\n\t\tif (IS_STRING(self->icon)) {\n\t\t\tyutani_window_advertise_icon(yctxInstance->yctx, self->window, AS_CSTRING(self->title), AS_CSTRING(self->icon));\n\t\t} else {\n\t\t\tyutani_window_advertise(yctxInstance->yctx, self->window, AS_CSTRING(self->title));\n\t\t}\n\t}\n\n\t/* TODO Update decorations */\n}\n\nKRK_Method(Window,__init__) {\n\tunsigned int width;\n\tunsigned int height;\n\tunsigned int flags = 0;\n\tKrkValue title = NONE_VAL();\n\tKrkValue icon = NONE_VAL();\n\tint doublebuffer = 1;\n\n\tif (!yctxInstance) return krk_runtimeError(vm.exceptions->valueError, \"Compositor is not initialized\");\n\n\tif (!krk_parseArgs(\n\t\t\".II|IV!V!p:Window\",(const char *[]){\"width\",\"height\",\"flags\",\"title\",\"icon\",\"doublebuffer\"},\n\t\t&width, &height,\n\t\t&flags,\n\t\tKRK_BASE_CLASS(str), &title,\n\t\tKRK_BASE_CLASS(str), &icon,\n\t\t&doublebuffer\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(Window);\n\n\tself->window = yutani_window_create_flags(yctxInstance->yctx, width, height, flags);\n\tself->doubleBuffered = doublebuffer;\n\tself->ctx = doublebuffer ? init_graphics_yutani_double_buffer(self->window) : init_graphics_yutani(self->window);\n\tself->title = title;\n\tself->icon = icon;\n\tself->closed = 0;\n\n\tupdate_window_title(self);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,title) {\n\tINIT_CHECK(Window);\n\tif (argc > 1) {\n\t\tif (!IS_STRING(argv[1]) && !IS_NONE(argv[1])) return TYPE_ERROR(str,argv[1]);\n\t\tself->title = argv[1];\n\t\tupdate_window_title(self);\n\t}\n\treturn self->title;\n}\n\nKRK_Method(Window,icon) {\n\tINIT_CHECK(Window);\n\tif (argc > 1) {\n\t\tif (!IS_STRING(argv[1]) && !IS_NONE(argv[1])) return TYPE_ERROR(str,argv[1]);\n\t\tself->icon = argv[1];\n\t\tupdate_window_title(self);\n\t}\n\n\treturn self->icon;\n}\n\nKRK_Method(Window,wid) { INIT_CHECK(Window); return INTEGER_VAL(self->window->wid); }\nKRK_Method(Window,x) { INIT_CHECK(Window); return INTEGER_VAL(self->window->x); }\nKRK_Method(Window,y) { INIT_CHECK(Window); return INTEGER_VAL(self->window->y); }\nKRK_Method(Window,focused) {\n\tINIT_CHECK(Window);\n\n\tif (argc > 1) {\n\t\tif (!IS_BOOLEAN(argv[1])) return krk_runtimeError(vm.exceptions->typeError, \"focused must be bool, not %T\", argv[1]);\n\t\tself->window->focused = AS_BOOLEAN(argv[1]);\n\t}\n\n\treturn BOOLEAN_VAL(self->window->focused);\n}\n\nKRK_Method(Window,closed) { return INTEGER_VAL(self->closed); }\n\nKRK_Method(Window,__repr__) {\n\tINIT_CHECK(Window);\n\tif (!self->window) {\n\t\treturn krk_stringFromFormat(\"Window(title=%R,closed=True)\", self->title);\n\t}\n\treturn krk_stringFromFormat(\"Window(wid=%d,title=%R,width=%d,height=%d)\",\n\t\tself->window->wid, self->title, self->window->width, self->window->height);\n}\n\nKRK_Method(Window,flip) {\n\tINIT_CHECK(Window);\n\tif (self->doubleBuffered) {\n\t\tflip(self->ctx);\n\t}\n\tyutani_flip(yctxInstance->yctx, self->window);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,move) {\n\tint x, y;\n\tif (!krk_parseArgs(\".ii\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_move(yctxInstance->yctx, self->window, x, y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,close) {\n\tINIT_CHECK(Window);\n\tyutani_close(yctxInstance->yctx, self->window);\n\tself->window = NULL;\n\trelease_graphics_yutani(self->ctx);\n\tself->ctx = NULL;\n\tself->closed = 1;\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,set_stack) {\n\tunsigned int z;\n\tif (!krk_parseArgs(\".I\", (const char*[]){\"z\"}, &z)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_set_stack(yctxInstance->yctx, self->window, z);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,special_request) {\n\tunsigned int request;\n\tif (!krk_parseArgs(\".I\", (const char*[]){\"request\"}, &request)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_special_request(yctxInstance->yctx, self->window, request);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,resize) {\n\tunsigned int width, height;\n\tif (!krk_parseArgs(\".II\", (const char*[]){\"width\",\"height\"}, &width, &height)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_resize(yctxInstance->yctx, self->window, width, height);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,resize_start) {\n\tunsigned int direction;\n\tif (!krk_parseArgs(\".I\", (const char*[]){\"direction\"}, &direction)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_resize_start(yctxInstance->yctx, self->window, direction);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,resize_done) {\n\tINIT_CHECK(Window);\n\tyutani_window_resize_done(yctxInstance->yctx, self->window);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,resize_offer) {\n\tunsigned int width, height;\n\tif (!krk_parseArgs(\".II\", (const char*[]){\"width\",\"height\"}, &width, &height)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_resize_offer(yctxInstance->yctx, self->window, width, height);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,resize_accept) {\n\tunsigned int width, height;\n\tif (!krk_parseArgs(\".II\", (const char*[]){\"width\",\"height\"}, &width, &height)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_resize_accept(yctxInstance->yctx, self->window, width, height);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,update_shape) {\n\tunsigned int threshold;\n\tif (!krk_parseArgs(\".I\", (const char*[]){\"threshold\"}, &threshold)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_update_shape(yctxInstance->yctx, self->window, threshold);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,show_mouse) {\n\tunsigned int mouse;\n\tif (!krk_parseArgs(\".I\", (const char*[]){\"mouse\"}, &mouse)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_show_mouse(yctxInstance->yctx, self->window, mouse);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,warp_mouse) {\n\tint x, y;\n\tif (!krk_parseArgs(\".ii\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tINIT_CHECK(Window);\n\tyutani_window_warp_mouse(yctxInstance->yctx, self->window, x, y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Window,reinit) {\n\tINIT_CHECK(Window);\n\treinit_graphics_yutani(self->ctx, self->window);\n\treturn NONE_VAL();\n}\n#undef CURRENT_CTYPE\n\nstatic void _yutani_Subregion_gcsweep(KrkInstance * _self) {\n\tstruct _yutani_Subregion * self = (void*)_self;\n\tif (self->ctx) {\n\t\tif (self->ctx->clips) {\n\t\t\tfree(self->ctx->clips);\n\t\t}\n\t\tfree(self->ctx);\n\t\tself->ctx = NULL;\n\t}\n}\n\n#define IS_Subregion(o) (krk_isInstanceOf(o,Subregion))\n#define AS_Subregion(o) ((struct _yutani_Subregion*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_Subregion*\n\nKRK_Method(Subregion,__init__) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tint x, y, w, h;\n\tif (!krk_parseArgs(\n\t\t\".O!iiii:Subregion\", (const char*[]){\"ctx\",\"x\",\"y\",\"w\",\"h\"},\n\t\tGraphicsContext, &ctx,\n\t\t&x, &y, &w, &h)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(Subregion);\n\n\tif (!ctx->ctx) return krk_runtimeError(vm.exceptions->typeError, \"ctx is not initialized\");\n\n\tif (w < 0 || h < 0) return krk_runtimeError(vm.exceptions->typeError, \"invalid subregion\");\n\n\tif (x < 0) {\n\t\tw += x;\n\t\tx = 0;\n\t}\n\n\tif (y < 0) {\n\t\th += y;\n\t\ty = 0;\n\t}\n\n\tif (x >= ctx->ctx->width || y >= ctx->ctx->height) {\n\t\tx = 0; y = 0; w = 0; h = 0;\n\t}\n\n\tif (x + w > ctx->ctx->width) {\n\t\tw = ctx->ctx->width - x;\n\t}\n\n\tif (y + h > ctx->ctx->height) {\n\t\th = ctx->ctx->height - y;\n\t}\n\n\tgfx_context_t * sub = init_graphics_subregion(ctx->ctx, x, y, w, h);\n\tself->ctx = sub;\n\tself->doubleBuffered = ctx->doubleBuffered;\n\tself->x = x;\n\tself->y = y;\n\tkrk_attachNamedObject(&self->inst.fields, \"parent\", (KrkObj*)ctx);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(Subregion,offset_x) {\n\tATTRIBUTE_NOT_ASSIGNABLE();\n\treturn INTEGER_VAL(self->x);\n}\n\nKRK_Method(Subregion,offset_y) {\n\tATTRIBUTE_NOT_ASSIGNABLE();\n\treturn INTEGER_VAL(self->y);\n}\n\n#undef CURRENT_CTYPE\n\nstatic KrkClass * TransformMatrix;\nstruct _yutani_TransformMatrix {\n\tKrkInstance inst;\n\tgfx_matrix_t matrix;\n};\n\n#define IS_TransformMatrix(o) (krk_isInstanceOf(o,TransformMatrix))\n#define AS_TransformMatrix(o) ((struct _yutani_TransformMatrix*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_TransformMatrix*\n\nKRK_Method(TransformMatrix,__init__) {\n\tdouble a = 1.0, b = 0, tx = 0, c = 0, d = 1.0, ty = 0;\n\tif (!krk_parseArgs(\".|dddddd:TransformMatrix\",\n\t\t(const char*[]){\"a\",\"b\",\"tx\",\"c\",\"d\",\"ty\"}, \n\t\t&a,&b,&tx,&c,&d,&ty)) return NONE_VAL();\n\tself->matrix[0][0] = a;\n\tself->matrix[0][1] = b;\n\tself->matrix[0][2] = tx;\n\tself->matrix[1][0] = c;\n\tself->matrix[1][1] = d;\n\tself->matrix[1][2] = ty;\n\treturn NONE_VAL();\n}\n\n#define MATRIX_VAR(name,row,col) KRK_Method(TransformMatrix,name) { \\\n\tdouble x = self->matrix[row][col]; \\\n\tif (!krk_parseArgs(\".|d\",(const char*[]){\"val\"},&x)) return NONE_VAL(); \\\n\tself->matrix[row][col] = x; \\\n\treturn FLOATING_VAL(x); \\\n}\n\nMATRIX_VAR(a,0,0)\nMATRIX_VAR(b,0,1)\nMATRIX_VAR(tx,0,1)\nMATRIX_VAR(c,1,0)\nMATRIX_VAR(d,1,1)\nMATRIX_VAR(ty,1,1)\n\nKRK_Method(TransformMatrix,__repr__) {\n\tstruct StringBuilder sb = {};\n\n\tKrkValue floats[6];\n\n\tfor (int i = 0; i < 6; ++i) {\n\t\tfloats[i] = FLOATING_VAL(self->matrix[i/3][i%3]);\n\t}\n\n\tkrk_pushStringBuilderFormat(&sb, \"TransformMatrix[ [%R,%R,%R] [%R,%R,%R] ]\",\n\t\tfloats[0], floats[1], floats[2], floats[3], floats[4], floats[5]);\n\n\treturn krk_finishStringBuilder(&sb);\n}\n\nKRK_Method(TransformMatrix,scale) {\n\tdouble x, y;\n\tif (!krk_parseArgs(\".dd\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tgfx_matrix_scale(self->matrix,x,y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TransformMatrix,translate) {\n\tdouble x, y;\n\tif (!krk_parseArgs(\".dd\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tgfx_matrix_translate(self->matrix,x,y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TransformMatrix,rotate) {\n\tdouble r;\n\tif (!krk_parseArgs(\".d\", (const char*[]){\"r\"}, &r)) return NONE_VAL();\n\tgfx_matrix_rotate(self->matrix,r);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TransformMatrix,shear) {\n\tdouble x, y;\n\tif (!krk_parseArgs(\".dd\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tgfx_matrix_shear(self->matrix,x,y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TransformMatrix,apply) {\n\tdouble x, y;\n\tif (!krk_parseArgs(\".dd\", (const char*[]){\"x\",\"y\"}, &x, &y)) return NONE_VAL();\n\tKrkTuple * out = krk_newTuple(2);\n\tkrk_push(OBJECT_VAL(out));\n\n\tdouble o_x, o_y;\n\tgfx_apply_matrix(x,y,self->matrix,&o_x,&o_y);\n\tout->values.values[out->values.count++] = FLOATING_VAL(o_x);\n\tout->values.values[out->values.count++] = FLOATING_VAL(o_y);\n\n\treturn krk_pop();\n}\n\n#undef CURRENT_CTYPE\n\nWRAP_TYPE(Font,struct TT_Font,fontData,\n\tint fontSize;\n\tuint32_t fontColor;\n);\n\n#define IS_Font(o) (krk_isInstanceOf(o,Font))\n#define AS_Font(o) ((struct _yutani_Font*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_Font*\n\nWRAP_TYPE(TTShape,struct TT_Shape, shape);\n#define IS_TTShape(o) (krk_isInstanceOf(o,TTShape))\n#define AS_TTShape(o) ((struct _yutani_TTShape*)AS_OBJECT(o))\n\nWRAP_TYPE(TTContour,struct TT_Contour, contour);\n#define IS_TTContour(o) (krk_isInstanceOf(o,TTContour))\n#define AS_TTContour(o) ((struct _yutani_TTContour*)AS_OBJECT(o))\n\n#define CHECK_FONT() do { if (!self->fontData) return krk_runtimeError(vm.exceptions->valueError, \"font is uninitialized\"); } while (0)\n\nstatic void _yutani_Font_gcsweep(KrkInstance * _self) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self;\n\tif (self->fontData) free(self->fontData);\n}\n\nKRK_Method(Font,__init__) {\n\tconst char * filename;\n\tint size;\n\tuint32_t color = rgb(0,0,0);\n\n\tif (!krk_parseArgs(\n\t\t\".si|I:Font\",(const char*[]){\"font\",\"size\",\"color\"},\n\t\t&filename, &size, &color)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(Font);\n\n\tstruct TT_Font * fd;\n\n\tif (strstr(filename, \"sans-serif\") == filename || strstr(filename, \"monospace\") == filename) {\n\t\tfd = tt_font_from_shm(filename);\n\t} else {\n\t\tfd = tt_font_from_file(filename);\n\t}\n\n\tif (!fd) {\n\t\treturn krk_runtimeError(vm.exceptions->ioError, \"failed to load '%s'\", filename);\n\t}\n\n\ttt_set_size(fd, size);\n\n\tself->fontData = fd;\n\tself->fontSize = size;\n\tself->fontColor = color;\n\n\tkrk_attachNamedValue(&self->inst.fields, \"file\", OBJECT_VAL(krk_copyString(filename, strlen(filename))));\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(Font,size) {\n\tINIT_CHECK(Font);\n\tif (argc > 1) {\n\t\tif (!IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, \"size must be int, not %T\", argv[1]);\n\t\tself->fontSize = AS_INTEGER(argv[1]);\n\t\ttt_set_size(self->fontData, self->fontSize);\n\t}\n\n\treturn self->fontSize;\n}\n\nKRK_Method(Font,draw_string) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tconst char * s;\n\tint x;\n\tint y;\n\n\tif (!krk_parseArgs(\n\t\t\".O!sii\", (const char*[]){\"ctx\",\"s\",\"x\",\"y\"},\n\t\tGraphicsContext, &ctx,\n\t\t&s, &x, &y)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(Font);\n\treturn INTEGER_VAL(tt_draw_string(ctx->ctx, self->fontData, x, y, s, self->fontColor));\n}\n\nKRK_Method(Font,draw_string_shadow) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tconst char * s;\n\tint x;\n\tint y;\n\tuint32_t shadow;\n\tint blur;\n\n\tif (!krk_parseArgs(\n\t\t\".O!siiIi\", (const char*[]){\"ctx\",\"s\",\"x\",\"y\",\"shadow\",\"blur\"},\n\t\tGraphicsContext, &ctx,\n\t\t&s, &x, &y, &shadow, &blur)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(Font);\n\n\t/* This has a weird API for reasons I can't remember */\n\ttt_draw_string_shadow(ctx->ctx, self->fontData, (char*)s, self->fontSize, x, y, self->fontColor, shadow, blur);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(Font,width) {\n\tconst char * s;\n\tif (!krk_parseArgs(\".s\", (const char*[]){\"s\"}, &s)) return NONE_VAL();\n\tINIT_CHECK(Font);\n\treturn INTEGER_VAL(tt_string_width(self->fontData, s));\n}\n\nKRK_Method(Font,measure) {\n\tINIT_CHECK(Font);\n\n\tKrkTuple * out = krk_newTuple(3);\n\tkrk_push(OBJECT_VAL(out));\n\n\tstruct TT_FontMetrics metrics;\n\n\ttt_measure_font(self->fontData, &metrics);\n\n\tout->values.values[out->values.count++] = FLOATING_VAL(metrics.ascender);\n\tout->values.values[out->values.count++] = FLOATING_VAL(metrics.descender);\n\tout->values.values[out->values.count++] = FLOATING_VAL(metrics.lineGap);\n\n\treturn krk_pop();\n}\n\nKRK_Method(Font,draw_glyph_into) {\n\tINIT_CHECK(Font);\n\n\tstruct _yutani_TTContour * contour;\n\tfloat x, y;\n\tunsigned int glyph;\n\n\tif (!krk_parseArgs(\".O!ffI\",\n\t\t(const char*[]){\"contour\",\"x\",\"y\",\"glyph\"},\n\t\tTTContour, &contour,\n\t\t&x, &y, &glyph)) return NONE_VAL();\n\n\tif (!contour->contour) return krk_runtimeError(vm.exceptions->typeError, \"contour is not initialized\");\n\n\t/* tt_draw_glyph_into returns potentially-realloc'd contour, but we'll return nothing and\n\t * just mutate the passed contour object. */\n\tcontour->contour = tt_draw_glyph_into(contour->contour, self->fontData, x, y, glyph);\n\treturn NONE_VAL();\n}\n\nKRK_Method(Font,prepare_string) {\n\tINIT_CHECK(Font);\n\n\tstruct _yutani_TTContour * contour = NULL;\n\tfloat x, y;\n\tconst char * s;\n\n\tif (!krk_parseArgs(\".ffs|O!\", (const char*[]){\"x\",\"y\",\"s\",\"into\"},\n\t\t&x, &y, &s, TTContour, &contour)) return NONE_VAL();\n\n\tfloat out_width = 0;\n\tKrkTuple * out_tuple = krk_newTuple(2); /* contour, width */\n\tkrk_push(OBJECT_VAL(out_tuple));\n\n\t/* if @c into is unset, make a new one to store result; otherwise, @c into is updated */\n\tif (!contour) {\n\t\tcontour = (struct _yutani_TTContour*)krk_newInstance(TTContour);\n\t}\n\n\tcontour->contour = tt_prepare_string_into(contour->contour, self->fontData, x, y, s, &out_width);\n\tout_tuple->values.values[out_tuple->values.count++] = OBJECT_VAL(contour);\n\tout_tuple->values.values[out_tuple->values.count++] = FLOATING_VAL(out_width);\n\n\treturn krk_pop();\n}\n\nKRK_Method(Font,ellipsify) {\n\tconst char * s;\n\tint max_width;\n\n\tif (!krk_parseArgs(\".si\", (const char*[]){\"s\",\"w\"},\n\t\t&s, &max_width)) return NONE_VAL();\n\n\tint out_width = 0;\n\tchar * out = tt_ellipsify(s, self->fontSize, self->fontData, max_width, &out_width);\n\n\tKrkTuple * out_tuple = krk_newTuple(2);\n\tkrk_push(OBJECT_VAL(out_tuple));\n\n\tout_tuple->values.values[out_tuple->values.count++] = OBJECT_VAL(krk_copyString(out, strlen(out)));\n\tout_tuple->values.values[out_tuple->values.count++] = INTEGER_VAL(out_width);\n\n\tfree(out);\n\n\treturn krk_pop();\n}\n\n#undef CURRENT_CTYPE\n\nWRAP_TYPE(MenuBar,struct menu_bar,menuBar);\nWRAP_TYPE(MenuList,struct MenuList, menuList);\nWRAP_TYPE(MenuEntry,struct MenuEntry, menuEntry);\nWRAP_TYPE(MenuEntrySubmenu,struct MenuEntry, menuEntry);\nWRAP_TYPE(MenuEntrySeparator,struct MenuEntry, menuEntry);\nWRAP_TYPE(MenuEntryToggle,struct MenuEntry, menuEntry);\nWRAP_TYPE(MenuEntryCustom,struct MenuEntry, menuEntry);\n\n#define IS_MenuBar(o) (krk_isInstanceOf(o,MenuBar))\n#define AS_MenuBar(o) ((struct _yutani_MenuBar*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuBar*\n\nstatic void _yutani_MenuBar_gcsweep(KrkInstance * _self) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self;\n\tif (self->menuBar->entries) {\n\t\tfor (size_t i = 0; self->menuBar->entries[i].title; ++i) {\n\t\t\tfree(self->menuBar->entries[i].title);\n\t\t\tfree(self->menuBar->entries[i].action);\n\t\t}\n\t\tfree(self->menuBar->entries);\n\t}\n\tfree(self->menuBar);\n}\n\nstatic void _menubar_callback(struct menu_bar * _self) {\n\tCURRENT_CTYPE self = _self->_private;\n\tKrkValue callback;\n\tif (krk_tableGet(&self->inst.fields, OBJECT_VAL(S(\"callback\")), &callback)) {\n\t\tkrk_push(callback);\n\t\tkrk_push(OBJECT_VAL(self));\n\t\tkrk_callStack(1);\n\t}\n}\n\nKRK_Method(MenuBar,__init__) {\n\tKrkTuple * entries;\n\n\tif (!krk_parseArgs(\n\t\t\".O!:MenuBar\", (const char*[]){\"entries\"},\n\t\tKRK_BASE_CLASS(tuple), &entries)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(MenuBar);\n\n\tsize_t count = entries->values.count;\n\tstruct menu_bar * out = calloc(sizeof(struct menu_bar), 1);\n\tout->entries = calloc(sizeof(struct menu_bar_entries), count + 1);\n\n\tfor (size_t i = 0; i < count; ++i) {\n\t\tKrkValue entry = entries->values.values[i];\n\t\tif (!IS_TUPLE(entry) || AS_TUPLE(entry)->values.count != 2 ||\n\t\t\t!IS_STRING(AS_TUPLE(entry)->values.values[0]) || !IS_STRING(AS_TUPLE(entry)->values.values[1])) {\n\t\t\tkrk_runtimeError(vm.exceptions->typeError, \"entries member should be tuple[str,str], not %T\", entry);\n\t\t\tgoto _error;\n\t\t}\n\n\t\tout->entries[i].title = strdup(AS_CSTRING(AS_TUPLE(entry)->values.values[0]));\n\t\tout->entries[i].action = strdup(AS_CSTRING(AS_TUPLE(entry)->values.values[1]));\n\t}\n\n\tout->entries[count].title = NULL;\n\tout->entries[count].action = NULL;\n\n\tout->set = menu_set_create();\n\n\tself->menuBar = out;\n\tout->_private = self;\n\tout->redraw_callback = _menubar_callback;\n\n\tkrk_attachNamedValue(&self->inst.fields, \"entries\", OBJECT_VAL(entries));\n\tkrk_attachNamedValue(&self->inst.fields, \"set\", krk_dict_of(0,NULL,0));\n\n\treturn NONE_VAL();\n\n_error:\n\tfor (size_t i = 0; i < count; ++i) {\n\t\tif (out->entries[i].title) free(out->entries[i].title);\n\t\tif (out->entries[i].action) free(out->entries[i].action);\n\t}\n\tfree(out->entries);\n\tfree(out);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuBar,place) {\n\tint x, y;\n\tunsigned int width;\n\tstruct _yutani_Window * window;\n\n\tif (!krk_parseArgs(\n\t\t\".iiIO!\", (const char*[]){\"x\",\"y\",\"width\",\"window\"},\n\t\t&x, &y, &width,\n\t\tWindow, &window)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(MenuBar);\n\n\tself->menuBar->x = x;\n\tself->menuBar->y = y;\n\tself->menuBar->width = width;\n\tself->menuBar->window = window->window;\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuBar,render) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tif (!krk_parseArgs(\".O!\",(const char*[]){\"ctx\"}, GraphicsContext, &ctx)) return NONE_VAL();\n\tINIT_CHECK(MenuBar);\n\tmenu_bar_render(self->menuBar, ctx->ctx);\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuBar,mouse_event) {\n\tstruct _yutani_Window * window;\n\tKrkValue message;\n\n\tif (!krk_parseArgs(\n\t\t\".O!V!\", (const char*[]){\"window\", \"message\"},\n\t\tWindow, &window,\n\t\tMessage_WindowMouseEvent, &message\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(MenuBar);\n\treturn INTEGER_VAL(menu_bar_mouse_event(yctxInstance->yctx, window->window, self->menuBar,\n\t\tAS_Message_WindowMouseEvent(message),\n\t\tAS_Message_WindowMouseEvent(message)->new_x ,\n\t\tAS_Message_WindowMouseEvent(message)->new_y));\n}\n\nKRK_Method(MenuBar,insert) {\n\tKrkValue name;\n\tstruct _yutani_MenuList * menu;\n\n\tif (!krk_parseArgs(\n\t\t\".V!O!\", (const char*[]){\"name\",\"menu\"},\n\t\tKRK_BASE_CLASS(str), &name,\n\t\tMenuList, &menu\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(MenuBar);\n\tmenu_set_insert(self->menuBar->set, AS_CSTRING(name), menu->menuList);\n\n\tKrkValue dict = NONE_VAL();\n\tif (!krk_tableGet(&self->inst.fields, OBJECT_VAL(S(\"set\")), &dict) || !krk_isInstanceOf(dict,KRK_BASE_CLASS(dict))) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"corrupt MenuBar\");\n\t}\n\n\tkrk_tableSet(AS_DICT(dict), name, OBJECT_VAL(menu));\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuBar,height) {\n\treturn INTEGER_VAL(MENU_BAR_HEIGHT);\n}\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuList(o) (krk_isInstanceOf(o,MenuList))\n#define AS_MenuList(o) ((struct _yutani_MenuList*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuList*\n\nKRK_Method(MenuList,__init__) {\n\tif (!krk_parseArgs(\".:MenuList\",(const char*[]){}, NULL)) return NONE_VAL();\n\n\tNO_REINIT(MenuList);\n\n\tstruct MenuList * out = menu_create();\n\tself->menuList = out;\n\n\tKrkValue list = krk_list_of(0,NULL,0);\n\tkrk_attachNamedValue(&self->inst.fields, \"entries\", list);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuList,insert) {\n\tstruct _yutani_MenuEntry * entry;\n\n\tif (!krk_parseArgs(\".O!\",(const char*[]){\"entry\"}, MenuEntry, &entry)) return NONE_VAL();\n\n\tINIT_CHECK(MenuList);\n\n\tmenu_insert(self->menuList, entry->menuEntry);\n\n\tKrkValue list = NONE_VAL();\n\n\tif (!krk_tableGet(&self->inst.fields, OBJECT_VAL(S(\"entries\")), &list) || !IS_list(list)) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"corrupt MenuList\");\n\t}\n\n\tkrk_writeValueArray(AS_LIST(list), OBJECT_VAL(entry));\n\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuEntry(o) (krk_isInstanceOf(o,MenuEntry))\n#define AS_MenuEntry(o) ((struct _yutani_MenuEntry*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuEntry*\n\nstatic void _MenuEntry_callback_internal(struct MenuEntry * _self) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private;\n\n\tKrkValue callback = NONE_VAL();\n\tif (krk_tableGet(&self->inst.fields, OBJECT_VAL(S(\"callback\")), &callback)) {\n\t\tkrk_push(callback);\n\t\tkrk_push(OBJECT_VAL(self));\n\t\tkrk_callStack(1);\n\t}\n}\n\nKRK_Method(MenuEntry,__init__) {\n\tconst char * title;\n\tKrkValue callback;\n\tconst char * icon = NULL;\n\tconst char * action = NULL;\n\n\tif (!krk_parseArgs(\n\t\t\".sV|zz:MenuEntry\", (const char*[]){\"title\",\"callback\",\"icon\",\"action\"},\n\t\t&title, &callback,\n\t\t&icon, &action\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(MenuEntry);\n\n\tstruct MenuEntry * out = menu_create_normal(icon, action, title, _MenuEntry_callback_internal);\n\tself->menuEntry = out;\n\tout->_private = self;\n\n\tkrk_attachNamedValue(&self->inst.fields, \"callback\", callback);\n\n\treturn NONE_VAL();\n}\n\n#define MENU_ENTRY_INT_PROP(name) \\\n\tKRK_Method(MenuEntry,name) { \\\n\t\tint set = 0, to = 0; \\\n\t\tif (!krk_parseArgs(\".|i?\",(const char*[]){\"value\"},&set,&to)) return NONE_VAL(); \\\n\t\tif (set) self->menuEntry-> name = to; \\\n\t\treturn INTEGER_VAL(self->menuEntry-> name); \\\n\t}\n\nMENU_ENTRY_INT_PROP(height)\nMENU_ENTRY_INT_PROP(width)\nMENU_ENTRY_INT_PROP(rwidth)\nMENU_ENTRY_INT_PROP(hilight)\nMENU_ENTRY_INT_PROP(offset)\n\n\nKRK_Method(MenuEntry,update_icon) {\n\tchar * icon;\n\tif (!krk_parseArgs(\".z\", (const char*[]){\"icon\"}, &icon)) return NONE_VAL();\n\n\tINIT_CHECK(MenuEntry);\n\n\tmenu_update_icon(self->menuEntry, icon);\n\n\treturn NONE_VAL();\n}\n\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuEntrySubmenu(o) (krk_isInstanceOf(o,MenuEntrySubmenu))\n#define AS_MenuEntrySubmenu(o) ((struct _yutani_MenuEntrySubmenu*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuEntrySubmenu*\n\nKRK_Method(MenuEntrySubmenu,__init__) {\n\tconst char * title;\n\tconst char * action;\n\tconst char * icon = NULL;\n\n\tif (!krk_parseArgs(\n\t\t\".ss|z:MenuEntrySubmenu\", (const char*[]){\"title\",\"action\",\"icon\"},\n\t\t&title,\n\t\t&action,\n\t\t&icon\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(MenuEntrySubmenu);\n\n\tstruct MenuEntry * out = menu_create_submenu(icon, action, title);\n\tself->menuEntry = out;\n\tout->_private = self;\n\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuEntryToggle(o) (krk_isInstanceOf(o,MenuEntryToggle))\n#define AS_MenuEntryToggle(o) ((struct _yutani_MenuEntryToggle*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuEntryToggle*\n\nKRK_Method(MenuEntryToggle,__init__) {\n\tconst char * title;\n\tKrkValue callback;\n\tint state = 0;\n\tconst char * action = NULL;\n\n\tif (!krk_parseArgs(\n\t\t\".sV|ps:MenuEntryToggle\", (const char*[]){\"title\",\"callback\",\"state\",\"action\"},\n\t\t&title,\n\t\t&callback,\n\t\t&state,\n\t\t&action\n\t)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tNO_REINIT(MenuEntryToggle);\n\n\tstruct MenuEntry * out = menu_create_toggle(action, title, state, _MenuEntry_callback_internal);\n\tself->menuEntry = out;\n\tout->_private = self;\n\n\tkrk_attachNamedValue(&self->inst.fields, \"callback\", callback);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(MenuEntryToggle,state) {\n\tint had_state = 0;\n\tint state = 0;\n\tif (!krk_parseArgs(\".|p?\", (const char*[]){\"state\"}, &had_state, &state)) return NONE_VAL();\n\n\tINIT_CHECK(MenuEntryToggle);\n\n\tif (had_state) {\n\t\tmenu_update_toggle_state(self->menuEntry, state);\n\t}\n\n\treturn BOOLEAN_VAL(((struct MenuEntry_Toggle*)self)->set);\n}\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuEntrySeparator(o) (krk_isInstanceOf(o,MenuEntrySeparator))\n#define AS_MenuEntrySeparator(o) ((struct _yutani_MenuEntrySeparator*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuEntrySeparator*\n\nKRK_Method(MenuEntrySeparator,__init__) {\n\tif (!krk_parseArgs(\".:MenuEntrySeparator\", (const char*[]){}, NULL)) return NONE_VAL();\n\tNO_REINIT(MenuEntrySeparator);\n\tstruct MenuEntry * out = menu_create_separator();\n\tself->menuEntry = out;\n\tout->_private = self;\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\n#define IS_MenuEntryCustom(o) (krk_isInstanceOf(o,MenuEntryCustom))\n#define AS_MenuEntryCustom(o) ((struct _yutani_MenuEntryCustom*)AS_OBJECT(o))\n#define CURRENT_CTYPE struct _yutani_MenuEntryCustom*\n\nstatic void _custom_menu_render(gfx_context_t * ctx, struct MenuEntry  * _self, int offset) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private;\n\tKrkClass * myClass = self->inst._class;\n\tKrkValue method;\n\tif (!krk_tableGet_fast(&myClass->methods, S(\"render\"), &method)) return;\n\tkrk_push(method);\n\tkrk_push(OBJECT_VAL(self));\n\n\tstruct _yutani_GraphicsContext * gctx = (struct _yutani_GraphicsContext*)krk_newInstance(GraphicsContext);\n\tgctx->ctx = ctx;\n\tkrk_push(OBJECT_VAL(gctx));\n\tkrk_push(INTEGER_VAL(offset));\n\n\tkrk_callStack(3);\n}\n\nstatic void _custom_menu_focus_change(struct MenuEntry * _self, int focused) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private;\n\tKrkClass * myClass = self->inst._class;\n\tKrkValue method;\n\tif (!krk_tableGet_fast(&myClass->methods, S(\"focus_change\"), &method)) return;\n\tkrk_push(method);\n\tkrk_push(OBJECT_VAL(self));\n\tkrk_push(BOOLEAN_VAL(focused));\n\tkrk_callStack(2);\n}\n\nstatic void _custom_menu_activate(struct MenuEntry * _self, int focused) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private;\n\tKrkClass * myClass = self->inst._class;\n\tKrkValue method;\n\tif (!krk_tableGet_fast(&myClass->methods, S(\"activate\"), &method)) return;\n\tkrk_push(method);\n\tkrk_push(OBJECT_VAL(self));\n\tkrk_push(BOOLEAN_VAL(focused));\n\tkrk_callStack(2);\n}\n\nstatic int _custom_menu_mouse_event(struct MenuEntry * _self, struct yutani_msg_window_mouse_event * event) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private;\n\tKrkClass * myClass = self->inst._class;\n\tKrkValue method;\n\tif (!krk_tableGet_fast(&myClass->methods, S(\"mouse_event\"), &method)) return 0;\n\tkrk_push(method);\n\tkrk_push(OBJECT_VAL(self));\n\n\tsize_t size = sizeof(yutani_msg_t) + sizeof(struct yutani_msg_window_mouse_event);\n\tyutani_msg_t * tmp = malloc(size);\n\ttmp->type = YUTANI_MSG_WINDOW_MOUSE_EVENT;\n\ttmp->size = size;\n\tmemcpy(tmp->data, event, sizeof(struct yutani_msg_window_mouse_event));\n\n\tkrk_push(makeMessage(tmp));\n\n\tKrkValue result = krk_callStack(2);\n\tif (IS_INTEGER(result)) return AS_INTEGER(result);\n\treturn 0;\n}\n\nstatic struct MenuEntryVTable _custom_menu_vtable = {\n\t.methods = 4,\n\t.renderer = _custom_menu_render,\n\t.focus_change = _custom_menu_focus_change,\n\t.activate = _custom_menu_activate,\n\t.mouse_event = _custom_menu_mouse_event,\n};\n\nKRK_Method(MenuEntryCustom,__init__) {\n\tif (!krk_parseArgs(\".:MenuEntryCustom\", (const char*[]){}, NULL)) return NONE_VAL();\n\tNO_REINIT(MenuEntryCustom);\n\tstruct MenuEntry * out = menu_create_separator(); /* Steal some defaults */\n\tout->_type = -1; /* Special */\n\tout->vtable = &_custom_menu_vtable;\n\tself->menuEntry = out;\n\tout->_private = self;\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\n#define CURRENT_CTYPE struct _yutani_TTContour*\n\nvoid _TTContour_ongcsweep(KrkInstance * _self) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self;\n\tif (self->contour) {\n\t\tfree(self->contour);\n\t}\n\tself->contour = NULL;\n}\n\nKRK_Method(TTContour,__init__) {\n\tfloat x, y;\n\tif (!krk_parseArgs(\".ff:TTContour\", (const char*[]){\"x\",\"y\"}, &x, &y))\n\t\treturn NONE_VAL();\n\n\tNO_REINIT(TTContour);\n\tself->contour = tt_contour_start(x,y);\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(TTContour,line_to) {\n\tfloat x, y;\n\tif (!krk_parseArgs(\".ff\", (const char*[]){\"x\",\"y\"}, &x, &y))\n\t\treturn NONE_VAL();\n\tINIT_CHECK(TTContour);\n\tself->contour = tt_contour_line_to(self->contour, x, y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TTContour,move_to) {\n\tfloat x, y;\n\tif (!krk_parseArgs(\".ff\", (const char*[]){\"x\",\"y\"}, &x, &y))\n\t\treturn NONE_VAL();\n\tINIT_CHECK(TTContour);\n\tself->contour = tt_contour_move_to(self->contour, x, y);\n\treturn NONE_VAL();\n}\n\nKRK_Method(TTContour,finish) {\n\tINIT_CHECK(TTContour);\n\tstruct _yutani_TTShape * newShape = (struct _yutani_TTShape*)krk_newInstance(TTShape);\n\tnewShape->shape = tt_contour_finish(self->contour);\n\treturn OBJECT_VAL(newShape);\n}\n\nKRK_Method(TTContour,stroke) {\n\tfloat width;\n\tif (!krk_parseArgs(\".f\", (const char*[]){\"width\"}, &width)) return NONE_VAL();\n\tINIT_CHECK(TTContour);\n\tstruct _yutani_TTShape * newShape = (struct _yutani_TTShape*)krk_newInstance(TTShape);\n\tnewShape->shape = tt_contour_stroke_shape(self->contour, width);\n\treturn OBJECT_VAL(newShape);\n}\n\nKRK_Method(TTContour,stroke_path) {\n\tfloat width;\n\tif (!krk_parseArgs(\".f\", (const char*[]){\"width\"}, &width)) return NONE_VAL();\n\tINIT_CHECK(TTContour);\n\tstruct _yutani_TTContour * newContour = (struct _yutani_TTContour*)krk_newInstance(TTContour);\n\tnewContour->contour = tt_contour_stroke_contour(self->contour, width);\n\treturn OBJECT_VAL(newContour);\n}\n\nKRK_Method(TTContour,free) {\n\tINIT_CHECK(TTContour);\n\tfree(self->contour);\n\tself->contour = NULL;\n\treturn NONE_VAL();\n}\n\nKRK_Method(TTContour,transform) {\n\tstruct _yutani_TransformMatrix * matrix;\n\tif (!krk_parseArgs(\".O!\", (const char*[]){\"matrix\"},\n\t\tTransformMatrix, &matrix)) return NONE_VAL();\n\tINIT_CHECK(TTContour);\n\n\ttt_contour_transform(self->contour, matrix->matrix);\n\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n#define CURRENT_CTYPE struct _yutani_TTShape*\n\nvoid _TTShape_ongcsweep(KrkInstance * _self) {\n\tCURRENT_CTYPE self = (CURRENT_CTYPE)_self;\n\tif (self->shape) {\n\t\tfprintf(stderr, \"free shape\\n\");\n\t\tfree(self->shape);\n\t}\n\tself->shape = NULL;\n}\n\nKRK_Method(TTShape,__init__) {\n\treturn krk_runtimeError(vm.exceptions->typeError, \"Can not initialize empty shape; use TTContour.finish instead\");\n}\n\nKRK_Method(TTShape,paint) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tuint32_t color;\n\n\tif (!krk_parseArgs(\n\t\t\".O!I\", (const char*[]){\"ctx\",\"color\"},\n\t\tGraphicsContext, &ctx, &color)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(TTShape);\n\n\ttt_path_paint(ctx->ctx, self->shape, color);\n\n\treturn NONE_VAL();\n}\n\nextern void tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix);\nextern void tt_path_paint_sprite_options(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix, int, int);\nKRK_Method(TTShape,paint_sprite) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tstruct _yutani_Sprite * sprite;\n\tstruct _yutani_TransformMatrix * matrix;\n\n\tint filter = 0;\n\tint wrap = 0;\n\n\tif (!krk_parseArgs(\n\t\t\".O!O!O!|ii\", (const char*[]){\"ctx\",\"sprite\",\"matrix\",\"filter\",\"wrap\"},\n\t\tGraphicsContext, &ctx,\n\t\tSprite, &sprite,\n\t\tTransformMatrix, &matrix,\n\t\t&filter, &wrap)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tINIT_CHECK(TTShape);\n\tif (!sprite->sprite) return krk_runtimeError(vm.exceptions->valueError, \"sprite go brrr\");\n\n\tif (filter == 0 && wrap == 0) {\n\t\ttt_path_paint_sprite(ctx->ctx, self->shape, sprite->sprite, matrix->matrix);\n\t} else {\n\t\ttt_path_paint_sprite_options(ctx->ctx, self->shape, sprite->sprite, matrix->matrix, filter, wrap);\n\t}\n\n\treturn NONE_VAL();\n}\n\nKRK_Method(TTShape,free) {\n\tINIT_CHECK(TTShape);\n\tfree(self->shape);\n\tself->shape = NULL;\n\treturn NONE_VAL();\n}\n\n#undef CURRENT_CTYPE\n\nKRK_Function(decor_get_bounds) {\n\tstruct _yutani_Window * window = NULL;\n\tif (!krk_parseArgs(\"|O!\",(const char *[]){\"window\"}, Window, &window)) return NONE_VAL();\n\tif (window && !window->window) return krk_runtimeError(vm.exceptions->valueError, \"Window is closed\");\n\tstruct decor_bounds bounds;\n\tdecor_get_bounds(window ? window->window : NULL, &bounds);\n\n\tKrkValue result = krk_dict_of(0, NULL, 0);\n\tkrk_push(result);\n#define SET(val) krk_attachNamedValue(AS_DICT(result), #val, INTEGER_VAL(bounds. val));\n\n\tSET(top_height);\n\tSET(bottom_height);\n\tSET(left_width);\n\tSET(right_width);\n\tSET(width);\n\tSET(height);\n\n\treturn krk_pop();\n}\n\nKRK_Function(decor_render) {\n\tstruct _yutani_Window * window;\n\tconst char * title = NULL;\n\tif (!krk_parseArgs(\"O!|z\",(const char *[]){\"window\",\"title\"}, Window, &window, &title)) return NONE_VAL();\n\tif (!window->window) return krk_runtimeError(vm.exceptions->valueError, \"Window is closed\");\n\tif (!title) title = IS_NONE(window->title) ? \"\" : AS_CSTRING(window->title);\n\trender_decorations(window->window, window->ctx, (char*)title);\n\treturn NONE_VAL();\n}\n\nKRK_Function(decor_handle_event) {\n\tstruct _yutani_Message * message = NULL;\n\tif (!krk_parseArgs(\"O!\",(const char *[]){\"message\"}, Message, &message)) return NONE_VAL();\n\treturn INTEGER_VAL(decor_handle_event(yctxInstance->yctx, message->msg));\n}\n\nKRK_Function(decor_show_default_menu) {\n\tstruct _yutani_Window * window;\n\tint x, y;\n\tif (!krk_parseArgs(\"O!ii\",(const char *[]){\"window\",\"x\",\"y\"}, Window, &window, &x, &y)) return NONE_VAL();\n\tif (!window->window) return krk_runtimeError(vm.exceptions->valueError, \"Window is closed\");\n\tdecor_show_default_menu(window->window, x, y);\n\treturn NONE_VAL();\n}\n\nKRK_Function(rgb) {\n\tint r, g, b;\n\tKrkValue a = NONE_VAL();\n\tif (!krk_parseArgs(\"bbb|V\",(const char*[]){\"r\",\"g\",\"b\",\"a\"}, &r, &g, &b, &a)) return NONE_VAL();\n\tif (IS_NONE(a)) {\n\t\treturn INTEGER_VAL(rgb(r,g,b));\n\t} else {\n\t\tif (IS_FLOATING(a)) a = INTEGER_VAL(AS_FLOATING(a) * 255);\n\t\tif (!IS_INTEGER(a)) return TYPE_ERROR(int or float,a);\n\t\treturn INTEGER_VAL(rgba(r,g,b,AS_INTEGER(a)));\n\t}\n}\n\nKRK_Function(draw_button) {\n\tstruct _yutani_GraphicsContext * ctx;\n\tint x, y, width, height, hilight;\n\tconst char * title;\n\n\tif (!krk_parseArgs(\"O!iiIIsi\",\n\t\t(const char*[]){\"ctx\",\"x\",\"y\",\"width\",\"height\",\"title\",\"hilight\"},\n\t\tGraphicsContext, &ctx,\n\t\t&x, &y, &width, &height,\n\t\t&title, &hilight)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tstruct TTKButton button = {x,y,width,height,(char*)title,hilight};\n\tttk_button_draw(ctx->ctx, &button);\n\treturn NONE_VAL();\n}\n\nKRK_Function(fswait) {\n\tKrkTuple * fds;\n\tint timeout = -1;\n\n\tif (!krk_parseArgs(\"O!|i\",(const char*[]){\"fds\",\"timeout\"},\n\t\tKRK_BASE_CLASS(tuple), &fds,\n\t\t&timeout)) {\n\t\treturn NONE_VAL();\n\t}\n\n\tsize_t count = fds->values.count;\n\n\tif (!count) {\n\t\treturn krk_runtimeError(vm.exceptions->typeError, \"can not wait on nothing?\");\n\t}\n\n\t/* Spot check first */\n\tfor (size_t i = 0; i < count; ++i) {\n\t\tKrkValue val = fds->values.values[i];\n\t\tif (!IS_INTEGER(val)) return krk_runtimeError(vm.exceptions->typeError, \"fds must be tuple of int, not %T\", val);\n\t}\n\n\tint * _fds = malloc(sizeof(int) * count);\n\tint * _results = malloc(sizeof(int) * count);\n\tfor (size_t i = 0; i < count; ++i) {\n\t\tKrkValue val = fds->values.values[i];\n\t\t_fds[i] = AS_INTEGER(val);\n\t\t_results[i] = 0;\n\t}\n\n\terrno = 0;\n\tint status = fswait3(count, _fds, timeout, _results);\n\tfree(_fds);\n\n\tif (status < 0) {\n\t\tint _errno = errno;\n\t\tfree(_results);\n\t\t/* check if we were already raising a keyboard interrupt */\n\t\tif (krk_currentThread.flags & (KRK_THREAD_HAS_EXCEPTION | KRK_THREAD_SIGNALLED)) return NONE_VAL();\n\t\treturn krk_runtimeError(vm.exceptions->OSError, \"%s\", strerror(_errno));\n\t}\n\n\tKrkTuple * output = krk_newTuple(count);\n\tkrk_push(OBJECT_VAL(output));\n\tfor (size_t i = 0; i < count; ++i) {\n\t\toutput->values.values[output->values.count++] = INTEGER_VAL(_results[i]);\n\t}\n\n\tfree(_results);\n\n\treturn krk_pop();\n}\n\n#undef CURRENT_CTYPE\n\nKRK_Module(_yutani2) {\n\t_module = module;\n\t/**\n\t * Base message type\n\t */\n\tkrk_makeClass(module, &Message, \"Message\", KRK_BASE_CLASS(object));\n\tMessage->allocSize = sizeof(struct _yutani_Message);\n\tMessage->_ongcsweep = _Message_gcsweep;\n\tBIND_STATICMETHOD(Message,__new__);\n\tBIND_METHOD(Message,__repr__);\n\tBIND_PROP(Message,msg_magic);\n\tBIND_PROP(Message,msg_type);\n\tBIND_PROP(Message,msg_size);\n#define TYPE(type) krk_attachNamedValue(&Message->methods, \"MSG_\" #type, INTEGER_VAL(YUTANI_MSG_ ## type))\n\tTYPE(HELLO); TYPE(WINDOW_NEW); TYPE(FLIP); TYPE(KEY_EVENT); TYPE(MOUSE_EVENT);\n\tTYPE(WINDOW_MOVE); TYPE(WINDOW_CLOSE); TYPE(WINDOW_SHOW); TYPE(WINDOW_HIDE);\n\tTYPE(WINDOW_STACK); TYPE(WINDOW_FOCUS_CHANGE); TYPE(WINDOW_MOUSE_EVENT);\n\tTYPE(FLIP_REGION); TYPE(WINDOW_NEW_FLAGS); TYPE(RESIZE_REQUEST);\n\tTYPE(RESIZE_OFFER); TYPE(RESIZE_ACCEPT); TYPE(RESIZE_BUFID); TYPE(RESIZE_DONE);\n\tTYPE(WINDOW_ADVERTISE); TYPE(SUBSCRIBE); TYPE(UNSUBSCRIBE); TYPE(NOTIFY);\n\tTYPE(QUERY_WINDOWS); TYPE(WINDOW_FOCUS); TYPE(WINDOW_DRAG_START); TYPE(WINDOW_WARP_MOUSE);\n\tTYPE(WINDOW_SHOW_MOUSE); TYPE(WINDOW_RESIZE_START); TYPE(SESSION_END);\n\tTYPE(KEY_BIND); TYPE(WINDOW_UPDATE_SHAPE); TYPE(CLIPBOARD); TYPE(GOODBYE);\n\tTYPE(SPECIAL_REQUEST); TYPE(WELCOME); TYPE(WINDOW_INIT);\n#undef TYPE\n\tkrk_finalizeClass(Message);\n\n#define MAKE_MSG(type) \\\n\tkrk_makeClass(module, &Message_ ## type, \"Message_\" # type, Message)\n\n\tMAKE_MSG(Welcome);\n\tBIND_PROP(Message_Welcome,display_width);\n\tBIND_PROP(Message_Welcome,display_height);\n\tkrk_finalizeClass(Message_Welcome);\n\n\tMAKE_MSG(WindowMouseEvent);\n\tBIND_PROP(Message_WindowMouseEvent,wid);\n\tBIND_PROP(Message_WindowMouseEvent,new_x);\n\tBIND_PROP(Message_WindowMouseEvent,new_y);\n\tBIND_PROP(Message_WindowMouseEvent,old_x);\n\tBIND_PROP(Message_WindowMouseEvent,old_y);\n\tBIND_PROP(Message_WindowMouseEvent,buttons);\n\tBIND_PROP(Message_WindowMouseEvent,command);\n\tBIND_PROP(Message_WindowMouseEvent,modifiers);\n\tkrk_finalizeClass(Message_WindowMouseEvent);\n\n\tMAKE_MSG(WindowFocusChange);\n\tBIND_PROP(Message_WindowFocusChange,wid);\n\tBIND_PROP(Message_WindowFocusChange,focused);\n\tkrk_finalizeClass(Message_WindowFocusChange);\n\n\tMAKE_MSG(ResizeOffer);\n\tBIND_PROP(Message_ResizeOffer,wid);\n\tBIND_PROP(Message_ResizeOffer,width);\n\tBIND_PROP(Message_ResizeOffer,height);\n\tBIND_PROP(Message_ResizeOffer,bufid);\n\tkrk_finalizeClass(Message_ResizeOffer);\n\n\tMAKE_MSG(WindowAdvertise);\n\tBIND_PROP(Message_WindowAdvertise,wid);\n\tBIND_PROP(Message_WindowAdvertise,flags);\n\tBIND_PROP(Message_WindowAdvertise,size);\n\tBIND_PROP(Message_WindowAdvertise,width);\n\tBIND_PROP(Message_WindowAdvertise,height);\n\tBIND_PROP(Message_WindowAdvertise,bufid);\n\tBIND_PROP(Message_WindowAdvertise,name);\n\tBIND_PROP(Message_WindowAdvertise,icon);\n\tkrk_finalizeClass(Message_WindowAdvertise);\n\n\tMAKE_MSG(WindowMove);\n\tBIND_PROP(Message_WindowMove,wid);\n\tBIND_PROP(Message_WindowMove,x);\n\tBIND_PROP(Message_WindowMove,y);\n\tkrk_finalizeClass(Message_WindowMove);\n\n\tMAKE_MSG(KeyEvent);\n\tBIND_PROP(Message_KeyEvent,wid);\n\tBIND_PROP(Message_KeyEvent,keycode);\n\tBIND_PROP(Message_KeyEvent,modifiers);\n\tBIND_PROP(Message_KeyEvent,action);\n\tBIND_PROP(Message_KeyEvent,key);\n\tBIND_PROP(Message_KeyEvent,kbd_state);\n\tBIND_PROP(Message_KeyEvent,kbd_s_state);\n\tBIND_PROP(Message_KeyEvent,k_ctrl);\n\tBIND_PROP(Message_KeyEvent,k_shift);\n\tBIND_PROP(Message_KeyEvent,k_alt);\n\tBIND_PROP(Message_KeyEvent,k_super);\n\tBIND_PROP(Message_KeyEvent,kl_ctrl);\n\tBIND_PROP(Message_KeyEvent,kl_shift);\n\tBIND_PROP(Message_KeyEvent,kl_alt);\n\tBIND_PROP(Message_KeyEvent,kl_super);\n\tBIND_PROP(Message_KeyEvent,kr_ctrl);\n\tBIND_PROP(Message_KeyEvent,kr_shift);\n\tBIND_PROP(Message_KeyEvent,kr_alt);\n\tBIND_PROP(Message_KeyEvent,kr_super);\n\tBIND_PROP(Message_KeyEvent,kbd_esc_buf);\n\tkrk_finalizeClass(Message_KeyEvent);\n\n\tMAKE_MSG(WindowClose);\n\tBIND_PROP(Message_WindowClose,wid);\n\tkrk_finalizeClass(Message_WindowClose);\n\n\t/**\n\t * Core connection type; singleton\n\t *\n\t * class YutaniCtx:\n\t *     display_width: int\n\t *     display_height: int\n\t *     def __new__(cls)\n\t *     def poll(self, sync=True) -> Message\n\t *     def wait_for(self, msgtype: int) -> Message\n\t *     def subscribe(self)\n\t *     def unsubscribe(self)\n\t *     def query_windows(self)\n\t *     def fileno(self) -> int\n\t *     def query(self) -> int\n\t *     def menu_process_event(self, message: Message) -> int\n\t */\n\tkrk_makeClass(module, &YutaniCtx, \"YutaniCtx\", KRK_BASE_CLASS(object));\n\tYutaniCtx->allocSize = sizeof(struct _yutani_YutaniCtx);\n\tYutaniCtx->obj.flags |= KRK_OBJ_FLAGS_NO_INHERIT;\n\tBIND_STATICMETHOD(YutaniCtx,__new__);\n\tBIND_METHOD(YutaniCtx,poll);\n\tBIND_METHOD(YutaniCtx,wait_for);\n\tBIND_METHOD(YutaniCtx,subscribe);\n\tBIND_METHOD(YutaniCtx,unsubscribe);\n\tBIND_METHOD(YutaniCtx,query_windows);\n\tBIND_METHOD(YutaniCtx,fileno);\n\tBIND_METHOD(YutaniCtx,query);\n\tBIND_METHOD(YutaniCtx,menu_process_event);\n\tBIND_PROP(YutaniCtx,display_width);\n\tBIND_PROP(YutaniCtx,display_height);\n\tkrk_finalizeClass(YutaniCtx);\n\n\t/*\n\t * Generic graphics context.\n\t *    Subclassed by Window and Sprite.\n\t *\n\t * class GraphicsContext:\n\t *     width: int\n\t *     height: int\n\t *     isDoubleBuffered: bool\n\t *     def fill(self, color: int)\n\t *     def flip(self)\n\t *     def blur(self, radius: int = 2)\n\t *     def line(self, x0: int, x1: int, y0: int, y1: int, color: int, thickness=None)\n\t *     def rect(self, x: int, y: int, width: int, height: int, color: int, solid: bool = False, radius: int = 0)\n\t *     def draw_sprite(self, sprite: Sprite, x: int, y: int, alpha: float = 1.0, rotation: float = 0.0, scale: tuple[int,int] = None, color: int = 0)\n\t *\n\t * To allocate a new graphics context with a fresh backing store, use Sprite.\n\t */\n\tkrk_makeClass(module, &GraphicsContext, \"GraphicsContext\", KRK_BASE_CLASS(object));\n\tGraphicsContext->allocSize = sizeof(struct _yutani_GraphicsContext);\n\tGraphicsContext->obj.flags |= KRK_OBJ_FLAGS_NO_INHERIT;\n\tBIND_STATICMETHOD(GraphicsContext,__new__);\n\tBIND_PROP(GraphicsContext,width);\n\tBIND_PROP(GraphicsContext,height);\n\tBIND_PROP(GraphicsContext,isDoubleBuffered);\n\tBIND_METHOD(GraphicsContext,fill);\n\tBIND_METHOD(GraphicsContext,flip);\n\tBIND_METHOD(GraphicsContext,blur);\n\tBIND_METHOD(GraphicsContext,line);\n\tBIND_METHOD(GraphicsContext,rect);\n\tBIND_METHOD(GraphicsContext,draw_sprite);\n\tBIND_METHOD(GraphicsContext,__setitem__);\n\tBIND_METHOD(GraphicsContext,__getitem__);\n\tkrk_finalizeClass(GraphicsContext);\n\n\t/*\n\t * Graphics object with a bitmap backing store,\n\t * typically derived from an image file.\n\t *\n\t * class Sprite(GraphicsContext):\n\t *     def __init__(self, file=None, width=0, height=0)\n\t */\n\tkrk_makeClass(module, &Sprite, \"Sprite\", GraphicsContext);\n\tSprite->allocSize = sizeof(struct _yutani_Sprite);\n\tSprite->_ongcsweep = _yutani_Sprite_gcsweep;\n\tBIND_METHOD(Sprite,__init__);\n\tBIND_METHOD(Sprite,__repr__);\n\tBIND_METHOD(Sprite,free);\n\tkrk_finalizeClass(Sprite);\n\n\t/*\n\t * A window.\n\t *\n\t * class Window(GraphicsContext):\n\t *     title: str\n\t *     icon: str\n\t *     wid: int\n\t *     x: int\n\t *     y: int\n\t *     focused: bool\n\t *     closed: bool\n\t *     def __init__(self, width: int, height: int, flags: int = 0, title: str = None, icon: str = None, doublebuffer: bool = True)\n\t *     def flip(self)\n\t *     def move(self, x: int, y: int)\n\t *     def close(self)\n\t *     def set_stack(self, z: int)\n\t *     def special_request(self: request: int)\n\t *     def resize(self, width: int, height: int)\n\t *     def resize_start(self, direction: int)\n\t *     def resize_done(self)\n\t *     def resize_offer(self, width: int, height: int)\n\t *     def resize_accept(self, width: int, height: int)\n\t *     def update_shape(self, threshold: int)\n\t *     def show_mouse(self, mouse: int)\n\t *     def warp_mouse(self, x: int, y: int)\n\t *     def reinit(self)\n\t */\n\tkrk_makeClass(module, &Window, \"Window\", GraphicsContext);\n\tWindow->allocSize = sizeof(struct _yutani_Window);\n\tWindow->_ongcscan = _yutani_Window_gcscan;\n\tBIND_METHOD(Window,__init__);\n\tBIND_METHOD(Window,__repr__);\n\tBIND_METHOD(Window,flip);\n\tBIND_METHOD(Window,move);\n\tBIND_METHOD(Window,close);\n\tBIND_METHOD(Window,set_stack);\n\tBIND_METHOD(Window,special_request);\n\tBIND_METHOD(Window,resize);\n\tBIND_METHOD(Window,resize_start);\n\tBIND_METHOD(Window,resize_done);\n\tBIND_METHOD(Window,resize_offer);\n\tBIND_METHOD(Window,resize_accept);\n\tBIND_METHOD(Window,update_shape);\n\tBIND_METHOD(Window,show_mouse);\n\tBIND_METHOD(Window,warp_mouse);\n\tBIND_METHOD(Window,reinit);\n\n\tBIND_PROP(Window,title);\n\tBIND_PROP(Window,icon);\n\tBIND_PROP(Window,wid);\n\tBIND_PROP(Window,x);\n\tBIND_PROP(Window,y);\n\tBIND_PROP(Window,focused);\n\tBIND_PROP(Window,closed);\n\tkrk_finalizeClass(Window);\n\n\tkrk_makeClass(module, &Subregion, \"Subregion\", GraphicsContext);\n\tSubregion->allocSize = sizeof(struct _yutani_Subregion);\n\tSubregion->_ongcsweep = _yutani_Subregion_gcsweep;\n\tBIND_METHOD(Subregion,__init__);\n\tBIND_PROP(Subregion,offset_x);\n\tBIND_PROP(Subregion,offset_y);\n\tkrk_finalizeClass(Subregion);\n\n\t/*\n\t * Typeface using the 'text' library.\n\t *\n\t * class Font:\n\t *     size: int\n\t *     def __init__(self, font: str, size: int, color: int = rgb(0,0,0))\n\t *     def draw_string(self, ctx: GraphicsContext, s: str, x: int, y: int) -> int\n\t *     def draw_string_shadow(self, ctx: GraphicsContext, s: str, x: int, y: int, shadow: int, blur: int)\n\t *     def width(self, s: str) -> int\n\t */\n\tkrk_makeClass(module, &Font, \"Font\", KRK_BASE_CLASS(object));\n\tFont->allocSize = sizeof(struct _yutani_Font);\n\tFont->_ongcsweep = _yutani_Font_gcsweep;\n\tBIND_METHOD(Font,__init__);\n\tBIND_METHOD(Font,draw_string);\n\tBIND_METHOD(Font,draw_string_shadow);\n\tBIND_METHOD(Font,width);\n\tBIND_METHOD(Font,measure);\n\tBIND_METHOD(Font,draw_glyph_into);\n\tBIND_METHOD(Font,prepare_string);\n\tBIND_METHOD(Font,ellipsify);\n\tBIND_PROP(Font,size);\n\tkrk_finalizeClass(Font);\n\n\t/*\n\t * Menu bar widget.\n\t *\n\t * This should really be in a higher-level GUI toolkit, but for now we have what we have...\n\t *\n\t * class MenuBar:\n\t *     def __init__(self, entries: tuple[tuple[str,str]])\n\t *     def place(self, x: int, y: int, width: int, window: Window)\n\t *     def render(self, ctx: GraphicsContext)\n\t *     def mouse_event(self, window: Window, message: Message_WindowMouseEvent)\n\t *     def insert(self, name: str, menu: MenuList)\n\t */\n\tkrk_makeClass(module, &MenuBar, \"MenuBar\", KRK_BASE_CLASS(object));\n\tMenuBar->allocSize = sizeof(struct _yutani_MenuBar);\n\tMenuBar->_ongcsweep = _yutani_MenuBar_gcsweep;\n\tBIND_METHOD(MenuBar,__init__);\n\tBIND_METHOD(MenuBar,place);\n\tBIND_METHOD(MenuBar,render);\n\tBIND_METHOD(MenuBar,mouse_event);\n\tBIND_METHOD(MenuBar,insert);\n\tBIND_PROP(MenuBar,height);\n\n\tkrk_finalizeClass(MenuBar);\n\n\t/*\n\t * MenuList wrapper\n\t *\n\t * class MenuList:\n\t *     def __init__(self)\n\t *     def insert(self, entry: MenuEntry)\n\t */\n\tkrk_makeClass(module, &MenuList, \"MenuList\", KRK_BASE_CLASS(object));\n\tMenuList->allocSize = sizeof(struct _yutani_MenuList);\n\t/* XXX where is the cleanup function for this? */\n\tBIND_METHOD(MenuList,__init__);\n\tBIND_METHOD(MenuList,insert);\n\tkrk_finalizeClass(MenuList);\n\n\n\t/*\n\t * Menu entry wrapper.\n\t *\n\t * class MenuEntry:\n\t *     def __init__(self, title: str, callback: function, icon: str = None, action: str = None)\n\t */\n\tkrk_makeClass(module, &MenuEntry, \"MenuEntry\", KRK_BASE_CLASS(object));\n\tMenuEntry->allocSize = sizeof(struct _yutani_MenuEntry);\n\tBIND_METHOD(MenuEntry,__init__);\n\tBIND_PROP(MenuEntry,height);\n\tBIND_PROP(MenuEntry,width);\n\tBIND_PROP(MenuEntry,rwidth);\n\tBIND_PROP(MenuEntry,hilight);\n\tBIND_PROP(MenuEntry,offset);\n\tBIND_METHOD(MenuEntry,update_icon);\n\tkrk_finalizeClass(MenuEntry);\n\n\t/*\n\t * Toggle subtype\n\t *\n\t * class MenuEntryToggle(MenuEntry):\n\t *     def __init__(self, title: str, callback: function, state: bool = False, action: str = None)\n\t */\n\tkrk_makeClass(module, &MenuEntryToggle, \"MenuEntryToggle\", MenuEntry);\n\tMenuEntryToggle->allocSize = sizeof(struct _yutani_MenuEntryToggle);\n\tBIND_METHOD(MenuEntryToggle,__init__);\n\tBIND_PROP(MenuEntryToggle,state);\n\tkrk_finalizeClass(MenuEntryToggle);\n\n\t/*\n\t * Submenu subtype\n\t *\n\t * class MenuEntrySubmenu(MenuEntry):\n\t *     def __init__(self, title: str, action: str, icon: str = None)\n\t */\n\tkrk_makeClass(module, &MenuEntrySubmenu, \"MenuEntrySubmenu\", MenuEntry);\n\tMenuEntrySubmenu->allocSize = sizeof(struct _yutani_MenuEntrySubmenu);\n\tBIND_METHOD(MenuEntrySubmenu,__init__);\n\tkrk_finalizeClass(MenuEntrySubmenu);\n\n\t/**\n\t * Separator subtype\n\t *\n\t * class MenuEntrySeparator(MenuEntry):\n\t *     def __init__(self)\n\t */\n\tkrk_makeClass(module, &MenuEntrySeparator, \"MenuEntrySeparator\", MenuEntry);\n\tMenuEntrySeparator->allocSize = sizeof(struct _yutani_MenuEntrySeparator);\n\tBIND_METHOD(MenuEntrySeparator,__init__);\n\tkrk_finalizeClass(MenuEntrySeparator);\n\n\tkrk_makeClass(module, &MenuEntryCustom, \"MenuEntryCustom\", MenuEntry);\n\tMenuEntryCustom->allocSize = sizeof(struct _yutani_MenuEntryCustom);\n\tBIND_METHOD(MenuEntryCustom,__init__);\n\tkrk_finalizeClass(MenuEntryCustom);\n\n\n\tkrk_makeClass(module, &TTContour, \"TTContour\", KRK_BASE_CLASS(object));\n\tTTContour->allocSize = sizeof(struct _yutani_TTContour);\n\tTTContour->_ongcsweep = _TTContour_ongcsweep;\n\tBIND_METHOD(TTContour,__init__);\n\tBIND_METHOD(TTContour,line_to);\n\tBIND_METHOD(TTContour,move_to);\n\tBIND_METHOD(TTContour,finish);\n\tBIND_METHOD(TTContour,free);\n\tBIND_METHOD(TTContour,stroke);\n\tBIND_METHOD(TTContour,stroke_path);\n\tBIND_METHOD(TTContour,transform);\n\tkrk_finalizeClass(TTContour);\n\n\tkrk_makeClass(module, &TTShape, \"TTShape\", KRK_BASE_CLASS(object));\n\tTTShape->allocSize = sizeof(struct _yutani_TTShape);\n\tTTShape->_ongcsweep = _TTShape_ongcsweep;\n\tBIND_METHOD(TTShape,__init__);\n\tBIND_METHOD(TTShape,paint);\n\tBIND_METHOD(TTShape,paint_sprite);\n\tBIND_METHOD(TTShape,free);\n#define CONST(n) krk_attachNamedValue(&TTShape->methods, #n, INTEGER_VAL(n))\n\tCONST(TT_PATH_FILTER_BILINEAR);\n\tCONST(TT_PATH_FILTER_NEAREST);\n\tCONST(TT_PATH_WRAP_REPEAT);\n\tCONST(TT_PATH_WRAP_NONE);\n\tCONST(TT_PATH_WRAP_PAD);\n#undef CONST\n\tkrk_finalizeClass(TTShape);\n\n\tkrk_makeClass(module, &TransformMatrix, \"TransformMatrix\", KRK_BASE_CLASS(object));\n\tTransformMatrix->allocSize = sizeof(struct _yutani_TransformMatrix);\n\tBIND_METHOD(TransformMatrix,__init__);\n\tBIND_METHOD(TransformMatrix,__repr__);\n\tBIND_METHOD(TransformMatrix,scale);\n\tBIND_METHOD(TransformMatrix,translate);\n\tBIND_METHOD(TransformMatrix,rotate);\n\tBIND_METHOD(TransformMatrix,shear);\n\tBIND_METHOD(TransformMatrix,apply);\n\tBIND_PROP(TransformMatrix,a);\n\tBIND_PROP(TransformMatrix,b);\n\tBIND_PROP(TransformMatrix,tx);\n\tBIND_PROP(TransformMatrix,c);\n\tBIND_PROP(TransformMatrix,d);\n\tBIND_PROP(TransformMatrix,ty);\n\tkrk_finalizeClass(TransformMatrix);\n\n\tBIND_FUNC(module,decor_get_bounds);\n\tBIND_FUNC(module,decor_render);\n\tBIND_FUNC(module,decor_handle_event);\n\tBIND_FUNC(module,decor_show_default_menu);\n\n\tBIND_FUNC(module,rgb);\n\n\tBIND_FUNC(module,draw_button);\n\tBIND_FUNC(module,fswait);\n}\n"
  },
  {
    "path": "lib/kuroko/yutani_mainloop.krk",
    "content": "'''\nAsynchronous event loop for Yutani applications.\n'''\nimport time\nimport _yutani2\n\nlet _windows = {}\nlet yctx = _yutani2.YutaniCtx()\n\nclass Window(_yutani2.Window):\n    '''\n    Base class for eventloop-managed windows, providing stub implementations\n    of the core callback functions.\n    '''\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args,**kwargs)\n        _windows[self.wid] = self\n\n    def close(self):\n        if self.wid in _windows:\n            del _windows[self.wid]\n        super().close()\n\n    def draw(self):\n        pass\n\n    def keyboard_event(self, msg):\n        pass\n\n    def focus_changed(self, msg):\n        self.focused = msg.focused\n        self.draw()\n\n    def finish_resize(self, msg):\n        self.resize_accept(msg.width, msg.height)\n        self.reinit()\n        self.draw()\n        self.resize_done()\n        self.flip()\n\n    def window_moved(self, msg):\n        pass\n\n    def mouse_event(self, msg):\n        pass\n\nlet current_loop\n\nclass Future():\n    def __init__(self):\n        self.result = None\n        self.loop = current_loop\n        self.done = False\n        self.callbacks = []\n\n    def add_callback(self, func):\n        self.callbacks.append(func)\n\n    def schedule_callbacks(self):\n        let mycallbacks = self.callbacks[:]\n        self.callbacks = []\n        for callback in mycallbacks:\n            self.loop.call_soon(callback, self)\n\n    def set_result(self, result):\n        self.result = result\n        self.done = True\n        self.schedule_callbacks()\n\n    def __await__(self):\n        if not self.done:\n            yield self\n        return self.result\n\nclass Task():\n    def __init__(self, coro):\n        self.coro = coro\n        current_loop.call_soon(self.step)\n\n    def step(self):\n        let result = self.coro.send(None)\n        if isinstance(result,Future):\n            result.add_callback(self.wakeup)\n        else if result == self.coro:\n            return # This task is done\n        else if result is None:\n            current_loop.call_soon(self.step)\n        else:\n            print(\"Don't know what to do with\",result)\n\n    def wakeup(self, future):\n        self.step()\n\nasync def sleep(delay, result=None):\n    let future = Future()\n    current_loop.call_later(delay, future.set_result, result)\n    return await future\n\nclass Timer():\n    def __init__(self, time, func, args):\n        self.time = time\n        self.func = func\n        self.args = args\n\n    def __lt__(self, other):\n        return self.time < other.time\n    def __gt__(self, other):\n        return self.time > other.time\n    def __le__(self, other):\n        return self.time <= other.time\n    def __ge__(self, other):\n        return self.time >= other.time\n    def __eq__(self, other):\n        return self.time == other.time\n\nclass AsyncMainloop():\n    def __init__(self):\n        self.should_exit = 0\n        self.status_code = 0\n        self.ready = []\n        self.schedule = []\n        self.fileno = yctx.fileno()\n        self.menu_closed_callback = None\n\n    def exit(self, arg=0):\n        self.status_code = arg\n        self.should_exit = 1\n\n    def call_soon(self, func, *args):\n        self.ready.append((func,args))\n\n    def call_later(self, delay, func, *args):\n        self.call_at(time.time() + delay, func, *args)\n\n    def call_at(self, time, func, *args):\n        self.schedule.append(Timer(time,func,args))\n        self.schedule.sort()\n\n    def maybe_coro(self, result):\n        if isinstance(result, generator):\n            Task(result)\n\n    def handle_message(self):\n        let msg = yctx.poll()\n        if yctx.menu_process_event(msg):\n            if self.menu_closed_callback:\n                self.maybe_coro(self.menu_closed_callback())\n        if msg.msg_type == _yutani2.Message.MSG_SESSION_END:\n            self.exit()\n            return False\n        else if msg.msg_type == _yutani2.Message.MSG_KEY_EVENT:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].keyboard_event(msg))\n        else if msg.msg_type == _yutani2.Message.MSG_WINDOW_FOCUS_CHANGE:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].focus_changed(msg))\n        else if msg.msg_type == _yutani2.Message.MSG_RESIZE_OFFER:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].finish_resize(msg))\n        else if msg.msg_type == _yutani2.Message.MSG_WINDOW_MOVE:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].window_moved(msg))\n        else if msg.msg_type == _yutani2.Message.MSG_WINDOW_MOUSE_EVENT:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].mouse_event(msg))\n        else if msg.msg_type == _yutani2.Message.MSG_WINDOW_CLOSE:\n            if msg.wid in _windows:\n                self.maybe_coro(_windows[msg.wid].close())\n        return True\n\n    def run_once(self):\n        # Determine if anything in the schedule list can be run\n        let timeout = -1\n        let now = time.time()\n\n        if self.ready:\n            timeout = 0\n        else if self.schedule:\n            timeout = max(0,self.schedule[0].time - now)\n\n        # Poll\n        let res = _yutani2.fswait((self.fileno,), int(timeout * 1000))\n\n        # TODO probably hooks if these have callbacks\n        if res[0]:\n            self.handle_message()\n\n        # Schedule future stuff\n        while self.schedule and self.schedule[0].time <= now:\n            self.ready.append((self.schedule[0].func,self.schedule[0].args))\n            self.schedule.pop(0)\n\n        let count = len(self.ready)\n\n        for i in range(count):\n            let func, args = self.ready.pop(0)\n            func(*args)\n\n    def run(self):\n        current_loop = self\n        while not self.should_exit:\n            self.run_once()\n\n    def activate(self):\n        current_loop = self\n"
  },
  {
    "path": "lib/list.c",
    "content": "/**\n * @brief General-purpose list implementations.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2018 K. Lange\n */\n\n#ifdef _KERNEL_\n#\tinclude <kernel/system.h>\n#else\n#\tinclude <stddef.h>\n#\tinclude <stdlib.h>\n#endif\n\n#include <toaru/list.h>\n\nvoid list_destroy(list_t * list) {\n\t/* Free all of the contents of a list */\n\tnode_t * n = list->head;\n\twhile (n) {\n\t\tfree(n->value);\n\t\tn = n->next;\n\t}\n}\n\nvoid list_free(list_t * list) {\n\t/* Free the actual structure of a list */\n\tnode_t * n = list->head;\n\twhile (n) {\n\t\tnode_t * s = n->next;\n\t\tfree(n);\n\t\tn = s;\n\t}\n}\n\nvoid list_append(list_t * list, node_t * node) {\n\tassert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->next = NULL;\n\t/* Insert a node onto the end of a list */\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist->head = node;\n\t\tlist->tail = node;\n\t\tnode->prev = NULL;\n\t\tnode->next = NULL;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tlist->tail->next = node;\n\tnode->prev = list->tail;\n\tlist->tail = node;\n\tlist->length++;\n}\n\nnode_t * list_insert(list_t * list, void * item) {\n\t/* Insert an item into a list */\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append(list, node);\n\n\treturn node;\n}\n\nvoid list_append_after(list_t * list, node_t * before, node_t * node) {\n\tassert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist_append(list, node);\n\t\treturn;\n\t}\n\tif (before == NULL) {\n\t\tnode->next = list->head;\n\t\tnode->prev = NULL;\n\t\tlist->head->prev = node;\n\t\tlist->head = node;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tif (before == list->tail) {\n\t\tlist->tail = node;\n\t} else {\n\t\tbefore->next->prev = node;\n\t\tnode->next = before->next;\n\t}\n\tnode->prev = before;\n\tbefore->next = node;\n\tlist->length++;\n}\n\nnode_t * list_insert_after(list_t * list, node_t * before, void * item) {\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append_after(list, before, node);\n\treturn node;\n}\n\nvoid list_append_before(list_t * list, node_t * after, node_t * node) {\n\tassert(!(node->next || node->prev) && \"Node is already in a list.\");\n\tnode->owner = list;\n\tif (!list->length) {\n\t\tlist_append(list, node);\n\t\treturn;\n\t}\n\tif (after == NULL) {\n\t\tnode->next = NULL;\n\t\tnode->prev = list->tail;\n\t\tlist->tail->next = node;\n\t\tlist->tail = node;\n\t\tlist->length++;\n\t\treturn;\n\t}\n\tif (after == list->head) {\n\t\tlist->head = node;\n\t} else {\n\t\tafter->prev->next = node;\n\t\tnode->prev = after->prev;\n\t}\n\tnode->next = after;\n\tafter->prev = node;\n\tlist->length++;\n}\n\nnode_t * list_insert_before(list_t * list, node_t * after, void * item) {\n\tnode_t * node = malloc(sizeof(node_t));\n\tnode->value = item;\n\tnode->next  = NULL;\n\tnode->prev  = NULL;\n\tnode->owner = NULL;\n\tlist_append_before(list, after, node);\n\treturn node;\n}\n\nlist_t * list_create(void) {\n\t/* Create a fresh list */\n\tlist_t * out = malloc(sizeof(list_t));\n\tout->head = NULL;\n\tout->tail = NULL;\n\tout->length = 0;\n\treturn out;\n}\n\nnode_t * list_find(list_t * list, void * value) {\n\tforeach(item, list) {\n\t\tif (item->value == value) {\n\t\t\treturn item;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nint list_index_of(list_t * list, void * value) {\n\tint i = 0;\n\tforeach(item, list) {\n\t\tif (item->value == value) {\n\t\t\treturn i;\n\t\t}\n\t\ti++;\n\t}\n\treturn -1; /* not find */\n}\n\nvoid * list_index(list_t * list, int index) {\n\tint i = 0;\n\tforeach(item, list) {\n\t\tif (i == index) return item->value;\n\t\ti++;\n\t}\n\treturn NULL;\n}\n\nvoid list_remove(list_t * list, size_t index) {\n\t/* remove index from the list */\n\tif (index > list->length) return;\n\tsize_t i = 0;\n\tnode_t * n = list->head;\n\twhile (i < index) {\n\t\tn = n->next;\n\t\ti++;\n\t}\n\tlist_delete(list, n);\n}\n\nvoid list_delete(list_t * list, node_t * node) {\n\t/* remove node from the list */\n\tassert(node->owner == list && \"Tried to remove a list node from a list it does not belong to.\");\n\tif (node == list->head) {\n\t\tlist->head = node->next;\n\t}\n\tif (node == list->tail) {\n\t\tlist->tail = node->prev;\n\t}\n\tif (node->prev) {\n\t\tnode->prev->next = node->next;\n\t}\n\tif (node->next) {\n\t\tnode->next->prev = node->prev;\n\t}\n\tnode->prev = NULL;\n\tnode->next = NULL;\n\tnode->owner = NULL;\n\tlist->length--;\n}\n\nnode_t * list_pop(list_t * list) {\n\t/* Remove and return the last value in the list\n\t * If you don't need it, you still probably want to free it!\n\t * Try free(list_pop(list)); !\n\t * */\n\tif (!list->tail) return NULL;\n\tnode_t * out = list->tail;\n\tlist_delete(list, out);\n\treturn out;\n}\n\nnode_t * list_dequeue(list_t * list) {\n\tif (!list->head) return NULL;\n\tnode_t * out = list->head;\n\tlist_delete(list, out);\n\treturn out;\n}\n\nlist_t * list_copy(list_t * original) {\n\t/* Create a new copy of original */\n\tlist_t * out = list_create();\n\tnode_t * node = original->head;\n\twhile (node) {\n\t\tlist_insert(out, node->value);\n\t}\n\treturn out;\n}\n\nvoid list_merge(list_t * target, list_t * source) {\n\t/* Destructively merges source into target */\n\tforeach(node, source) {\n\t\tnode->owner = target;\n\t}\n\tif (source->head) {\n\t\tsource->head->prev = target->tail;\n\t}\n\tif (target->tail) {\n\t\ttarget->tail->next = source->head;\n\t} else {\n\t\ttarget->head = source->head;\n\t}\n\tif (source->tail) {\n\t\ttarget->tail = source->tail;\n\t}\n\ttarget->length += source->length;\n\tfree(source);\n}\n"
  },
  {
    "path": "lib/markup.c",
    "content": "/**\n * @brief HTML-ish markup parser.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018 K. Lange\n */\n#include <stdio.h>\n#include <toaru/markup.h>\n\nstruct markup_state {\n\tint state;\n\tvoid * user;\n\tmarkup_callback_tag_open  callback_tag_open;\n\tmarkup_callback_tag_close callback_tag_close;\n\tmarkup_callback_data  callback_data;\n\n\t/* Private stuff */\n\tstruct markup_tag tag;\n\tsize_t len;\n\tchar data[64];\n\tchar * attr;\n};\n\nstruct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data) {\n\tstruct markup_state * out = malloc(sizeof(struct markup_state));\n\n\tout->state = 0;\n\tout->user = user;\n\tout->len = 0;\n\n\tout->callback_tag_open  = open;\n\tout->callback_tag_close = close;\n\tout->callback_data  = data;\n\n\treturn out;\n}\n\nstatic void _dump_buffer(struct markup_state * state) {\n\tif (state->len) {\n\t\tstate->data[state->len] = '\\0';\n\t\tstate->callback_data(state, state->user, state->data);\n\t\tstate->data[0] = '\\0';\n\t\tstate->len = 0;\n\t}\n}\n\nstatic void _finish_name(struct markup_state * state) {\n\tstate->data[state->len] = '\\0';\n\tstate->tag.name = strdup(state->data);\n\tstate->tag.options = hashmap_create(5);\n\tstate->data[0] = '\\0';\n\tstate->len = 0;\n\tstate->state = 2;\n}\n\nstatic void _finish_close(struct markup_state * state) {\n\tstate->data[state->len] = '\\0';\n\tstate->callback_tag_close(state, state->user, state->data);\n\tstate->data[0] = '\\0';\n\tstate->len = 0;\n\tstate->state = 0;\n}\n\nstatic void _finish_tag(struct markup_state * state) {\n\tstate->callback_tag_open(state, state->user, &state->tag);\n\tstate->state = 0;\n}\n\nstatic void _finish_bare_attr(struct markup_state * state) {\n\tstate->data[state->len] = '\\0';\n\thashmap_set(state->tag.options, state->data, strdup(state->data));\n\tstate->data[0] = '\\0';\n\tstate->len = 0;\n}\n\nstatic void _finish_attr(struct markup_state * state) {\n\tstate->data[state->len] = '\\0';\n\tstate->attr = strdup(state->data);\n\tstate->data[0] = '\\0';\n\tstate->len = 0;\n\tstate->state = 4;\n}\n\nstatic void _finish_attr_value(struct markup_state * state) {\n\tstate->data[state->len] = '\\0';\n\thashmap_set(state->tag.options, state->attr, strdup(state->data));\n\tfree(state->attr);\n\tstate->data[0] = '\\0';\n\tstate->len = 0;\n\tstate->state = 2;\n}\n\nint markup_free_tag(struct markup_tag * tag) {\n\tfree(tag->name);\n\tlist_t * keys = hashmap_keys(tag->options);\n\tif (keys->length) {\n\t\tforeach(node, keys) {\n\t\t\tfree(hashmap_get(tag->options, node->value));\n\t\t}\n\t}\n\tlist_free(keys);\n\tfree(keys);\n\thashmap_free(tag->options);\n\tfree(tag->options);\n\treturn 0;\n}\n\nint markup_parse(struct markup_state * state, char c) {\n\tswitch (state->state) {\n\t\tcase 0: /* STATE_NORMAL */\n\t\t\tif (state->len == 63) {\n\t\t\t\t_dump_buffer(state);\n\t\t\t}\n\t\t\tswitch (c) {\n\t\t\t\tcase '<':\n\t\t\t\t\t_dump_buffer(state);\n\t\t\t\t\tstate->state = 1;\n\t\t\t\t\treturn 0;\n\t\t\t\tdefault:\n\t\t\t\t\tstate->data[state->len] = c;\n\t\t\t\t\tstate->len++;\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 1: /* STATE_TAG_OPEN */\n\t\t\tswitch (c) {\n\t\t\t\tcase '/':\n\t\t\t\t\tif (state->len) {\n\t\t\t\t\t\tfprintf(stderr, \"syntax error\\n\");\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t\tstate->state = 3; /* STATE_TAG_CLOSE */\n\t\t\t\t\treturn 0;\n\t\t\t\tcase '>':\n\t\t\t\t\t_finish_name(state);\n\t\t\t\t\t_finish_tag(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tcase ' ':\n\t\t\t\t\t_finish_name(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tdefault:\n\t\t\t\t\tstate->data[state->len] = c;\n\t\t\t\t\tstate->len++;\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 2: /* STATE_TAG_ATTRIB */\n\t\t\tswitch (c) {\n\t\t\t\tcase ' ': /* attribute has no value, end it and append it with = self */\n\t\t\t\t\t_finish_bare_attr(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tcase '>':\n\t\t\t\t\t_finish_bare_attr(state);\n\t\t\t\t\t_finish_tag(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tcase '=': /* attribute has a value, go to next mode */\n\t\t\t\t\t_finish_attr(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tdefault:\n\t\t\t\t\tstate->data[state->len] = c;\n\t\t\t\t\tstate->len++;\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t\treturn 0;\n\t\tcase 3: /* STATE_TAG_CLOSE */\n\t\t\tswitch (c) {\n\t\t\t\tcase '>':\n\t\t\t\t\t_finish_close(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tdefault:\n\t\t\t\t\tstate->data[state->len] = c;\n\t\t\t\t\tstate->len++;\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 4: /* STATE_ATTR_VALUE */\n\t\t\tswitch (c) {\n\t\t\t\tcase ' ':\n\t\t\t\t\t_finish_attr_value(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tcase '>':\n\t\t\t\t\t_finish_attr_value(state);\n\t\t\t\t\t_finish_tag(state);\n\t\t\t\t\treturn 0;\n\t\t\t\tdefault:\n\t\t\t\t\tstate->data[state->len] = c;\n\t\t\t\t\tstate->len++;\n\t\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfprintf(stderr, \"parser in unknown state\\n\");\n\t\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nint markup_finish(struct markup_state * state) {\n\tif (state->state != 0) {\n\t\tfprintf(stderr, \"unexpected end of data\\n\");\n\t\treturn 1;\n\t} else {\n\t\t_dump_buffer(state);\n\t\tfree(state);\n\t\treturn 0;\n\t}\n}\n\n"
  },
  {
    "path": "lib/markup_text.c",
    "content": "/**\n * @brief Marked up text label renderer.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <math.h>\n#include <toaru/markup.h>\n#include <toaru/list.h>\n#include <toaru/graphics.h>\n#include <toaru/text.h>\n#include <toaru/decodeutf8.h>\n#include \"toaru/markup_text.h\"\n\nstatic struct TT_Font * dejaVuSans = NULL;\nstatic struct TT_Font * dejaVuSans_Bold = NULL;\nstatic struct TT_Font * dejaVuSans_Oblique = NULL;\nstatic struct TT_Font * dejaVuSans_BoldOblique = NULL;\nstatic struct TT_Font * dejaVuSansMono = NULL;\nstatic struct TT_Font * dejaVuSansMono_Bold = NULL;\nstatic struct TT_Font * dejaVuSansMono_Oblique = NULL;\nstatic struct TT_Font * dejaVuSansMono_BoldOblique = NULL;\n\nstruct MarkupState {\n\tstruct markup_state * parser;\n\tlist_t * state;\n\tint current_state;\n\tint cursor_x;\n\tint cursor_y;\n\tint initial_left;\n\tuint32_t color;\n\tgfx_context_t * ctx;\n\tint max_cursor_x;\n\tlist_t * colors;\n\tint sizes[3];\n\tint dryrun;\n};\n\nstatic void push_state(struct MarkupState * state, int val) {\n\tlist_insert(state->state, (void*)(uintptr_t)state->current_state);\n\tstate->current_state |= val;\n}\n\nstatic void pop_state(struct MarkupState * state) {\n\tnode_t * nstate = list_pop(state->state);\n\tstate->current_state = (int)(uintptr_t)nstate->value;\n\tfree(nstate);\n}\n\nstatic uint32_t parseColor(const char * c) {\n\tif (*c != '#' || strlen(c) != 7) return rgba(0,0,0,255);\n\n\tchar r[3] = {c[1],c[2],'\\0'};\n\tchar g[3] = {c[3],c[4],'\\0'};\n\tchar b[3] = {c[5],c[6],'\\0'};\n\n\treturn rgba(strtoul(r,NULL,16),strtoul(g,NULL,16),strtoul(b,NULL,16),255);\n}\n\nstatic int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) {\n\tstruct MarkupState * state = (struct MarkupState*)user;\n\tif (!strcmp(tag->name, \"b\")) {\n\t\tpush_state(state, MARKUP_TEXT_STATE_BOLD);\n\t} else if (!strcmp(tag->name, \"i\")) {\n\t\tpush_state(state, MARKUP_TEXT_STATE_OBLIQUE);\n\t} else if (!strcmp(tag->name, \"h1\")) {\n\t\tpush_state(state, MARKUP_TEXT_STATE_HEADING);\n\t} else if (!strcmp(tag->name, \"small\")) {\n\t\tpush_state(state, MARKUP_TEXT_STATE_SMALL);\n\t} else if (!strcmp(tag->name, \"mono\")) {\n\t\tpush_state(state, MARKUP_TEXT_STATE_MONO);\n\t} else if (!strcmp(tag->name, \"br\")) {\n\t\tstate->cursor_x = state->initial_left;\n\t\tstate->cursor_y += 20; /* state->line_height? */\n\t} else if (!strcmp(tag->name, \"color\")) {\n\t\t/* get options */\n\t\tlist_t * args = hashmap_keys(tag->options);\n\t\tif (args->length == 1) {\n\t\t\tlist_insert(state->colors, (void*)(uintptr_t)state->color);\n\t\t\tstate->color = parseColor((char*)args->head->value);\n\t\t}\n\t\tfree(args);\n\t}\n\tmarkup_free_tag(tag);\n\treturn 0;\n}\n\nstatic int parser_close(struct markup_state * self, void * user, char * tag_name) {\n\tstruct MarkupState * state = (struct MarkupState*)user;\n\tif (!strcmp(tag_name, \"b\")) {\n\t\tpop_state(state);\n\t} else if (!strcmp(tag_name, \"i\")) {\n\t\tpop_state(state);\n\t} else if (!strcmp(tag_name, \"h1\")) {\n\t\tpop_state(state);\n\t} else if (!strcmp(tag_name, \"small\")) {\n\t\tpop_state(state);\n\t} else if (!strcmp(tag_name, \"mono\")) {\n\t\tpop_state(state);\n\t} else if (!strcmp(tag_name, \"color\")) {\n\t\tnode_t * ncolor = list_pop(state->colors);\n\t\tstate->color = (uint32_t)(uintptr_t)ncolor->value;\n\t\tfree(ncolor);\n\t}\n\treturn 0;\n}\n\nstatic struct TT_Font * fontForState(struct MarkupState * state) {\n\tint bold = !!(state->current_state & MARKUP_TEXT_STATE_BOLD);\n\tint obli = !!(state->current_state & MARKUP_TEXT_STATE_OBLIQUE);\n\tint mono = !!(state->current_state & MARKUP_TEXT_STATE_MONO);\n\tif (mono) {\n\t\tif (bold && obli) return dejaVuSansMono_BoldOblique;\n\t\tif (bold) return dejaVuSansMono_Bold;\n\t\tif (obli) return dejaVuSansMono_Oblique;\n\t\treturn dejaVuSansMono;\n\t} else {\n\t\tif (bold && obli) return dejaVuSans_BoldOblique;\n\t\tif (bold) return dejaVuSans_Bold;\n\t\tif (obli) return dejaVuSans_Oblique;\n\t\treturn dejaVuSans;\n\t}\n}\n\nstatic int sizeForState(struct MarkupState * state) {\n\tif (state->current_state & MARKUP_TEXT_STATE_HEADING) return state->sizes[2];\n\tif (state->current_state & MARKUP_TEXT_STATE_SMALL) return state->sizes[1];\n\treturn state->sizes[0];\n}\n\nstruct GlyphCacheEntry {\n\tstruct TT_Font * font;\n\tsprite_t * sprites[3];\n\tint xs[3];\n\tuint32_t size;\n\tuint32_t glyph;\n\tuint32_t color;\n\tint y;\n};\n\nstatic struct GlyphCacheEntry glyph_cache[1024];\n\nstatic void draw_cached_glyph(gfx_context_t * ctx, struct TT_Font * _font, uint32_t size, int x, int y, uint32_t glyph, uint32_t fg, float xadj) {\n\tunsigned int hash = (((uintptr_t)_font >> 8) ^ (glyph * size)) & 1023;\n\n\tstruct GlyphCacheEntry * entry = &glyph_cache[hash];\n\n\tif (entry->font != _font || entry->size != size || entry->glyph != glyph) {\n\t\tif (entry->sprites[0]) sprite_free(entry->sprites[0]);\n\t\tif (entry->sprites[1]) sprite_free(entry->sprites[1]);\n\t\tif (entry->sprites[2]) sprite_free(entry->sprites[2]);\n\t\ttt_set_size(_font, size);\n\n\t\tentry->font = _font;\n\t\tentry->size = size;\n\t\tentry->glyph = glyph;\n\t\tentry->color = _ALP(fg) == 255 ? fg : rgb(0,0,0);\n\t\tentry->sprites[0] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[0], &entry->y, 0.0);\n\t\tentry->sprites[1] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[1], &entry->y, 0.333);\n\t\tentry->sprites[2] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[2], &entry->y, 0.666);\n\t}\n\n\tif (entry->sprites[0]) {\n\t\tint sprite = xadj < 0.166 ? 0 : xadj < 0.5 ? 1 : 2;\n\t\tif (entry->color != fg) {\n\t\t\tdraw_sprite_alpha_paint(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y, 1.0, fg);\n\t\t} else {\n\t\t\tdraw_sprite(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y);\n\t\t}\n\t}\n}\n\nstatic int string_draw_internal(gfx_context_t * ctx, struct TT_Font * font, int font_size, int x, int y, char * data, uint32_t color) {\n\tfloat x_offset = x;\n\tuint32_t cp = 0;\n\tuint32_t istate = 0;\n\n\tfor (const unsigned char * c = (const unsigned char*)data; *c; ++c) {\n\t\tif (!decode(&istate, &cp, *c)) {\n\t\t\tunsigned int glyph = tt_glyph_for_codepoint(font, cp);\n\t\t\tdraw_cached_glyph(ctx, font, font_size, (int)floor(x_offset), y, glyph, color, x_offset-floor(x_offset));\n\t\t\tx_offset += tt_glyph_width(font, glyph);\n\t\t}\n\t}\n\n\treturn x_offset - x;\n}\n\nstatic int parser_data(struct markup_state * self, void * user, char * data) {\n\tstruct MarkupState * state = (struct MarkupState*)user;\n\tstruct TT_Font * font = fontForState(state);\n\tint size = sizeForState(state);\n\ttt_set_size(font, size);\n\tstate->cursor_x += string_draw_internal(state->ctx, font, size, state->cursor_x, state->cursor_y, data, state->color);\n\tif (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x;\n\treturn 0;\n}\n\nstatic int parser_dryrun(struct markup_state * self, void * user, char * data) {\n\tstruct MarkupState * state = (struct MarkupState*)user;\n\tstruct TT_Font * font = fontForState(state);\n\ttt_set_size(font, sizeForState(state));\n\tstate->cursor_x += tt_string_width(font, data);\n\tif (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x;\n\treturn 0;\n}\n\nstruct MarkupState * markup_setup_renderer(gfx_context_t * ctx, int x, int y, uint32_t color, int dryrun) {\n\tstruct MarkupState * state = malloc(sizeof(struct MarkupState));\n\tstate->parser = markup_init(state, parser_open, parser_close, dryrun ? parser_dryrun : parser_data);\n\tstate->state = list_create();\n\tstate->current_state = 0;\n\tstate->cursor_x = x;\n\tstate->cursor_y = y;\n\tstate->initial_left = x;\n\tstate->color = color;\n\tstate->ctx = ctx;\n\tstate->max_cursor_x = x;\n\tstate->colors = list_create();\n\tstate->sizes[0] = 13;\n\tstate->sizes[1] = 10;\n\tstate->sizes[2] = 18;\n\tstate->dryrun = dryrun;\n\treturn state;\n}\n\nvoid markup_set_base_font_size(struct MarkupState * state, int size) {\n\tstate->sizes[0] = size;\n\tstate->sizes[1] = 10 * size / 13;\n\tstate->sizes[2] = 18 * size / 13;\n}\n\nvoid markup_set_base_state(struct MarkupState * state, int mode) {\n\tstate->current_state = mode;\n}\n\nint markup_push_string(struct MarkupState * state, const char * str) {\n\twhile (*str) {\n\t\tif (markup_parse(state->parser, *str++)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn state->max_cursor_x - state->initial_left;\n}\n\nint markup_push_raw_string(struct MarkupState * state, const char * str) {\n\tif (state->dryrun) {\n\t\treturn parser_dryrun(state->parser, state, (char*)str);\n\t} else {\n\t\treturn parser_data(state->parser, state, (char*)str);\n\t}\n}\n\nint markup_finish_renderer(struct MarkupState * state) {\n\tmarkup_finish(state->parser);\n\tlist_free(state->state);\n\tlist_free(state->colors);\n\tfree(state->state);\n\tfree(state->colors);\n\tint total = state->max_cursor_x - state->initial_left;\n\tfree(state);\n\treturn total;\n}\n\nint markup_string_width(const char * str) {\n\tstruct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1);\n\twhile (*str) {\n\t\tif (markup_parse(state->parser, *str++)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn markup_finish_renderer(state);\n}\n\nint markup_string_height(const char * str) {\n\tstruct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1);\n\twhile (*str) {\n\t\tif (markup_parse(state->parser, *str++)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\tint out = state->cursor_y;\n\tmarkup_finish_renderer(state);\n\treturn out;\n}\n\nint markup_draw_string(gfx_context_t * ctx, int x, int y, const char * str, uint32_t color) {\n\tstruct MarkupState * state = markup_setup_renderer(ctx,x,y,color,0);\n\twhile (*str) {\n\t\tif (markup_parse(state->parser, *str++)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn markup_finish_renderer(state);\n}\n\nvoid markup_text_init(void) {\n\tif (!dejaVuSans) {\n\t\tdejaVuSans             = tt_font_from_shm(\"sans-serif\");\n\t\tdejaVuSans_Bold        = tt_font_from_shm(\"sans-serif.bold\");\n\t\tdejaVuSans_Oblique     = tt_font_from_shm(\"sans-serif.italic\");\n\t\tdejaVuSans_BoldOblique = tt_font_from_shm(\"sans-serif.bolditalic\");\n\t\tdejaVuSansMono             = tt_font_from_shm(\"monospace\");\n\t\tdejaVuSansMono_Bold        = tt_font_from_shm(\"monospace.bold\");\n\t\tdejaVuSansMono_Oblique     = tt_font_from_shm(\"monospace.italic\");\n\t\tdejaVuSansMono_BoldOblique = tt_font_from_shm(\"monospace.bolditalic\");\n\t}\n}\n\n"
  },
  {
    "path": "lib/menu.c",
    "content": "/**\n * @brief Cascading graphical menu library.\n *\n * C reimplementation of the original Python menu library.\n * Used to provide menu bars and the applications menu.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2021 K. Lange\n */\n#include <stdio.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <math.h>\n#include <sys/types.h>\n#include <dlfcn.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n#include <toaru/icon_cache.h>\n#include <toaru/text.h>\n#include <toaru/markup_text.h>\n\n#include <toaru/menu.h>\n\n#define MENU_ENTRY_HEIGHT 20\n#define MENU_BACKGROUND rgb(239,238,232)\n#define MENU_ICON_SIZE 16\n\n#define HILIGHT_BORDER_TOP rgb(54,128,205)\n#define HILIGHT_GRADIENT_TOP rgb(93,163,236)\n#define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220)\n#define HILIGHT_BORDER_BOTTOM rgb(47,106,167)\n\n#define MENU_ENTRY_FLAGS_DISABLED 0x0001\n\nstatic hashmap_t * menu_windows = NULL;\nstatic yutani_t * my_yctx = NULL;\n\nstatic struct MenuList * hovered_menu = NULL;\n\nint menu_definitely_close(struct MenuList * menu);\n\n__attribute__((constructor))\nstatic void _init_menus(void) {\n\tmenu_windows = hashmap_create_int(10);\n\tmarkup_text_init();\n}\n\nhashmap_t * menu_get_windows_hash(void) {\n\treturn menu_windows;\n}\n\nstatic int string_width(const char * s) {\n\treturn markup_string_width(s);\n}\n\nstatic int draw_string(gfx_context_t * ctx, int x, int y, uint32_t color, const char * s) {\n\treturn markup_draw_string(ctx,x,y+13,s,color);\n}\n\nstatic int _menu_is_disabled(struct MenuEntry * entry);\n\nvoid _menu_draw_MenuEntry_Normal(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\n\t_self->offset = offset;\n\n\t/* Background gradient */\n\tif (!_menu_is_disabled(self) && _self->hilight) {\n\t\tdraw_line(ctx, 1, _self->width-2, offset, offset, HILIGHT_BORDER_TOP);\n\t\tdraw_line(ctx, 1, _self->width-2, offset + _self->height - 1, offset + _self->height - 1, HILIGHT_BORDER_BOTTOM);\n\t\tfor (int i = 1; i < self->height-1; ++i) {\n\t\t\tint thing = ((i - 1) * 256) / (_self->height - 2);\n\t\t\tif (thing > 255) thing = 255;\n\t\t\tif (thing < 0) thing = 0;\n\t\t\tuint32_t c = interp_colors(HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM, thing);\n\t\t\tdraw_line(ctx, 1, self->width-2, offset + i, offset + i, c);\n\t\t}\n\t}\n\n\t/* Icon */\n\tif (_self->icon) {\n\t\tsprite_t * icon = icon_get_16(_self->icon);\n\t\tif (icon->width == MENU_ICON_SIZE) {\n\t\t\tif (_menu_is_disabled(self)) {\n\t\t\t\tdraw_sprite_alpha(ctx, icon, 4, offset + 2, 0.5);\n\t\t\t} else {\n\t\t\t\tdraw_sprite(ctx, icon, 4, offset + 2);\n\t\t\t}\n\t\t} else {\n\t\t\tif (_menu_is_disabled(self)) {\n\t\t\t\tdraw_sprite_scaled_alpha(ctx, icon, 4, offset + 2, MENU_ICON_SIZE, MENU_ICON_SIZE, 0.5);\n\t\t\t} else {\n\t\t\t\tdraw_sprite_scaled(ctx, icon, 4, offset + 2, MENU_ICON_SIZE, MENU_ICON_SIZE);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* Foreground text color */\n\tuint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0));\n\n\t/* Draw title */\n\tdraw_string(ctx, 22, offset + 1, color, _self->title);\n}\n\nvoid _menu_focus_MenuEntry_Normal(struct MenuEntry * self, int focused) {\n\tif (focused) {\n\t\tif (self->_owner && self->_owner->child) {\n\t\t\tmenu_definitely_close(self->_owner->child);\n\t\t\tself->_owner->child = NULL;\n\t\t}\n\t}\n}\n\nvoid _menu_activate_MenuEntry_Normal(struct MenuEntry * self, int flags) {\n\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\n\tif (_menu_is_disabled(self)) return; /* Do nothing */\n\n\tlist_t * menu_keys = hashmap_keys(menu_windows);\n\thovered_menu = NULL;\n\tforeach(_key, menu_keys) {\n\t\tyutani_window_t * window = hashmap_get(menu_windows, (void*)(uintptr_t)_key->value);\n\t\tif (window) {\n\t\t\tstruct MenuList * menu = window->user_data;\n\t\t\tmenu_definitely_close(menu);\n\t\t\tif (menu->parent && menu->parent->child == menu) {\n\t\t\t\tmenu->parent->child = NULL;\n\t\t\t}\n\t\t}\n\t}\n\n\tlist_free(menu_keys);\n\tfree(menu_keys);\n\n\tif (_self->callback) {\n\t\t_self->callback(_self);\n\t}\n}\n\nstatic struct MenuEntryVTable _menu_vtable_MenuEntry_Normal = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Normal,\n\t.focus_change = _menu_focus_MenuEntry_Normal,\n\t.activate = _menu_activate_MenuEntry_Normal,\n};\n\nstruct MenuEntry * menu_create_normal(const char * icon, const char * action, const char * title, void (*callback)(struct MenuEntry *)) {\n\tstruct MenuEntry_Normal * out = calloc(1,sizeof(struct MenuEntry_Normal));\n\n\tout->_type = MenuEntry_Normal;\n\tout->height = MENU_ENTRY_HEIGHT;\n\tout->hilight = 0;\n\tout->vtable = &_menu_vtable_MenuEntry_Normal;\n\tout->icon = icon ? strdup(icon) : NULL;\n\tout->title = strdup(title);\n\tout->action = action ? strdup(action) : NULL;\n\tout->callback = callback;\n\n\tout->rwidth = 50 + string_width(out->title);\n\n\treturn (struct MenuEntry *)out;\n}\n\nvoid _menu_draw_MenuEntry_Toggle(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\t_menu_draw_MenuEntry_Normal(ctx,self,offset);\n\tstruct MenuEntry_Toggle * _self = (struct MenuEntry_Toggle *)self;\n\n\tif (_self->set) {\n\t\tsprite_t * check_box = icon_get_16(\"check\");\n\t\tuint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0));\n\t\tdraw_sprite_alpha_paint(ctx, check_box, 4, offset + 2, 1.0, color);\n\t}\n}\nstatic struct MenuEntryVTable _menu_vtable_MenuEntry_Toggle = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Toggle, /* Only the renderer differs; users must manage toggle state */\n\t.focus_change = _menu_focus_MenuEntry_Normal,\n\t.activate = _menu_activate_MenuEntry_Normal,\n};\n\nstruct MenuEntry * menu_create_toggle(const char * action, const char * title, int set, void (*callback)(struct MenuEntry *)) {\n\t/* Reuse the initializer for the normal one, assuming with no icon. */\n\tstruct MenuEntry_Toggle * out = (struct MenuEntry_Toggle*)menu_create_normal(NULL, action, title, callback);\n\tif (!out) return out;\n\tout = realloc(out, sizeof(struct MenuEntry_Toggle));\n\n\tout->_type = MenuEntry_Toggle;\n\tout->vtable = &_menu_vtable_MenuEntry_Toggle;\n\n\t/* And just set our status */\n\tout->set = set;\n\treturn out;\n}\n\nvoid _menu_draw_MenuEntry_Submenu(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\n\tstruct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self;\n\tint h = _self->hilight;\n\tif (_self->_owner && _self->_my_child && _self->_owner->child == _self->_my_child && !_self->_my_child->closed) {\n\t\t_self->hilight = 1;\n\t}\n\t_menu_draw_MenuEntry_Normal(ctx,self,offset);\n\n\t/* Draw the tick on the right side to indicate this is a submenu */\n\tuint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0));\n\tsprite_t * tick = icon_get_16(\"menu-tick\");\n\tdraw_sprite_alpha_paint(ctx, tick, _self->width - 16, offset + 2, 1.0, color);\n\t_self->hilight = h;\n}\n\nvoid _menu_focus_MenuEntry_Submenu(struct MenuEntry * self, int focused) {\n\tif (focused) {\n\t\tself->vtable->activate(self, focused);\n\t}\n}\n\nvoid _menu_activate_MenuEntry_Submenu(struct MenuEntry * self, int focused) {\n\tstruct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self;\n\n\tif (_menu_is_disabled(self)) return; /* Do nothing */\n\n\tif (_self->_owner && _self->_owner->set) {\n\t\t/* Show a menu */\n\t\tstruct MenuList * new_menu = menu_set_get_menu(_self->_owner->set, (char *)_self->action);\n\t\tif (_self->_owner->child && _self->_owner->child != new_menu) {\n\t\t\tmenu_definitely_close(_self->_owner->child);\n\t\t\t_self->_owner->child = NULL;\n\t\t}\n\t\tnew_menu->parent = _self->_owner;\n\t\tnew_menu->parent->child = new_menu;\n\t\t_self->_my_child = new_menu;\n\t\tif (new_menu->closed) {\n\t\t\tnew_menu->main_window = new_menu->parent->main_window;\n\t\t\tmenu_prepare(new_menu, _self->_owner->window->ctx);\n\t\t\tint offset_x = _self->_owner->window->width - 2;\n\t\t\tif (_self->_owner->window->width + _self->_owner->window->x - 2 + new_menu->window->width > _self->_owner->window->ctx->display_width) {\n\t\t\t\toffset_x = 2 - new_menu->window->width;\n\t\t\t}\n\t\t\tyutani_window_move_relative(_self->_owner->window->ctx, new_menu->window, _self->_owner->window,\n\t\t\t\toffset_x, _self->offset - 4);\n\t\t\tyutani_flip(_self->_owner->window->ctx, new_menu->window);\n\t\t}\n\t}\n\n}\n\nstatic struct MenuEntryVTable _menu_vtable_MenuEntry_Submenu = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Submenu,\n\t.focus_change = _menu_focus_MenuEntry_Submenu,\n\t.activate = _menu_activate_MenuEntry_Submenu,\n};\n\nstruct MenuEntry * menu_create_submenu(const char * icon, const char * action, const char * title) {\n\tstruct MenuEntry_Submenu * out = calloc(1,sizeof(struct MenuEntry_Submenu));\n\n\tout->_type = MenuEntry_Submenu;\n\tout->height = MENU_ENTRY_HEIGHT;\n\tout->hilight = 0;\n\tout->vtable = &_menu_vtable_MenuEntry_Submenu;\n\tout->icon = icon ? strdup(icon) : NULL;\n\tout->title = strdup(title);\n\tout->action = action ? strdup(action) : NULL;\n\n\tout->rwidth = 50 + string_width(out->title);\n\n\treturn (struct MenuEntry *)out;\n}\n\nvoid _menu_draw_MenuEntry_Separator(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\tself->offset = offset;\n\tdraw_line(ctx, 2, self->width-4, offset+3, offset+3, rgb(178,178,178));\n\tdraw_line(ctx, 2, self->width-5, offset+4, offset+4, rgb(250,250,250));\n}\n\nvoid _menu_focus_MenuEntry_Separator(struct MenuEntry * self, int focused) {\n\tif (focused) {\n\t\tif (self->_owner && self->_owner->child) {\n\t\t\tmenu_definitely_close(self->_owner->child);\n\t\t\tself->_owner->child = NULL;\n\t\t}\n\t}\n}\n\nvoid _menu_activate_MenuEntry_Separator(struct MenuEntry * self, int focused) {\n\n}\n\nstatic struct MenuEntryVTable _menu_vtable_MenuEntry_Separator = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Separator,\n\t.focus_change = _menu_focus_MenuEntry_Separator,\n\t.activate = _menu_activate_MenuEntry_Separator,\n};\n\nstruct MenuEntry * menu_create_separator(void) {\n\tstruct MenuEntry_Separator * out = calloc(1,sizeof(struct MenuEntry_Separator));\n\n\tout->_type = MenuEntry_Separator;\n\tout->height = 6;\n\tout->hilight = 0;\n\tout->rwidth = 10; /* at least a bit please */\n\tout->vtable = &_menu_vtable_MenuEntry_Separator;\n\n\treturn (struct MenuEntry *)out;\n}\n\nvoid menu_update_title(struct MenuEntry * self, char * new_title) {\n\n\tif (self->_type == MenuEntry_Normal) {\n\t\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\t\tif (_self->title) {\n\t\t\tfree((void*)_self->title);\n\t\t}\n\t\t_self->title = strdup(new_title);\n\t\t_self->rwidth = 50 + string_width(_self->title);\n\t} else if (self->_type == MenuEntry_Submenu) {\n\t\tstruct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self;\n\t\tif (_self->title) {\n\t\t\tfree((void*)_self->title);\n\t\t}\n\t\t_self->title = strdup(new_title);\n\t\t_self->rwidth = 50 + string_width(_self->title);\n\t}\n}\n\nvoid menu_update_icon(struct MenuEntry * self, char * newIcon) {\n\tswitch (self->_type) {\n\t\tcase MenuEntry_Submenu:\n\t\tcase MenuEntry_Normal: {\n\t\t\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\t\t\tif (_self->icon)   free(_self->icon);\n\t\t\t_self->icon = newIcon ? strdup(newIcon) : NULL;\n\t\t\tbreak;\n\t\t}\n\t\tcase MenuEntry_Toggle: /* Toggle should not be able to set icon */\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid menu_update_toggle_state(struct MenuEntry * self, int state) {\n\tswitch (self->_type) {\n\t\tcase MenuEntry_Toggle: {\n\t\t\tstruct MenuEntry_Toggle * _self = (struct MenuEntry_Toggle *)self;\n\t\t\t_self->set = state;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid menu_update_enabled(struct MenuEntry * self, int state) {\n\tswitch (self->_type) {\n\t\tcase MenuEntry_Toggle:\n\t\tcase  MenuEntry_Submenu:\n\t\tcase MenuEntry_Normal: {\n\t\t\tunsigned long flags = ((struct MenuEntry_Normal*)self)->flags & ~MENU_ENTRY_FLAGS_DISABLED;\n\t\t\tif (!state) flags |= MENU_ENTRY_FLAGS_DISABLED;\n\t\t\t((struct MenuEntry_Normal*)self)->flags = flags;\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\nvoid menu_free_entry(struct MenuEntry * self) {\n\tswitch (self->_type) {\n\t\tcase MenuEntry_Toggle:\n\t\tcase  MenuEntry_Submenu:\n\t\tcase MenuEntry_Normal: {\n\t\t\tstruct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self;\n\t\t\tif (_self->icon)   free(_self->icon);\n\t\t\tif (_self->title)  free(_self->title);\n\t\t\tif (_self->action) free(_self->action);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n\tfree(self);\n}\n\nstatic int _close_enough(struct yutani_msg_window_mouse_event * me) {\n\tif (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) {\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic char read_buf[1024];\nstatic size_t available = 0;\nstatic size_t offset = 0;\nstatic size_t read_from = 0;\nstatic char * read_line(FILE * f, char * out, ssize_t len) {\n\twhile (len > 0) {\n\t\tif (available == 0) {\n\t\t\tif (offset == 1024) {\n\t\t\t\toffset = 0;\n\t\t\t}\n\t\t\tsize_t r = read(fileno(f), &read_buf[offset], 1024 - offset);\n\t\t\tread_from = offset;\n\t\t\tavailable = r;\n\t\t\toffset += available;\n\t\t}\n\n\t\tif (available == 0) {\n\t\t\t*out = '\\0';\n\t\t\treturn out;\n\t\t}\n\n\t\twhile (read_from < offset && len > 0) {\n\t\t\t*out = read_buf[read_from];\n\t\t\tlen--;\n\t\t\tread_from++;\n\t\t\tavailable--;\n\t\t\tif (*out == '\\n') {\n\t\t\t\treturn out;\n\t\t\t}\n\t\t\tout++;\n\t\t}\n\t}\n\n\treturn out;\n}\n\nvoid menu_calculate_dimensions(struct MenuList * menu, int * height, int * width) {\n\tlist_t * list = menu->entries;\n\t*width = 0;\n\t*height = (menu->flags & MENU_FLAG_BUBBLE) ? 16 : 8; /* TODO top and height */\n\tforeach(node, list) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\t*height += entry->height;\n\t\tif (*width < entry->rwidth) {\n\t\t\t*width = entry->rwidth;\n\t\t}\n\t}\n\t/* Go back through and update actual widths */\n\tforeach(node, list) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\tentry->width = *width;\n\t}\n}\n\nstruct MenuList * menu_set_get_root(struct MenuSet * menu) {\n\treturn (void*)hashmap_get(menu->_menus,\"_\");\n}\n\nstruct MenuList * menu_set_get_menu(struct MenuSet * menu, char * submenu) {\n\treturn (void*)hashmap_get(menu->_menus, submenu);\n}\n\nvoid menu_insert(struct MenuList * menu, struct MenuEntry * entry) {\n\tlist_insert(menu->entries, entry);\n\tentry->_owner = menu;\n}\n\nstruct MenuList * menu_create(void) {\n\tstruct MenuList * p = malloc(sizeof(struct MenuList));\n\tp->entries = list_create();\n\tp->ctx = NULL;\n\tp->window = NULL;\n\tp->set = NULL;\n\tp->child = NULL;\n\tp->_bar = NULL;\n\tp->parent = NULL;\n\tp->closed = 1;\n\tp->flags = 0;\n\tp->tail_offset = 0;\n\tp->main_window = 0;\n\treturn p;\n}\n\nstruct MenuSet * menu_set_create(void) {\n\tstruct MenuSet * _out = malloc(sizeof(struct MenuSet));\n\t_out->_menus = hashmap_create(10);\n\treturn _out;\n}\n\nvoid menu_set_insert(struct MenuSet * set, char * action, struct MenuList * menu) {\n\thashmap_set(set->_menus, action, menu);\n\tmenu->set = set;\n}\n\nstruct MenuSet * menu_set_from_description(const char * path, void (*callback)(struct MenuEntry *)) {\n\tFILE * f;\n\tif (!strcmp(path,\"-\")) {\n\t\tf = stdin;\n\t} else {\n\t\tf = fopen(path,\"r\");\n\t}\n\n\tif (!f) {\n\t\treturn NULL;\n\t}\n\n\tstruct MenuSet * _out = malloc(sizeof(struct MenuSet));\n\thashmap_t * out = hashmap_create(10);\n\t_out->_menus = out;\n\n\tstruct MenuList * current_menu = NULL;\n\n\t/* Read through the file */\n\tchar line[256];\n\twhile (1) {\n\t\tmemset(line, 0, 256);\n\t\tread_line(f, line, 256);\n\t\tif (!*line) break;\n\n\t\tif (line[strlen(line)-1] == '\\n') {\n\t\t\tline[strlen(line)-1] = '\\0';\n\t\t}\n\n\t\tif (!*line) continue; /* skip blank */\n\n\t\tif (*line == ':') {\n\t\t\t/* New menu */\n\t\t\tstruct MenuList * p = menu_create();\n\t\t\tp->set = _out;\n\t\t\thashmap_set(out, line+1, p);\n\t\t\tcurrent_menu = p;\n\t\t} else if (*line == '#') {\n\t\t\t/* Comment */\n\t\t\tcontinue;\n\t\t} else if (*line == '-') {\n\t\t\tif (!current_menu) {\n\t\t\t\tfprintf(stderr, \"Tried to add separator with no active menu.\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\tmenu_insert(current_menu, menu_create_separator());\n\t\t} else if (*line == '&') {\n\t\t\tif (!current_menu) {\n\t\t\t\tfprintf(stderr, \"Tried to add submenu with no active menu.\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\tchar * action = line+1;\n\t\t\tchar * icon = strstr(action,\",\");\n\t\t\tif (!icon) {\n\t\t\t\tfprintf(stderr, \"Malformed line in submenu: no icon\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\t*icon = '\\0';\n\t\t\ticon++;\n\t\t\tchar * title = strstr(icon,\",\");\n\t\t\tif (!title) {\n\t\t\t\tfprintf(stderr, \"Malformed line in submenu: no title\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\t*title = '\\0';\n\t\t\ttitle++;\n\t\t\tmenu_insert(current_menu, menu_create_submenu(icon,action,title));\n\t\t} else {\n\t\t\tif (!current_menu) {\n\t\t\t\tfprintf(stderr, \"Tried to add item with no active menu.\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\tchar * action = line;\n\t\t\tchar * icon = strstr(action,\",\");\n\t\t\tif (!icon) {\n\t\t\t\tfprintf(stderr, \"Malformed line in action: no icon\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\t*icon = '\\0';\n\t\t\ticon++;\n\t\t\tchar * title = strstr(icon,\",\");\n\t\t\tif (!title) {\n\t\t\t\tfprintf(stderr, \"Malformed line in action: no title\\n\");\n\t\t\t\tgoto failure;\n\t\t\t}\n\t\t\t*title = '\\0';\n\t\t\ttitle++;\n\t\t\tmenu_insert(current_menu, menu_create_normal(icon,action,title,callback));\n\t\t}\n\t}\n\n\treturn _out;\n\nfailure:\n\tfprintf(stderr, \"malformed description file\\n\");\n\tif (f != stdin) {\n\t\tfclose(f);\n\t}\n\tfree(out);\n\treturn NULL;\n}\n\nstatic void _menu_redraw(yutani_window_t * menu_window, yutani_t * yctx, struct MenuList * menu, int expose) {\n\n\tgfx_context_t * ctx = menu->ctx;\n\tlist_t * entries = menu->entries;\n\t/* Window background */\n\tif (menu->flags & MENU_FLAG_BUBBLE) {\n\t\tdraw_fill(ctx, rgba(0,0,0,0));\n\t\tdraw_rounded_rectangle(ctx, 0, 6, ctx->width, ctx->height - 6, 6, rgb(109,111,112));\n\t\tdraw_rounded_rectangle(ctx, 1, 7, ctx->width-2, ctx->height - 8, 5, MENU_BACKGROUND);\n\n\t\t/* Figure out where to draw the tail */\n\t\tint tail_left = 0;\n\t\t/* Do we have a set tail? */\n\t\t#define TAIL_BOUND 8\n\t\tif (menu->flags & MENU_FLAG_TAIL_POSITION) {\n\t\t\ttail_left = (menu->tail_offset < TAIL_BOUND) ? TAIL_BOUND : (menu->tail_offset > ctx->width - TAIL_BOUND) ? (ctx->width - TAIL_BOUND) : menu->tail_offset;\n\t\t} else if (menu->flags & MENU_FLAG_BUBBLE_LEFT) {\n\t\t\ttail_left = 16;\n\t\t} else if (menu->flags & MENU_FLAG_BUBBLE_RIGHT) {\n\t\t\ttail_left = ctx->width - 16;\n\t\t} else if (menu->flags & MENU_FLAG_BUBBLE_CENTER) {\n\t\t\ttail_left = ctx->width / 2;\n\t\t}\n\t\tfor (int i = 1; i < 7; ++i) {\n\t\t\tdraw_line(ctx, tail_left - i, tail_left + i, i, i, MENU_BACKGROUND);\n\t\t}\n\t\tdraw_line_aa(ctx, tail_left - 6, tail_left, 6, 0, rgb(109,111,112), 0.5);\n\t\tdraw_line_aa(ctx, tail_left + 6, tail_left, 6, 0, rgb(109,111,112), 0.5);\n\t} else {\n\t\tdraw_fill(ctx, MENU_BACKGROUND);\n\n\t\t/* Window border */\n\t\tdraw_line(ctx, 0, ctx->width-1, 0, 0, rgb(109,111,112));\n\t\tdraw_line(ctx, 0, 0, 0, ctx->height-1, rgb(109,111,112));\n\t\tdraw_line(ctx, ctx->width-1, ctx->width-1, 0, ctx->height-1, rgb(109,111,112));\n\t\tdraw_line(ctx, 0, ctx->width-1, ctx->height-1, ctx->height-1, rgb(109,111,112));\n\t}\n\n\t/* Draw menu entries */\n\tint offset = (menu->flags & MENU_FLAG_BUBBLE) ? 12 : 4;\n\tforeach(node, entries) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\tif (entry->vtable->methods >= 1 && entry->vtable->renderer) {\n\t\t\tentry->vtable->renderer(ctx, entry, offset);\n\t\t}\n\n\t\toffset += entry->height;\n\t}\n\n\tflip(ctx);\n\n\tif (expose) {\n\t\tyutani_flip(yctx, menu_window);\n\t}\n}\n\nvoid menu_prepare(struct MenuList * menu, yutani_t * yctx) {\n\t/* Calculate window dimensions */\n\tint height, width;\n\tmenu_calculate_dimensions(menu,&height, &width);\n\n\tmy_yctx = yctx;\n\n\tmenu->closed = 0;\n\n\t/* Create window */\n\n\tyutani_window_t * menu_window = yutani_window_create_flags(yctx, width, height,\n\t\t((menu->flags & MENU_FLAG_BUBBLE) ? YUTANI_WINDOW_FLAG_ALT_ANIMATION : YUTANI_WINDOW_FLAG_NO_ANIMATION)\n\t\t| YUTANI_WINDOW_FLAG_DISALLOW_DRAG\n\t\t| YUTANI_WINDOW_FLAG_DISALLOW_RESIZE\n\t\t| YUTANI_WINDOW_FLAG_PARENT_WID,\n\t\tmenu->main_window);\n\tyutani_set_stack(yctx, menu_window, YUTANI_ZORDER_MENU);\n\tif (menu->ctx) {\n\t\treinit_graphics_yutani(menu->ctx, menu_window);\n\t} else {\n\t\tmenu->ctx = init_graphics_yutani_double_buffer(menu_window);\n\t}\n\n\tmenu_window->user_data = menu;\n\tmenu->window = menu_window;\n\n\t_menu_redraw(menu_window, yctx, menu, 0);\n\n\thashmap_set(menu_windows, (void*)(uintptr_t)menu_window->wid, menu_window);\n}\n\nvoid menu_show_at(struct MenuList * menu, yutani_window_t * parent, int x, int y) {\n\tmenu->main_window = parent;\n\tmenu_prepare(menu, parent->ctx);\n\n\tif (parent->x + x + menu->window->width > parent->ctx->display_width) x -= menu->window->width;\n\tif (parent->y + y + menu->window->height > parent->ctx->display_height) y -= menu->window->height;\n\n\tyutani_window_move_relative(parent->ctx, menu->window, parent, x, y);\n\tyutani_flip(parent->ctx, menu->window);\n}\n\nint menu_has_eventual_child(struct MenuList * root, struct MenuList * child) {\n\n\tif (!child) return 0;\n\tif (root == child) return 1;\n\n\tstruct MenuList * candidate = root->child;\n\n\twhile (candidate && candidate != child) {\n\t\tif (candidate == root->child) {\n\t\t\treturn 1;\n\t\t}\n\t\tcandidate = root->child;\n\t}\n\n\treturn (candidate == child);\n}\n\nint menu_definitely_close(struct MenuList * menu) {\n\n\tif (menu->child) {\n\t\tmenu_definitely_close(menu->child);\n\t\tmenu->child = NULL;\n\t}\n\n\tif (menu->closed) {\n\t\treturn 0;\n\t}\n\n\t/* if focused_widget, leave focus on widget */\n\tforeach(node, menu->entries) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\tentry->hilight = 0;\n\t}\n\tmenu->closed = 1;\n\tyutani_wid_t wid = menu->window->wid;\n\tyutani_close(menu->window->ctx, menu->window);\n\tmenu->window = NULL;\n\tmenu->main_window = NULL;\n\thashmap_remove(menu_windows, (void*)(uintptr_t)wid);\n\n\treturn 0;\n}\n\nint menu_leave(struct MenuList * menu) {\n\n\tif (!hovered_menu) {\n\t\twhile (menu->parent) {\n\t\t\tmenu = menu->parent;\n\t\t}\n\t\tmenu_definitely_close(menu);\n\t\treturn 0;\n\t}\n\n\tif (!menu_has_eventual_child(menu, hovered_menu)) {\n\t\t/* Get all menus */\n\t\tlist_t * menu_keys = hashmap_keys(menu_windows);\n\t\tforeach(_key, menu_keys) {\n\t\t\tyutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)_key->value);\n\t\t\tif (window) {\n\t\t\t\tstruct MenuList * menu = window->user_data;\n\t\t\t\tif (!hovered_menu || (menu != hovered_menu->child && !menu_has_eventual_child(menu, hovered_menu)))  {\n\t\t\t\t\tmenu_definitely_close(menu);\n\t\t\t\t\tif (menu->parent && menu->parent->child == menu) {\n\t\t\t\t\t\tmenu->parent->child = NULL;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlist_free(menu_keys);\n\t\tfree(menu_keys);\n\t}\n\n\treturn 0;\n}\n\nstatic int _menu_is_disabled(struct MenuEntry * entry) {\n\tswitch (entry->_type) {\n\t\tcase MenuEntry_Toggle:\n\t\tcase MenuEntry_Submenu:\n\t\tcase MenuEntry_Normal:\n\t\t\treturn ((struct MenuEntry_Normal*)entry)->flags & MENU_ENTRY_FLAGS_DISABLED;\n\t\tcase MenuEntry_Separator:\n\t\t\treturn 1;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nvoid menu_key_action(struct MenuList * menu, struct yutani_msg_key_event * me) {\n\tif (me->event.action != KEY_ACTION_DOWN) return;\n\n\tyutani_window_t * window = menu->window;\n\tyutani_t * yctx = window->ctx;\n\n\thovered_menu = menu;\n\n\t/* Find hilighted entry */\n\tstruct MenuEntry * hilighted = NULL;\n\tstruct MenuEntry * previous = NULL;\n\tstruct MenuEntry * next = NULL;\n\tstruct MenuEntry * first = NULL;\n\tstruct MenuEntry * last = NULL;\n\tforeach(node, menu->entries) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\tif (!first && !_menu_is_disabled(entry)) {\n\t\t\tfirst = entry;\n\t\t}\n\t\tif (!hilighted && entry->hilight) {\n\t\t\thilighted = entry;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!_menu_is_disabled(entry)) {\n\t\t\tif (!next && hilighted) next = entry;\n\t\t\tif (!hilighted) previous = entry;\n\t\t\tlast = entry;\n\t\t}\n\t}\n\n\tif (me->event.keycode == KEY_ARROW_DOWN) {\n\t\tif (hilighted) {\n\t\t\thilighted->hilight = 0;\n\t\t}\n\t\thilighted = next ? next : first;\n\t\tif (hilighted) hilighted->hilight = 1;\n\t\t_menu_redraw(window,yctx,menu,1);\n\t} else if (me->event.keycode == KEY_ARROW_UP) {\n\t\tif (hilighted) {\n\t\t\thilighted->hilight = 0;\n\t\t}\n\t\thilighted = previous ? previous : last;\n\t\tif (hilighted) hilighted->hilight = 1;\n\t\t_menu_redraw(window,yctx,menu,1);\n\t} else if (me->event.keycode == KEY_ARROW_RIGHT) {\n\t\tif (!hilighted) {\n\t\t\thilighted = menu->entries->head->value;\n\t\t}\n\t\tif (hilighted) {\n\t\t\thilighted->hilight = 1;\n\t\t\tif (hilighted->_type == MenuEntry_Submenu) {\n\t\t\t\tif (hilighted->vtable->methods >= 3 && hilighted->vtable->activate) {\n\t\t\t\t\thilighted->vtable->activate(hilighted, 0);\n\t\t\t\t}\n\t\t\t\t_menu_redraw(window,yctx,menu,1);\n\t\t\t} else {\n\t\t\t\tstruct menu_bar * bar = NULL;\n\t\t\t\tstruct MenuList * p = menu;\n\t\t\t\tdo {\n\t\t\t\t\tif (p->_bar) {\n\t\t\t\t\t\tbar = p->_bar;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} while ((p = p->parent));\n\t\t\t\tif (bar) {\n\t\t\t\t\tmenu_definitely_close(p);\n\t\t\t\t\tint active = (bar->active_entry_idx + 1 + bar->num_entries) % (bar->num_entries);\n\t\t\t\t\tbar->active_entry = &bar->entries[active];\n\t\t\t\t\tif (bar->redraw_callback) {\n\t\t\t\t\t\tbar->redraw_callback(bar);\n\t\t\t\t\t}\n\t\t\t\t\tmenu_bar_show_menu(yctx, bar->window, bar, -1, bar->active_entry);\n\t\t\t\t} else {\n\t\t\t\t\t_menu_redraw(window,yctx,menu,1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if (me->event.key == '\\n') {\n\t\tif (!hilighted) {\n\t\t\thilighted = menu->entries->head->value;\n\t\t}\n\t\tif (hilighted) {\n\t\t\thilighted->hilight = 1;\n\t\t\tif (hilighted->vtable->methods >= 3 && hilighted->vtable->activate) {\n\t\t\t\thilighted->vtable->activate(hilighted, 0);\n\t\t\t}\n\t\t}\n\t} else if (me->event.keycode == KEY_ARROW_LEFT) {\n\t\tif (menu->parent) {\n\t\t\thovered_menu = menu->parent;\n\t\t} /* else previous from menu bar? */\n\t\tmenu_definitely_close(menu);\n\t\tif (menu->_bar) {\n\t\t\tint active = (menu->_bar->active_entry_idx - 1 + menu->_bar->num_entries) % (menu->_bar->num_entries);\n\t\t\tmenu->_bar->active_entry = &menu->_bar->entries[active];\n\t\t\tif (menu->_bar->redraw_callback) {\n\t\t\t\tmenu->_bar->redraw_callback(menu->_bar);\n\t\t\t}\n\t\t\tmenu_bar_show_menu(yctx, menu->_bar->window, menu->_bar, -1, menu->_bar->active_entry);\n\t\t} else if (menu->parent && menu->parent->window) {\n\t\t\tyutani_focus_window(yctx, menu->parent->window->wid);\n\t\t}\n\t} else if (me->event.keycode == KEY_ESCAPE) {\n\t\thovered_menu = NULL;\n\t\tmenu_leave(menu);\n\t}\n}\n\nvoid menu_mouse_action(struct MenuList * menu, struct yutani_msg_window_mouse_event * me) {\n\tyutani_window_t * window = menu->window;\n\tyutani_t * yctx = window->ctx;\n\n\tint offset = (menu->flags & MENU_FLAG_BUBBLE) ? 12 : 4;\n\tint changed = 0;\n\tforeach(node, menu->entries) {\n\t\tstruct MenuEntry * entry = node->value;\n\t\tif (me->new_y >= offset && me->new_y < offset + entry->height &&\n\t\t\t\tme->new_x >= 0 && me->new_x < entry->width) {\n\t\t\tif (!entry->hilight) {\n\t\t\t\tchanged = 1;\n\t\t\t\tentry->hilight = 1;\n\t\t\t\tif (entry->vtable->methods >= 2 && entry->vtable->focus_change) {\n\t\t\t\t\tentry->vtable->focus_change(entry, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entry->vtable->methods >= 4 && entry->vtable->mouse_event) {\n\t\t\t\tif (entry->vtable->mouse_event(entry, me)) {\n\t\t\t\t\t_menu_redraw(window,yctx,menu,1);\n\t\t\t\t}\n\t\t\t} else if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) {\n\t\t\t\tif (entry->vtable->methods >= 3 && entry->vtable->activate) {\n\t\t\t\t\tentry->vtable->activate(entry, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tif (entry->hilight) {\n\t\t\t\tchanged = 1;\n\t\t\t\tentry->hilight = 0;\n\t\t\t\tif (entry->vtable->methods >= 2 && entry->vtable->focus_change) {\n\t\t\t\t\tentry->vtable->focus_change(entry, 0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\toffset += entry->height;\n\t}\n\tif (changed) {\n\t\t_menu_redraw(window,yctx,menu,1);\n\t}\n}\n\nvoid menu_force_redraw(struct MenuList * menu) {\n\tyutani_window_t * window = menu->window;\n\tyutani_t * yctx = window->ctx;\n\t_menu_redraw(window,yctx,menu,1);\n}\n\nstruct MenuList * menu_any_contains(int x, int y) {\n\tstruct MenuList * out = NULL;\n\tlist_t * menu_keys = hashmap_keys(menu_windows);\n\tforeach(_key, menu_keys) {\n\t\tyutani_window_t * window = hashmap_get(menu_windows, (void*)_key->value);\n\t\tif (window) {\n\t\t\tif (x >= (int)window->x && x < (int)window->x + (int)window->width && y >= (int)window->y && y < (int)window->y + (int)window->height) {\n\t\t\t\tout = window->user_data;\n\t\t\t\tbreak;\n\n\t\t\t}\n\t\t}\n\t}\n\n\tlist_free(menu_keys);\n\tfree(menu_keys);\n\n\treturn out;\n}\n\nint menu_process_event(yutani_t * yctx, yutani_msg_t * m) {\n\tif (m) {\n\t\tswitch (m->type) {\n\t\t\tcase YUTANI_MSG_KEY_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_key_event * me = (void*)m->data;\n\t\t\t\t\tif (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) {\n\t\t\t\t\t\tyutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid);\n\t\t\t\t\t\tstruct MenuList * menu = window->user_data;\n\t\t\t\t\t\tmenu_key_action(menu, me);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_MOUSE_EVENT:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_mouse_event * me = (void*)m->data;\n\t\t\t\t\tif (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) {\n\t\t\t\t\t\tyutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid);\n\t\t\t\t\t\tstruct MenuList * menu = window->user_data;\n\t\t\t\t\t\tif (me->new_x >= 0 && me->new_x < (int)window->width && me->new_y >= 0 && me->new_y < (int)window->height) {\n\t\t\t\t\t\t\tif (hovered_menu != menu)  {\n\t\t\t\t\t\t\t\thovered_menu = menu;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (hovered_menu) {\n\t\t\t\t\t\t\t\tstruct MenuList * t = menu_any_contains(me->new_x + window->x, me->new_y + window->y);\n\t\t\t\t\t\t\t\tif (t) {\n\t\t\t\t\t\t\t\t\thovered_menu = t;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\thovered_menu = NULL;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmenu_mouse_action(menu, me);\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase YUTANI_MSG_WINDOW_FOCUS_CHANGE:\n\t\t\t\t{\n\t\t\t\t\tstruct yutani_msg_window_focus_change * me = (void*)m->data;\n\t\t\t\t\tif (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) {\n\t\t\t\t\t\tyutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid);\n\t\t\t\t\t\tstruct MenuList * menu = window->user_data;\n\t\t\t\t\t\tif (!me->focused) {\n\t\t\t\t\t\t\t/* XXX leave menu */\n\t\t\t\t\t\t\tmenu_leave(menu);\n\t\t\t\t\t\t\t/* if root and not window.root.menus and window.root.focused */\n\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twindow->focused = me->focused;\n\t\t\t\t\t\t\t/* Redraw? */\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn 0;\n}\n\nvoid menu_bar_render(struct menu_bar * self, gfx_context_t * ctx) {\n\tint _x = self->x;\n\tint _y = self->y;\n\tint width = self->width;\n\tint height = MENU_BAR_HEIGHT;\n\n\tif (_x < 0) _x = 0;\n\tif (_y < 0) _y = 0;\n\tif (_x + width > ctx->width) width = ctx->width - _x;\n\tif (_y + height > ctx->height) height = ctx->height - _y;\n\n\tgfx_context_t * subctx = init_graphics_subregion(ctx, _x, _y, width, height);\n\n\tuint32_t menu_bar_color = rgb(59,59,59);\n\tdraw_rectangle(subctx, 0, 0, width, height, menu_bar_color);\n\n\t/* for each menu entry */\n\tint offset = 0;\n\tstruct menu_bar_entries * _entries = self->entries;\n\n\tif (!self->num_entries) {\n\t\twhile (_entries->title) {\n\t\t\t_entries++;\n\t\t\tself->num_entries++;\n\t\t}\n\t\t_entries = self->entries;\n\t}\n\twhile (_entries->title) {\n\t\tint w = string_width(_entries->title) + 11;\n\t\tif ((self->active_menu && hashmap_has(menu_get_windows_hash(), (void*)(uintptr_t)self->active_menu_wid)) && _entries == self->active_entry) {\n\t\t\tdraw_rectangle(subctx, offset + 2, 0, w, height, rgb(93,163,236));\n\t\t}\n\t\tdraw_string(subctx, offset + 7, 2, 0xFFFFFFFF, _entries->title);\n\t\toffset += w;\n\t\t_entries++;\n\t}\n\n\tfree(subctx);\n}\n\nvoid menu_bar_show_menu(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, int offset, struct menu_bar_entries * _entries) {\n\tstruct MenuList * new_menu = menu_set_get_menu(self->set, _entries->action);\n\tint i = 0;\n\n\tif (offset == -1) {\n\t\t/* Must calculate */\n\t\toffset = self->x;\n\t\tstruct menu_bar_entries * e = self->entries;\n\t\twhile (e->title) {\n\t\t\tif (e == _entries) break;\n\t\t\toffset += string_width(e->title) + 10;\n\t\t\te++;\n\t\t\ti++;\n\t\t}\n\t} else {\n\t\tstruct menu_bar_entries * e = self->entries;\n\t\twhile (e->title) {\n\t\t\tif (e == _entries) break;\n\t\t\te++;\n\t\t\ti++;\n\t\t}\n\t}\n\n\tnew_menu->main_window = window;\n\tmenu_prepare(new_menu, yctx);\n\tyutani_window_move_relative(yctx, new_menu->window, window, offset, self->y + MENU_BAR_HEIGHT);\n\tyutani_flip(yctx, new_menu->window);\n\n\tself->active_menu = new_menu;\n\tself->active_menu->_bar = self;\n\tself->active_menu_wid = new_menu->window->wid;\n\tself->active_entry = _entries;\n\tself->active_entry_idx = i;\n\tif (self->redraw_callback) {\n\t\tself->redraw_callback(self);\n\t}\n}\n\nint menu_bar_mouse_event(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, struct yutani_msg_window_mouse_event * me, int x, int y) {\n\tif (x < self->x || x >= self->x + self->width || y < self->y || y >= self->y + 24 /* base height */) {\n\t\treturn 0;\n\t}\n\n\tint offset = self->x;\n\n\tstruct menu_bar_entries * _entries = self->entries;\n\n\twhile (_entries->title) {\n\t\tint w = string_width(_entries->title) + 11;\n\t\tif (x >= offset && x < offset + w) {\n\t\t\tif (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) {\n\t\t\t\tmenu_bar_show_menu(yctx, window, self,offset,_entries);\n\t\t\t} else if (self->active_menu && hashmap_has(menu_get_windows_hash(), (void*)(uintptr_t)self->active_menu_wid) && _entries != self->active_entry) {\n\t\t\t\tmenu_definitely_close(self->active_menu);\n\t\t\t\tmenu_bar_show_menu(yctx, window, self,offset,_entries);\n\t\t\t}\n\t\t}\n\n\t\toffset += w;\n\t\t_entries++;\n\t}\n\n\tif (x >= offset && me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\tyutani_window_drag_start(yctx, window);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "lib/panel_appmenu.c",
    "content": "/**\n * @brief Panel \"Applications\" menu widget\n */\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\n\nstatic struct MenuList * appmenu;\n\nstatic int widget_draw_appmenu(struct PanelWidget * this, gfx_context_t * ctx) {\n\tpanel_highlight_widget(this,ctx, !!appmenu->window);\n\ttt_set_size(this->pctx->font, 16);\n\tint w = tt_string_width(this->pctx->font, \"Applications\");\n\ttt_draw_string(ctx, this->pctx->font, (ctx->width - w) / 2, 20, \"Applications\", appmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal);\n\treturn 0;\n}\n\nstatic int widget_click_appmenu(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!appmenu->window) {\n\t\tpanel_menu_show(this,appmenu);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int widget_onkey_appmenu(struct PanelWidget * this, struct yutani_msg_key_event * ke) {\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t(ke->event.keycode == KEY_F1) &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\t\tpanel_menu_show(this,appmenu);\n\t}\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_appmenu(void) {\n\tappmenu = menu_set_get_root(menu_set_from_description(\"/etc/panel.menu\", launch_application_menu));\n\tappmenu->flags = MENU_FLAG_BUBBLE_CENTER;\n\n\t/* Bind Alt+F1 */\n\tyutani_key_bind(yctx, KEY_F1, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);\n\n\tstruct PanelWidget * widget = widget_new();\n\n\twidget->width = 130;\n\twidget->draw = widget_draw_appmenu;\n\twidget->click = widget_click_appmenu;\n\twidget->onkey = widget_onkey_appmenu;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_clock.c",
    "content": "/**\n * @brief Panel clock widget\n */\n#include <time.h>\n#include <math.h>\n#include <sys/time.h>\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\nstatic struct MenuList * clockmenu;\n\nstatic void watch_draw_line(gfx_context_t * ctx, int offset, double r, double ir, double a, double b, uint32_t color, float thickness) {\n\tdouble theta = (a / b) * 2.0 * M_PI;\n\n\tstruct gfx_point v = {74.0 + sin(theta) * ir, 70.0 + offset - cos(theta) * ir};\n\tstruct gfx_point w = {74.0 + sin(theta) * r,  70.0 + offset - cos(theta) * r};\n\n\tdraw_line_aa_points(ctx,&v,&w,color,thickness);\n}\n\nstatic double tick(double t) {\n\tdouble ts = t*t;\n\tdouble tc = ts*t;\n\treturn (0.5*tc*ts + -8.0*ts*ts + 20.0*tc + -19.0*ts + 7.5*t);\n}\n\nvoid _menu_draw_MenuEntry_Clock(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\tself->offset = offset;\n\n\t/* Draw the background */\n\tdraw_rounded_rectangle(ctx, 4, offset, 140, 140, 70, rgb(0,0,0));\n\tdraw_rounded_rectangle(ctx, 6, offset + 2, 136, 136, 68, rgb(255,255,255));\n\n\tfor (int i = 0; i < 60; ++i) {\n\t\twatch_draw_line(ctx, offset, 68, i % 5 ? 65 : 60, i, 60, rgb(0,0,0), i % 5 ? 0.3 : 1.0);\n\t}\n\n\tstatic const char * digits[] = {\"12\",\"1\",\"2\",\"3\",\"4\",\"5\",\"6\",\"7\",\"8\",\"9\",\"10\",\"11\"};\n\n\tstruct TT_Font * font = ((struct PanelWidget*)self->_private)->pctx->font;\n\ttt_set_size(font, 12);\n\tfor (int i = 0; i < 12; ++i) {\n\t\tint w = tt_string_width(font, digits[i]);\n\t\tdouble theta = (i / 12.0) * 2.0 * M_PI;\n\t\tdouble x = 74.0 + sin(theta) * 50.0;\n\t\tdouble y = 70.0 + offset - cos(theta) * 50.0;\n\n\t\tint _x = x - w / 2;\n\t\tint _y = y + 6;\n\n\t\ttt_draw_string(ctx, font, _x, _y, digits[i], rgb(0,0,0));\n\t}\n\n\tstruct timeval now;\n\tstruct tm * timeinfo;\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\tdouble sec = timeinfo->tm_sec + tick((double)now.tv_usec / 1000000.0) - 1.0;\n\tdouble min = timeinfo->tm_min + sec / 60.0;\n\tdouble hour = (timeinfo->tm_hour % 12) + min / 60.0;\n\n\twatch_draw_line(ctx, offset, 40, 0, hour, 12, rgb(0,0,0), 2.0);\n\twatch_draw_line(ctx, offset, 60, 0, min, 60, rgb(0,0,0), 1.5);\n\twatch_draw_line(ctx, offset, 65, -12, sec, 60, rgb(240,0,0), 0.5);\n\twatch_draw_line(ctx, offset, -4, -8, sec, 60, rgb(240,0,0), 2.0);\n\n}\n\nstatic struct MenuEntryVTable clock_vtable = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Clock,\n};\n\nstruct MenuEntry * menu_create_clock(struct PanelWidget * this) {\n\tstruct MenuEntry * out = menu_create_separator(); /* Steal some defaults */\n\n\tout->_type = -1; /* Special */\n\tout->height = 140;\n\tout->rwidth = 148;\n\tout->vtable = &clock_vtable;\n\tout->_private = this;\n\treturn out;\n}\n\nstatic int widget_draw_clock(struct PanelWidget * this, gfx_context_t * ctx) {\n\tstruct timeval now;\n\tstruct tm * timeinfo;\n\n\tpanel_highlight_widget(this,ctx,!!clockmenu->window);\n\n\tstruct TT_Font * font = this->pctx->font;\n\n\t/* Get the current time for the clock */\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\t/* Hours : Minutes : Seconds */\n\tchar time[80];\n\tstrftime(time, 80, \"%H:%M:%S\", timeinfo);\n\ttt_set_size(font, 16);\n\tint w = tt_string_width(font, time);\n\ttt_draw_string(ctx, font, (ctx->width - w) / 2, 20, time, clockmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal);\n\n\treturn 0;\n}\n\nstatic int widget_click_clock(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!clockmenu->window) {\n\t\tpanel_menu_show(this,clockmenu);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int widget_update_clock(struct PanelWidget * this, int * force_updates) {\n\tif (clockmenu && clockmenu->window) {\n\t\tmenu_force_redraw(clockmenu);\n\t\t*force_updates = 1;\n\t}\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_clock(void) {\n\tstruct PanelWidget * widget = widget_new();\n\n\tclockmenu = menu_create();\n\tclockmenu->flags |= MENU_FLAG_BUBBLE_RIGHT;\n\tmenu_insert(clockmenu, menu_create_clock(widget));\n\n\twidget->width = 90; /* TODO what */\n\twidget->draw = widget_draw_clock;\n\twidget->click = widget_click_clock;\n\twidget->update = widget_update_clock;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n"
  },
  {
    "path": "lib/panel_date.c",
    "content": "/**\n * @brief Panel date widget\n */\n#include <time.h>\n#include <sys/time.h>\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\nstatic struct MenuList * calmenu;\nstatic int date_widget_width = 48;\n#define CALENDAR_LINE_HEIGHT 22\n#define CALENDAR_BASE_HEIGHT 45\n#define CALENDAR_PAD_HEIGHT  2\n\nstatic int days_in_month(struct tm * timeinfo) {\n\tstatic int days_in_months[] = {\n\t\t31, 0, 31, 30, 31, 30, 31,\n\t\t31, 30, 31, 30, 31,\n\t};\n\tif (timeinfo->tm_mon != 1) return days_in_months[timeinfo->tm_mon];\n\t/* How many days in February? */\n\tstruct tm tmp;\n\tmemcpy(&tmp, timeinfo, sizeof(struct tm));\n\ttmp.tm_mday = 29;\n\ttmp.tm_hour = 12;\n\ttime_t tmp3 = mktime(&tmp);\n\tstruct tm * tmp2 = localtime(&tmp3);\n\treturn tmp2->tm_mday == 29 ? 29 : 28;\n}\n\nstatic int weeks_in_month(struct tm * timeinfo) {\n\tint line = 0;\n\tint wday = (36 + timeinfo->tm_wday - timeinfo->tm_mday) % 7;\n\tfor (int day = 1; day <= days_in_month(timeinfo); day++, (wday = (wday + 1) % 7)) {\n\t\tif (wday == 6) {\n\t\t\tline++;\n\t\t}\n\t}\n\treturn (wday ? line + 1 : line);\n}\n\nvoid _menu_draw_MenuEntry_Calendar(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\tself->offset = offset;\n\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\n\tstruct tm actual;\n\tstruct tm * timeinfo;\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\tmemcpy(&actual, timeinfo, sizeof(struct tm));\n\ttimeinfo = &actual;\n\n\tstruct TT_Font * font = ((struct PanelWidget*)self->_private)->pctx->font;\n\tstruct TT_Font * font_bold = ((struct PanelWidget*)self->_private)->pctx->font_bold;\n\n\t/* Render heading with Month Year */\n\t{\n\t\tchar month[20];\n\t\tstrftime(month, 20, \"%B %Y\", timeinfo);\n\n\t\ttt_set_size(font_bold, 16);\n\t\ttt_draw_string(ctx, font_bold, (self->width - tt_string_width(font_bold, month)) / 2, self->offset + 16, month, rgb(0,0,0));\n\t}\n\n\t/* Get ready to draw a table... */\n\tint cell_size = self->width / 7;\n\tint base_left = (self->width - cell_size * 7) / 2;\n\n\t/* Render weekday abbreviations */\n\tconst char * weekdays[] = {\"Su\",\"Mo\",\"Tu\",\"We\",\"Th\",\"Fr\",\"Sa\",NULL};\n\tint left = base_left;\n\ttt_set_size(font, 11);\n\tfor (const char ** w = weekdays; *w; w++) {\n\t\ttt_draw_string(ctx, font, left + (cell_size - tt_string_width(font,*w)) / 2,\n\t\t\tself->offset + 22 + 13, *w, rgb(0,0,0));\n\t\tleft += cell_size;\n\t}\n\n\tint weeks = weeks_in_month(timeinfo);\n\tself->height = CALENDAR_LINE_HEIGHT * weeks + CALENDAR_BASE_HEIGHT + CALENDAR_PAD_HEIGHT;\n\n\t/* The 1st was a... */\n\tint wday = (36 + timeinfo->tm_wday - timeinfo->tm_mday) % 7;\n\n\tint line = 0;\n\tleft = base_left + cell_size * wday;\n\ttt_set_size(font, 13);\n\tfor (int day = 1; day <= days_in_month(timeinfo); day++, (wday = (wday + 1) % 7)) {\n\t\tchar date[12];\n\t\tsnprintf(date, 11, \"%d\", day);\n\t\t/* Is this the cell for today? */\n\t\tif (day == timeinfo->tm_mday) {\n\t\t\tdraw_rounded_rectangle(ctx, left - 1, self->offset + CALENDAR_BASE_HEIGHT + line * CALENDAR_LINE_HEIGHT - 2, cell_size + 2, CALENDAR_LINE_HEIGHT, 12, ((struct PanelWidget*)self->_private)->pctx->color_special);\n\t\t\ttt_draw_string(ctx, font, left + (cell_size - tt_string_width(font, date)) / 2,\n\t\t\t\tself->offset + CALENDAR_BASE_HEIGHT + 13 + line * CALENDAR_LINE_HEIGHT, date, rgb(255,255,255));\n\t\t} else {\n\t\t\ttt_draw_string(ctx, font, left + (cell_size - tt_string_width(font, date)) / 2,\n\t\t\t\tself->offset + CALENDAR_BASE_HEIGHT + 13 + line * CALENDAR_LINE_HEIGHT, date, (wday == 0 || wday == 6) ? rgba(0,0,0,120) : rgb(0,0,0));\n\t\t}\n\t\tif (wday == 6) {\n\t\t\tleft = base_left;\n\t\t\tline++;\n\t\t} else {\n\t\t\tleft += cell_size;\n\t\t}\n\t}\n}\n\nstatic struct MenuEntryVTable calendar_vtable = {\n\t.methods = 3,\n\t.renderer = _menu_draw_MenuEntry_Calendar,\n};\n\n/*\n * Special menu entry to display a calendar\n */\nstruct MenuEntry * menu_create_calendar(struct PanelWidget * this) {\n\tstruct MenuEntry * out = menu_create_separator(); /* Steal some defaults */\n\n\tout->_type = -1; /* Special */\n\n\tstruct timeval now;\n\tgettimeofday(&now, NULL);\n\tout->height = CALENDAR_LINE_HEIGHT * weeks_in_month(localtime((time_t *)&now.tv_sec)) + CALENDAR_BASE_HEIGHT + CALENDAR_PAD_HEIGHT;\n\n\tout->rwidth = 200;\n\tout->vtable = &calendar_vtable;\n\tout->_private = this;\n\treturn out;\n}\n\n\nstatic int weekday_width, date_width;\nstatic char weekday[80], date[80];\n\nstatic void update_date_widget(struct PanelWidget * this) {\n\tstruct timeval now;\n\tstruct tm * timeinfo;\n\n\tstruct TT_Font * font = this->pctx->font;\n\tstruct TT_Font * font_bold = this->pctx->font_bold;\n\n\t/* Get the current time for the clock */\n\tgettimeofday(&now, NULL);\n\ttimeinfo = localtime((time_t *)&now.tv_sec);\n\n\tstrftime(weekday, 80, \"%A\", timeinfo);\n\tstrftime(date, 80, \"%B %e\", timeinfo);\n\n\ttt_set_size(font, 11);\n\ttt_set_size(font_bold, 11);\n\n\t/* Update date_widget_width */\n\tweekday_width = tt_string_width(font, weekday);\n\tdate_width = tt_string_width(font_bold, date);\n\n\t/* Uh, we need to calculate this elsewhere */\n\tdate_widget_width = (weekday_width > date_width ? weekday_width : date_width) + 24; /* A bit of padding... */\n}\n\nstatic int widget_draw_date(struct PanelWidget * this, gfx_context_t * ctx) {\n\tupdate_date_widget(this);\n\n\tpanel_highlight_widget(this,ctx,!!calmenu->window);\n\n\tstruct TT_Font * font = this->pctx->font;\n\tstruct TT_Font * font_bold = this->pctx->font_bold;\n\n\t/* Day-of-week */\n\tint t = (this->width - weekday_width) / 2;\n\ttt_draw_string(ctx, font, t, 13, weekday,  calmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal);\n\n\t/* Month Day */\n\tt = (this->width - date_width) / 2;\n\ttt_draw_string(ctx, font_bold, t, 23, date,  calmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal);\n\n\treturn 0;\n}\n\nstatic int widget_click_date(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!calmenu->window) {\n\t\tpanel_menu_show(this,calmenu);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstatic int widget_update_date(struct PanelWidget * this, int * force_updates) {\n\tint width_before = date_widget_width;\n\tupdate_date_widget(this);\n\tthis->width = date_widget_width;\n\treturn width_before != date_widget_width;\n}\n\nstruct PanelWidget * widget_init_date(void) {\n\tstruct PanelWidget * widget = widget_new();\n\n\tcalmenu = menu_create();\n\tcalmenu->flags |= MENU_FLAG_BUBBLE_CENTER;\n\tmenu_insert(calmenu, menu_create_calendar(widget));\n\n\twidget->width = 92; /* TODO calculate correct width */\n\twidget->draw = widget_draw_date;\n\twidget->click = widget_click_date;\n\twidget->update = widget_update_date;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_logout.c",
    "content": "/**\n * @brief Panel Logout Widget\n *\n * Shows a button that presents a menu with the option to log out.\n */\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\nstatic struct MenuList * logout_menu;\nstatic sprite_t * sprite_logout;\n\nstatic int widget_draw_logout(struct PanelWidget * this, gfx_context_t * ctx) {\n\tpanel_highlight_widget(this,ctx,!!logout_menu->window);\n\tdraw_sprite_alpha_paint(ctx, sprite_logout, (ctx->width - sprite_logout->width) / 2, 2, 1.0, (logout_menu->window ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal)); /* Logout button */\n\treturn 0;\n}\n\nstatic int widget_click_logout(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!logout_menu->window) {\n\t\tpanel_menu_show(this,logout_menu);\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_logout(void) {\n\tsprite_logout = malloc(sizeof(sprite_t));\n\tload_sprite(sprite_logout, \"/usr/share/icons/panel-shutdown.png\");\n\tlogout_menu = menu_create();\n\tlogout_menu->flags |= MENU_FLAG_BUBBLE_RIGHT;\n\tmenu_insert(logout_menu, menu_create_normal(\"exit\", \"log-out\", \"Log Out\", launch_application_menu));\n\n\tstruct PanelWidget * widget = widget_new();\n\n\twidget->width = sprite_logout->width + widget->pctx->extra_widget_spacing;\n\twidget->draw = widget_draw_logout;\n\twidget->click = widget_click_logout;\n\tlist_insert(widgets_enabled, widget);\n\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_network.c",
    "content": "/**\n * @brief Panel Network Status Widget\n */\n#include <fcntl.h>\n#include <stdint.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <sys/ioctl.h>\n#include <sys/socket.h>\n#include <net/if.h>\n#include <arpa/inet.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\nstatic int netstat_count = 0;\nstatic int netstat_prev[32] = {0};\nstatic char netstat_data[32][1024];\nstatic struct MenuList * netstat;\nstatic int network_status = 0;\nstatic sprite_t * sprite_net_active;\nstatic sprite_t * sprite_net_disabled;\n\nstatic void ip_ntoa(const uint32_t src_addr, char * out) {\n\tsnprintf(out, 16, \"%d.%d.%d.%d\",\n\t\t(src_addr & 0xFF000000) >> 24,\n\t\t(src_addr & 0xFF0000) >> 16,\n\t\t(src_addr & 0xFF00) >> 8,\n\t\t(src_addr & 0xFF));\n}\n\nstatic void netif_show_toast(const char * str) {\n\tint toastDaemon = open(\"/dev/pex/toast\", O_WRONLY);\n\tif (toastDaemon >= 0) {\n\t\twrite(toastDaemon, str, strlen(str));\n\t\tclose(toastDaemon);\n\t}\n}\n\nstatic void netif_disconnected(const char * if_name) {\n\tnetwork_status |= 1;\n\tif (netstat_prev[netstat_count] && netstat_prev[netstat_count] != 1) {\n\t\tchar toastMsg[1024];\n\t\tsprintf(toastMsg, \"{\\\"icon\\\":\\\"/usr/share/icons/48/network-jack-disconnected.png\\\",\\\"body\\\":\\\"<b>%s</b><br>Network disconnected.\\\"}\", if_name);\n\t\tnetif_show_toast(toastMsg);\n\t}\n\tnetstat_prev[netstat_count] = 1;\n}\n\nstatic void netif_connected(const char * if_name) {\n\tnetwork_status |= 2;\n\tif (netstat_prev[netstat_count] && netstat_prev[netstat_count] != 2) {\n\t\tchar toastMsg[1024];\n\t\tsprintf(toastMsg, \"{\\\"icon\\\":\\\"/usr/share/icons/48/network-jack.png\\\",\\\"body\\\":\\\"<b>%s</b><br>Connection established.\\\"}\", if_name);\n\t\tnetif_show_toast(toastMsg);\n\t}\n\tnetstat_prev[netstat_count] = 2;\n}\n\nstatic void check_network(const char * if_name) {\n\tif (netstat_count >= 32) return;\n\n\tchar if_path[512];\n\tsnprintf(if_path, 511, \"/dev/net/%s\", if_name);\n\tint netdev = open(if_path, O_RDONLY);\n\n\tif (netdev < 0) return;\n\n\tuint32_t flags;\n\tif (!ioctl(netdev, SIOCGIFFLAGS, &flags)) {\n\t\tif (!(flags & IFF_UP)) {\n\t\t\tsnprintf(netstat_data[netstat_count], 1023, \"%s: disconnected\", if_name);\n\t\t\tnetif_disconnected(if_name);\n\t\t\tgoto _netif_next;\n\t\t}\n\t}\n\n\t/* Get IPv4 address */\n\tuint32_t ip_addr;\n\tif (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) {\n\t\tchar ip_str[16];\n\t\tip_ntoa(ntohl(ip_addr), ip_str);\n\t\tsnprintf(netstat_data[netstat_count], 1023, \"%s: %s\", if_name, ip_str);\n\t\tnetif_connected(if_name);\n\t} else {\n\t\tsnprintf(netstat_data[netstat_count], 1023, \"%s: No address\", if_name);\n\t\tnetif_disconnected(if_name);\n\t}\n\n_netif_next:\n\tclose(netdev);\n\tnetstat_count++;\n}\n\nstatic int widget_update_network(struct PanelWidget * this, int * force_updates) {\n\tnetwork_status = 0;\n\tnetstat_count = 0;\n\n\tDIR * d = opendir(\"/dev/net\");\n\tif (!d) return 1;\n\n\tstruct dirent * ent;\n\twhile ((ent = readdir(d))) {\n\t\tif (ent->d_name[0] == '.') continue;\n\t\tif (!strcmp(ent->d_name, \"lo\")) continue; /* Ignore loopback */\n\t\tcheck_network(ent->d_name);\n\t}\n\n\tclosedir(d);\n\treturn 0;\n}\n\nstatic int widget_click_network(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!netstat) {\n\t\tnetstat = menu_create();\n\t\tnetstat->flags |= MENU_FLAG_BUBBLE_LEFT;\n\t\tmenu_insert(netstat, menu_create_normal(NULL, NULL, \"<b>Network Status</b>\", NULL));\n\t\tmenu_insert(netstat, menu_create_separator());\n\t}\n\twhile (netstat->entries->length > 2) {\n\t\tnode_t * node = list_pop(netstat->entries);\n\t\tmenu_free_entry((struct MenuEntry *)node->value);\n\t\tfree(node);\n\t}\n\tif (!network_status) {\n\t\tmenu_insert(netstat, menu_create_normal(NULL, NULL, \"No network.\", NULL));\n\t} else {\n\t\tfor (int i = 0; i < netstat_count; ++i) {\n\t\t\tmenu_insert(netstat, menu_create_normal(NULL, NULL, netstat_data[i], NULL));\n\t\t}\n\t}\n\tif (!netstat->window) {\n\t\tpanel_menu_show(this,netstat);\n\t}\n\n\treturn 1;\n}\n\nstatic int widget_draw_network(struct PanelWidget * this, gfx_context_t * ctx) {\n\tuint32_t color = (netstat && netstat->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal;\n\tpanel_highlight_widget(this,ctx,(netstat && netstat->window));\n\tif (network_status & 2) {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_net_active, (ctx->width - sprite_net_active->width) / 2, 1, 1.0, color);\n\t} else {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_net_disabled, (ctx->width - sprite_net_disabled->width) / 2, 1, 1.0, color);\n\t}\n\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_network(void) {\n\tsprite_net_active = malloc(sizeof(sprite_t));\n\tload_sprite(sprite_net_active, \"/usr/share/icons/24/net-active.png\");\n\tsprite_net_disabled = malloc(sizeof(sprite_t));\n\tload_sprite(sprite_net_disabled, \"/usr/share/icons/24/net-disconnected.png\");\n\n\tstruct PanelWidget * widget = widget_new();\n\twidget->width = sprite_net_active->width + widget->pctx->extra_widget_spacing;\n\twidget->draw = widget_draw_network;\n\twidget->click = widget_click_network;\n\twidget->update = widget_update_network;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_volume.c",
    "content": "/**\n * @brief Panel Volume Widget\n *\n * Shows an icon indicating the mixer's master volume,\n * and shows a menu with a volume slider when clicked.\n */\n#include <fcntl.h>\n#include <sys/ioctl.h>\n#include <kernel/mod/sound.h>\n\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\n\n#define VOLUME_DEVICE_ID 0\n#define VOLUME_KNOB_ID   0\n\nstatic sprite_t * sprite_volume_mute;\nstatic sprite_t * sprite_volume_low;\nstatic sprite_t * sprite_volume_med;\nstatic sprite_t * sprite_volume_high;\nstatic struct MenuList * volume_menu;\n\nstatic long volume_level = 0;\nstatic int mixer = -1;\n\nstatic int widget_update_volume(struct PanelWidget * this, int * force_updates) {\n\tif (mixer == -1) {\n\t\tmixer = open(\"/dev/mixer\", O_RDONLY);\n\t}\n\n\tsnd_knob_value_t value = {0};\n\tvalue.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */\n\tvalue.id     = VOLUME_KNOB_ID;   /* TODO this too */\n\n\tioctl(mixer, SND_MIXER_READ_KNOB, &value);\n\tvolume_level = value.val;\n\n\treturn 0;\n}\n\nstatic void set_volume(void) {\n\tsnd_knob_value_t value = {0};\n\tvalue.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */\n\tvalue.id     = VOLUME_KNOB_ID;   /* TODO this too */\n\tvalue.val    = volume_level;\n\n\tioctl(mixer, SND_MIXER_WRITE_KNOB, &value);\n\tredraw();\n}\n\nstatic void volume_raise(void) {\n\tvolume_level += 0x10000000;\n\tif (volume_level > 0xF0000000) volume_level = 0xFC000000;\n\tset_volume();\n}\n\nstatic void volume_lower(void) {\n\tvolume_level -= 0x10000000;\n\tif (volume_level < 0x0) volume_level = 0x0;\n\tset_volume();\n}\n\n#define VOLUME_SLIDER_LEFT_PAD  38\n#define VOLUME_SLIDER_RIGHT_PAD  14\n#define VOLUME_SLIDER_PAD (VOLUME_SLIDER_LEFT_PAD + VOLUME_SLIDER_RIGHT_PAD)\n#define VOLUME_SLIDER_VERT_PAD   10\n#define VOLUME_SLIDER_BALL_RADIUS 8\n\nstruct SliderStuff {\n\tint level;\n\tuint32_t on;\n\tuint32_t off;\n};\n\nuint32_t volume_pattern(int32_t x, int32_t y, double alpha, void * extra) {\n\tstruct SliderStuff * stuff = extra;\n\tif (alpha > 1.0) alpha = 1.0;\n\tif (alpha < 0.0) alpha = 0.0;\n\tuint32_t color = stuff->off;\n\tif (x < stuff->level + VOLUME_SLIDER_LEFT_PAD) {\n\t\tcolor = stuff->on;\n\t}\n\tcolor |= rgba(0,0,0,alpha*255);\n\treturn premultiply(color);\n}\n\nvoid _menu_draw_MenuEntry_Slider(gfx_context_t * ctx, struct MenuEntry * self, int offset) {\n\tself->offset = offset;\n\n\tdraw_sprite_alpha_paint(ctx, sprite_volume_high, 4, offset, 1.0, rgb(0,0,0));\n\n\tstruct SliderStuff stuff;\n\tstuff.level = (ctx->width - VOLUME_SLIDER_PAD) * (float)volume_level / (float)0xFC000000;\n\tstuff.on  = rgba(0,120,220,0);\n\tstuff.off = rgba(140,140,140,0);\n\tdraw_rounded_rectangle_pattern(ctx,\n\t\t/* x */ VOLUME_SLIDER_LEFT_PAD - 4,\n\t\t/* y */ offset + VOLUME_SLIDER_VERT_PAD - 1,\n\t\t/* w */ ctx->width - VOLUME_SLIDER_PAD + 8,\n\t\t/* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD + 2, 6, volume_pattern, &stuff);\n\tstuff.on  = rgba(40,160,255,0);\n\tstuff.off = rgba(200,200,200,0);\n\tdraw_rounded_rectangle_pattern(ctx,\n\t\t/* x */ VOLUME_SLIDER_LEFT_PAD - 3,\n\t\t/* y */ offset + VOLUME_SLIDER_VERT_PAD,\n\t\t/* w */ ctx->width - VOLUME_SLIDER_PAD + 6,\n\t\t/* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD, 5, volume_pattern, &stuff);\n\n\tdraw_rounded_rectangle(ctx,\n\t\t/* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + VOLUME_SLIDER_LEFT_PAD,\n\t\t/* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS,\n\t\t/* w */ VOLUME_SLIDER_BALL_RADIUS * 2,\n\t\t/* h */ VOLUME_SLIDER_BALL_RADIUS * 2, VOLUME_SLIDER_BALL_RADIUS, rgb(140,140,140));\n\tdraw_rounded_rectangle(ctx,\n\t\t/* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + 1 + VOLUME_SLIDER_LEFT_PAD,\n\t\t/* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS + 1,\n\t\t/* w */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2,\n\t\t/* h */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2, VOLUME_SLIDER_BALL_RADIUS - 1, rgb(220,220,220));\n}\n\nint _menu_mouse_MenuEntry_Slider(struct MenuEntry * self, struct yutani_msg_window_mouse_event * event) {\n\tif (event->buttons & YUTANI_MOUSE_BUTTON_LEFT) {\n\t\t/* Figure out where it is */\n\t\tfloat level = (float)(event->new_x - VOLUME_SLIDER_LEFT_PAD) / (float)(self->width - VOLUME_SLIDER_PAD);\n\t\tif (level >= 1.0) level = 1.0;\n\t\tif (level <= 0.0) level = 0.0;\n\t\tif (volume_level != level * 0xFC000000) {\n\t\t\tvolume_level = level * 0xFC000000;\n\t\t\tset_volume();\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic struct MenuEntryVTable slider_vtable = {\n\t.methods = 4,\n\t.renderer = _menu_draw_MenuEntry_Slider,\n\t.mouse_event = _menu_mouse_MenuEntry_Slider,\n};\n\nstruct MenuEntry * menu_create_slider(void) {\n\tstruct MenuEntry * out = menu_create_separator(); /* Steal some defaults */\n\tout->_type = -1; /* Special */\n\tout->height = 24;\n\tout->rwidth = 200;\n\tout->vtable = &slider_vtable;\n\treturn out;\n}\n\n\nstatic int widget_click_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!volume_menu) {\n\t\tvolume_menu = menu_create();\n\t\tvolume_menu->flags |= MENU_FLAG_BUBBLE_LEFT;\n\t}\n\n\t/* Clear the menu */\n\twhile (volume_menu->entries->length) {\n\t\tnode_t * node = list_pop(volume_menu->entries);\n\t\tmenu_free_entry((struct MenuEntry *)node->value);\n\t\tfree(node);\n\t}\n\n\tmenu_insert(volume_menu, menu_create_slider());\n\n\t/* TODO Our mixer supports multiple knobs and we could show all of them. */\n\t/* TODO We could also show a nice slider... if we had one... */\n\n\tif (!volume_menu->window) {\n\t\tpanel_menu_show(this, volume_menu);\n\t}\n\n\treturn 1;\n}\n\n\nstatic int widget_draw_volume(struct PanelWidget * this, gfx_context_t * ctx) {\n\tuint32_t color = (volume_menu && volume_menu->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal;\n\n\tpanel_highlight_widget(this,ctx,(volume_menu && volume_menu->window));\n\n\tif (volume_level < 10) {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_volume_mute, (ctx->width - sprite_volume_mute->width) / 2, 1, 1.0, color);\n\t} else if (volume_level < 0x547ae147) {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_volume_low,  (ctx->width - sprite_volume_low->width) / 2, 1, 1.0, color);\n\t} else if (volume_level < 0xa8f5c28e) {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_volume_med,  (ctx->width - sprite_volume_med->width) / 2, 1, 1.0, color);\n\t} else {\n\t\tdraw_sprite_alpha_paint(ctx, sprite_volume_high, (ctx->width - sprite_volume_high->width) / 2, 1, 1.0, color);\n\t}\n\n\treturn 0;\n}\n\n/* For dumb legacy reasons, scroll wheel movement shows up here... */\nstatic int widget_move_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tint scroll_direction = 0;\n\tif (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1;\n\telse if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1;\n\n\tif (scroll_direction == 1) {\n\t\tvolume_lower();\n\t\treturn 1;\n\t} else if (scroll_direction == -1) {\n\t\tvolume_raise();\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_volume(void) {\n\tsprite_volume_mute = malloc(sizeof(sprite_t));\n\tsprite_volume_low  = malloc(sizeof(sprite_t));\n\tsprite_volume_med  = malloc(sizeof(sprite_t));\n\tsprite_volume_high = malloc(sizeof(sprite_t));\n\tload_sprite(sprite_volume_mute, \"/usr/share/icons/24/volume-mute.png\");\n\tload_sprite(sprite_volume_low,  \"/usr/share/icons/24/volume-low.png\");\n\tload_sprite(sprite_volume_med,  \"/usr/share/icons/24/volume-medium.png\");\n\tload_sprite(sprite_volume_high, \"/usr/share/icons/24/volume-full.png\");\n\n\tstruct PanelWidget * widget = widget_new();\n\twidget->width = sprite_volume_high->width + widget->pctx->extra_widget_spacing;\n\twidget->draw = widget_draw_volume;\n\twidget->click = widget_click_volume;\n\twidget->move  = widget_move_volume;\n\twidget->update = widget_update_volume;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_weather.c",
    "content": "/**\n * @brief Panel Weather Widget\n */\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\n\nstruct MenuList * weather;\n\nstatic struct MenuEntry_Normal * weather_title_entry;\nstatic struct MenuEntry_Normal * weather_updated_entry;\nstatic struct MenuEntry_Normal * weather_conditions_entry;\nstatic struct MenuEntry_Normal * weather_humidity_entry;\nstatic struct MenuEntry_Normal * weather_clouds_entry;\nstatic struct MenuEntry_Normal * weather_pressure_entry;\nstatic char * weather_title_str;\nstatic char * weather_updated_str;\nstatic char * weather_conditions_str;\nstatic char * weather_humidity_str;\nstatic char * weather_clouds_str;\nstatic char * weather_pressure_str;\nstatic char * weather_temp_str;\nstatic int weather_status_valid = 0;\nstatic hashmap_t * weather_icons = NULL;\nstatic sprite_t * weather_icon = NULL;\nstatic int widgets_weather_enabled = 0;\n\nstatic int widget_update_weather(struct PanelWidget * this, int * force_updates) {\n\tFILE * f = fopen(\"/tmp/weather-parsed.conf\",\"r\");\n\tif (!f) {\n\t\tweather_status_valid = 0;\n\t\tif (widgets_weather_enabled) {\n\t\t\twidgets_weather_enabled = 0;\n\t\t\tthis->width = 0;\n\t\t\treturn 1;\n\t\t}\n\t\treturn 0;\n\t}\n\n\tweather_status_valid = 1;\n\n\tif (weather_title_str) free(weather_title_str);\n\tif (weather_updated_str) free(weather_updated_str);\n\tif (weather_conditions_str) free(weather_conditions_str);\n\tif (weather_humidity_str) free(weather_humidity_str);\n\tif (weather_clouds_str) free(weather_clouds_str);\n\tif (weather_pressure_str) free(weather_pressure_str);\n\tif (weather_temp_str) free(weather_temp_str);\n\n\t/* read the entire status file */\n\tfseek(f, 0, SEEK_END);\n\tsize_t size = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\tchar * data = malloc(size + 1);\n\tfread(data, size, 1, f);\n\tdata[size] = 0;\n\tfclose(f);\n\n\t/* Find relevant pieces */\n\tchar * t = data;\n\tchar * temp = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * temp_r = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * conditions = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * icon = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * humidity = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * clouds = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * city = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * updated = t;\n\tt = strstr(t, \"\\n\"); *t = '\\0'; t++;\n\tchar * pressure = t;\n\tt = strstr(t, \"\\n\"); if (t) { *t = '\\0'; t++; }\n\n\tif (!weather_icons) {\n\t\tweather_icons = hashmap_create(10);\n\t}\n\n\tif (!hashmap_has(weather_icons, icon)) {\n\t\tsprite_t * tmp = malloc(sizeof(sprite_t));\n\t\tchar path[512];\n\t\tsprintf(path,\"/usr/share/icons/weather/%s.png\", icon);\n\t\tload_sprite(tmp, path);\n\t\thashmap_set(weather_icons, icon, tmp);\n\t}\n\n\tweather_icon = hashmap_get(weather_icons, icon);\n\n\tchar tmp[300];\n\tsprintf(tmp, \"Weather for <b>%s</b>\", city);\n\tweather_title_str = strdup(tmp);\n\tsprintf(tmp, \"<small><i>%s</i></small>\", updated);\n\tweather_updated_str = strdup(tmp);\n\tsprintf(tmp, \"<b>%s°</b> - %s\", temp, conditions);\n\tweather_conditions_str = strdup(tmp);\n\tsprintf(tmp, \"<b>Humidity:</b> %s%%\", humidity);\n\tweather_humidity_str = strdup(tmp);\n\tsprintf(tmp, \"<b>Clouds:</b> %s%%\", clouds);\n\tweather_clouds_str = strdup(tmp);\n\tsprintf(tmp, \"<b>Pressure:</b> %s hPa\", pressure);\n\tweather_pressure_str = strdup(tmp);\n\n\tsprintf(tmp, \"%s°\", temp_r);\n\tweather_temp_str = strdup(tmp);\n\n\tfree(data);\n\n\tif (!widgets_weather_enabled) {\n\t\twidgets_weather_enabled = 1;\n\t\tthis->width = 60;\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic void weather_refresh(struct MenuEntry * self) {\n\t(void)self;\n\tsystem(\"weather-tool &\");\n}\n\nstatic void weather_configure(struct MenuEntry * self) {\n\t(void)self;\n\tsystem(\"terminal sh -c \\\"sudo weather-configurator; weather-tool\\\" &\");\n}\n\nstatic int widget_click_weather(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tif (!weather) {\n\t\tweather = menu_create();\n\t\tweather->flags |= MENU_FLAG_BUBBLE_LEFT;\n\t\tweather_title_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_title_entry);\n\t\tweather_updated_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_updated_entry);\n\t\tmenu_insert(weather, menu_create_separator());\n\t\tweather_conditions_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_conditions_entry);\n\t\tweather_humidity_entry = (struct MenuEntry_Normal *)menu_create_normal(\"weather-humidity\", NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_humidity_entry);\n\t\tweather_clouds_entry = (struct MenuEntry_Normal *)menu_create_normal(\"weather-clouds\", NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_clouds_entry);\n\t\tweather_pressure_entry = (struct MenuEntry_Normal *)menu_create_normal(\"weather-pressure\", NULL, \"\", NULL);\n\t\tmenu_insert(weather, weather_pressure_entry);\n\t\tmenu_insert(weather, menu_create_separator());\n\t\tmenu_insert(weather, menu_create_normal(\"refresh\", NULL, \"Refresh...\", weather_refresh));\n\t\tmenu_insert(weather, menu_create_normal(\"config\", NULL, \"Configure...\", weather_configure));\n\t\tmenu_insert(weather, menu_create_separator());\n\t\tmenu_insert(weather, menu_create_normal(NULL, NULL, \"<small><i>Weather data provided by</i></small>\", NULL));\n\t\tmenu_insert(weather, menu_create_normal(NULL, NULL, \"<b>OpenWeather™</b>\", NULL));\n\t}\n\tif (weather_status_valid) {\n\t\tmenu_update_title(weather_title_entry, weather_title_str);\n\t\tmenu_update_title(weather_updated_entry, weather_updated_str);\n\t\tmenu_update_title(weather_conditions_entry, weather_conditions_str);\n\t\tmenu_update_title(weather_humidity_entry, weather_humidity_str);\n\t\tmenu_update_title(weather_clouds_entry, weather_clouds_str);\n\t\tmenu_update_title(weather_pressure_entry, weather_pressure_str);\n\t}\n\tif (!weather->window) {\n\t\tpanel_menu_show(this,weather);\n\t}\n\treturn 1;\n}\n\nstatic int widget_draw_weather(struct PanelWidget * this, gfx_context_t * ctx) {\n\tif (widgets_weather_enabled) {\n\t\tuint32_t color = (weather && weather->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal;\n\t\tpanel_highlight_widget(this,ctx, (weather && weather->window));\n\t\ttt_set_size(this->pctx->font, 12);\n\t\tint t = tt_string_width(this->pctx->font, weather_temp_str);\n\t\ttt_draw_string(ctx, this->pctx->font, 4 + 24 + (24 - t) / 2, 6 + 12, weather_temp_str, color);\n\t\tdraw_sprite_alpha_paint(ctx, weather_icon, 4, 1, 1.0, color);\n\t}\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_weather(void) {\n\tweather_refresh(NULL);\n\n\tstruct PanelWidget * widget = widget_new();\n\twidget->width = 0;\n\twidget->draw = widget_draw_weather;\n\twidget->click = widget_click_weather;\n\twidget->update = widget_update_weather;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/panel_windowlist.c",
    "content": "/**\n * @brief Panel window list widget\n */\n#include <toaru/yutani.h>\n#include <toaru/graphics.h>\n#include <toaru/menu.h>\n#include <toaru/text.h>\n#include <toaru/panel.h>\n#include <toaru/icon_cache.h>\n\n#define GRADIENT_HEIGHT 24\n#define GRADIENT_AT(y) premultiply(rgba(72, 167, 255, ((24-(y))*160)/24))\n#define MAX_TEXT_WIDTH 180\n#define MIN_TEXT_WIDTH 50\n#define TOTAL_CELL_WIDTH (title_width)\nstatic struct MenuList * window_menu;\nstatic int title_width = 0;\nstatic yutani_wid_t _window_menu_wid = 0;\nstatic int focused_app = -1;\n\nstatic void _window_menu_start_move(struct MenuEntry * self) {\n\tif (!_window_menu_wid)\n\t\treturn;\n\tyutani_focus_window(yctx, _window_menu_wid);\n\tyutani_window_drag_start_wid(yctx, _window_menu_wid);\n}\n\nstatic void _window_menu_start_maximize(struct MenuEntry * self) {\n\tif (!_window_menu_wid)\n\t\treturn;\n\tyutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_MAXIMIZE);\n\tyutani_focus_window(yctx, _window_menu_wid);\n}\n\nstatic void _window_menu_start_minimize(struct MenuEntry * self) {\n\tif (!_window_menu_wid)\n\t\treturn;\n\tyutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_MINIMIZE);\n}\n\nstatic void _window_menu_close(struct MenuEntry * self) {\n\tif (!_window_menu_wid)\n\t\treturn;\n\tyutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE);\n}\n\nstatic void window_show_menu(yutani_wid_t wid, int x) {\n\tif (window_menu->window) return;\n\t_window_menu_wid = wid;\n\tpanel_menu_show_at(window_menu, x);\n}\n\nstatic int widget_draw_windowlist(struct PanelWidget * this, gfx_context_t * ctx) {\n\tif (!window_list || window_list->length == 0) {\n\t\ttitle_width = 0;\n\t} else if (this->width <= 0) {\n\t\ttitle_width = 28;\n\t} else {\n\t\ttitle_width = this->width / window_list->length;\n\t\tif (title_width > MAX_TEXT_WIDTH) {\n\t\t\ttitle_width = MAX_TEXT_WIDTH;\n\t\t}\n\t\tif (title_width < MIN_TEXT_WIDTH) {\n\t\t\ttitle_width = 28;\n\t\t}\n\t}\n\n\tint i = 0, j = 0;\n\tif (window_list) {\n\t\tforeach(node, window_list) {\n\t\t\tstruct window_ad * ad = node->value;\n\t\t\tint w = title_width;\n\n\t\t\tif (i + w > this->width) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t/* Hilight the focused window */\n\t\t\tif (ad->flags & 1) {\n\t\t\t\t/* This is the focused window */\n\t\t\t\tfor (int y = 0; y < GRADIENT_HEIGHT; ++y) {\n\t\t\t\t\tfor (int x = i; x < i + w; ++x) {\n\t\t\t\t\t\tGFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), GRADIENT_AT(y));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tuint32_t text_color = this->pctx->color_text_normal;\n\t\t\tif (j == focused_app) text_color = this->pctx->color_text_hilighted;\n\t\t\telse if (ad->flags & 1) text_color = this->pctx->color_text_focused;\n\t\t\telse if (ad->flags & 2) text_color = premultiply(rgba(_RED(this->pctx->color_text_normal),_GRE(this->pctx->color_text_normal),_BLU(this->pctx->color_text_normal),127));\n\n\t\t\tif (title_width >= MIN_TEXT_WIDTH) {\n\t\t\t\t/* Ellipsifiy the title */\n\t\t\t\tchar * s = tt_ellipsify(ad->name, 14, this->pctx->font, title_width - 4, NULL);\n\t\t\t\tsprite_t * icon = icon_get_48(ad->icon);\n\t\t\t\tgfx_context_t * subctx = init_graphics_subregion(ctx, i, 0, w, ctx->height-1);\n\t\t\t\tdraw_sprite_scaled_alpha(subctx, icon, w - 48 - 2, 0, 48, 48, (ad->flags & 1) ? 1.0 : 0.7);\n\t\t\t\ttt_draw_string_shadow(subctx, this->pctx->font, s, 14, 2, 6, text_color, rgb(0,0,0), 4);\n\t\t\t\tfree(subctx);\n\t\t\t\tfree(s);\n\t\t\t} else {\n\t\t\t\tsprite_t * icon = icon_get_16(ad->icon);\n\t\t\t\tgfx_context_t * subctx = init_graphics_subregion(ctx, i, 0, w, ctx->height-1);\n\t\t\t\tdraw_sprite_scaled(subctx, icon, 6, 6, 16, 16);\n\t\t\t\tfree(subctx);\n\t\t\t}\n\n\t\t\tad->left = this->left + i;\n\n\t\t\tyutani_window_panel_size(yctx, ad->wid, ad->left + this->pctx->basewindow->x, this->pctx->basewindow->y, w, ctx->height);\n\n\t\t\tj++;\n\t\t\ti += w;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int widget_click_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tforeach(node, window_list) {\n\t\tstruct window_ad * ad = node->value;\n\t\tif (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) {\n\t\t\tyutani_focus_window(yctx, ad->wid);\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int widget_rightclick_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tforeach(node, window_list) {\n\t\tstruct window_ad * ad = node->value;\n\t\tif (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) {\n\t\t\twindow_show_menu(ad->wid, evt->new_x);\n\t\t}\n\t}\n\treturn 0;\n}\n\n/* Update the hover-focus window */\nstatic int set_focused(int i) {\n\tif (focused_app != i) {\n\t\tfocused_app = i;\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\n\nstatic int widget_move_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tint found = 0;\n\tint i = 0;\n\tint should_redraw = 0;\n\n\tforeach(node, window_list) {\n\t\tstruct window_ad * ad = node->value;\n\t\tif (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) {\n\t\t\tfound = 1;\n\t\t\tshould_redraw |= set_focused(i);\n\t\t\tbreak;\n\t\t}\n\t\ti++;\n\t}\n\n\tif (!found) {\n\t\tshould_redraw |= set_focused(-1);\n\t}\n\n\tint scroll_direction = 0;\n\tif (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1;\n\telse if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1;\n\n\tif (scroll_direction != 0) {\n\t\tstruct window_ad * last = window_list->tail ? window_list->tail->value : NULL;\n\t\tint focus_next = 0;\n\t\tforeach(node, window_list) {\n\t\t\tstruct window_ad * ad = node->value;\n\t\t\tif (focus_next) {\n\t\t\t\tyutani_focus_window(yctx, ad->wid);\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\tif (ad->flags & 1) {\n\t\t\t\tif (scroll_direction == -1) {\n\t\t\t\t\tyutani_focus_window(yctx, last->wid);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tif (scroll_direction == 1) {\n\t\t\t\t\tfocus_next = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlast = ad;\n\t\t}\n\t\tif (focus_next && window_list->head) {\n\t\t\tstruct window_ad * ad = window_list->head->value;\n\t\t\tyutani_focus_window(yctx, ad->wid);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn should_redraw;\n}\n\nstatic int widget_leave_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) {\n\tthis->highlighted = 0;\n\treturn set_focused(-1);\n}\n\nstatic int widget_onkey_windowlist(struct PanelWidget * this, struct yutani_msg_key_event * ke) {\n\tif ((ke->event.modifiers & KEY_MOD_LEFT_ALT) &&\n\t\t(ke->event.keycode == KEY_F3) &&\n\t\t(ke->event.action == KEY_ACTION_DOWN)) {\n\t\tforeach(node, window_list) {\n\t\t\tstruct window_ad * ad = node->value;\n\t\t\tif (ad->flags & 1) {\n\t\t\t\twindow_show_menu(ad->wid, ad->left + title_width / 2);\n\t\t\t}\n\t\t}\n\t}\n\treturn 0;\n}\n\nstruct PanelWidget * widget_init_windowlist(void) {\n\twindow_menu = menu_create();\n\twindow_menu->flags |= MENU_FLAG_BUBBLE_LEFT;\n\tmenu_insert(window_menu, menu_create_normal(NULL, NULL, \"Maximize\", _window_menu_start_maximize));\n\tmenu_insert(window_menu, menu_create_normal(NULL, NULL, \"Minimize\", _window_menu_start_minimize));\n\tmenu_insert(window_menu, menu_create_normal(NULL, NULL, \"Move\", _window_menu_start_move));\n\tmenu_insert(window_menu, menu_create_separator());\n\tmenu_insert(window_menu, menu_create_normal(NULL, NULL, \"Close\", _window_menu_close));\n\n\t/* Alt+F3 = window context menu */\n\tyutani_key_bind(yctx, KEY_F3, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL);\n\n\tstruct PanelWidget * widget = widget_new();\n\twidget->fill = 1;\n\twidget->draw = widget_draw_windowlist;\n\twidget->click = widget_click_windowlist;\n\twidget->right_click = widget_rightclick_windowlist;\n\twidget->move = widget_move_windowlist;\n\twidget->leave = widget_leave_windowlist;\n\twidget->onkey = widget_onkey_windowlist;\n\tlist_insert(widgets_enabled, widget);\n\treturn widget;\n}\n\n"
  },
  {
    "path": "lib/pex.c",
    "content": "/**\n * @brief pex - Packet EXchange client library\n *\n * Provides a friendly interface to the \"Packet Exchange\"\n * functionality provided by the packetfs kernel interface.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <alloca.h>\n#include <assert.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/ioctl.h>\n\n#include <toaru/pex.h>\n\nsize_t pex_send(FILE * sock, uintptr_t rcpt, size_t size, char * blob) {\n\tassert(size <= MAX_PACKET_SIZE);\n\tpex_header_t * broadcast = malloc(sizeof(pex_header_t) + size);\n\tbroadcast->target = rcpt;\n\tmemcpy(broadcast->data, blob, size);\n\tsize_t out = write(fileno(sock), broadcast, sizeof(pex_header_t) + size);\n\tfree(broadcast);\n\treturn out;\n}\n\nsize_t pex_broadcast(FILE * sock, size_t size, char * blob) {\n\treturn pex_send(sock, 0, size, blob);\n}\n\nsize_t pex_listen(FILE * sock, pex_packet_t * packet) {\n\treturn read(fileno(sock), packet, PACKET_SIZE);\n}\n\nsize_t pex_reply(FILE * sock, size_t size, char * blob) {\n\treturn write(fileno(sock), blob, size);\n}\n\nsize_t pex_recv(FILE * sock, char * blob) {\n\tmemset(blob, 0, MAX_PACKET_SIZE);\n\treturn read(fileno(sock), blob, MAX_PACKET_SIZE);\n}\n\nFILE * pex_connect(char * target) {\n\tchar tmp[100];\n\tif (strlen(target) > 80) return NULL;\n\tsprintf(tmp, \"/dev/pex/%s\", target);\n\tFILE * out = fopen(tmp, \"r+\");\n\tif (out) {\n\t\tsetbuf(out, NULL);\n\t}\n\treturn out;\n}\n\nFILE * pex_bind(char * target) {\n\tchar tmp[100];\n\tif (strlen(target) > 80) return NULL;\n\tsprintf(tmp, \"/dev/pex/%s\", target);\n\tint fd = open(tmp, O_CREAT | O_EXCL | O_RDWR | O_APPEND);\n\tif (fd < 0) {\n\t\treturn NULL;\n\t}\n\tFILE * out = fdopen(fd, \"a+\");\n\tif (out) {\n\t\tsetbuf(out, NULL);\n\t}\n\treturn out;\n}\n\nsize_t pex_query(FILE * sock) {\n\treturn ioctl(fileno(sock), IOCTL_PACKETFS_QUEUED, NULL);\n}\n"
  },
  {
    "path": "lib/png.c",
    "content": "/**\n * @brief PNG decoder.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2020 K. Lange\n */\n#include <stdio.h>\n#include <stdlib.h>\n\n#include <toaru/graphics.h>\n#include <toaru/inflate.h>\n\n/**\n * Read 32-bit big-endian value from file.\n */\nunsigned int read_32(FILE * f) {\n\tunsigned char a = fgetc(f);\n\tunsigned char b = fgetc(f);\n\tunsigned char c = fgetc(f);\n\tunsigned char d = fgetc(f);\n\treturn (a << 24) | (b << 16) | (c << 8) | d;\n}\n\n/**\n * Read 16-bit big-endian value from file.\n */\nunsigned int read_16(FILE * f) {\n\tunsigned char a = fgetc(f);\n\tunsigned char b = fgetc(f);\n\treturn (a << 8) | b;\n}\n\n/**\n * (Debug) Return a chunk type as a string.\n */\n__attribute__((unused))\nstatic char*  reorder_type(unsigned int type) {\n\tstatic char out[4];\n\tout[0] = (type >> 24) & 0xFF;\n\tout[1] = (type >> 16) & 0xFF;\n\tout[2] = (type >> 8)  & 0xFF;\n\tout[3] = (type >> 0)  & 0xFF;\n\treturn out;\n}\n\n/**\n * Internal PNG decoder state for use with inflate.\n */\nstruct png_ctx {\n\tFILE * f;          /* File being decoded. */\n\tsprite_t * sprite; /* Sprite being generated. */\n\tint y;             /* Cursor pointers for writing out bitmap data */\n\tint x;\n\tchar buffer[4];   /* A buffer to hold a pixel's worth of data until it can\n\t                     be written out to the image with the right filter. */\n\tint  buf_off;     /* How much data is in the above buffer */\n\tint seen_ihdr;    /* Whether the IHDR was seen; for error handling */\n\n\tunsigned int width;   /* Image width (dup from sprite) */\n\tunsigned int height;  /* Image height (dup from sprite) */\n\tint bit_depth;        /* Bit depth of the image */\n\tint color_type;       /* PNG color type */\n\tint compression;      /* Compression method (must be 0) */\n\tint filter;           /* Filter method (must be 0) */\n\tint interlace;        /* Interlace method (we only support 0) */\n\n\tunsigned int size;    /* Remaining IDAT chunk size */\n\tint sf;               /* Current scanline filter type */\n};\n\n/* PNG chunk types */\n#define PNG_IHDR 0x49484452\n#define PNG_IDAT 0x49444154\n#define PNG_IEND 0x49454e44\n\n/* PNG filter types */\n#define PNG_FILTER_NONE  0\n#define PNG_FILTER_SUB   1\n#define PNG_FILTER_UP    2\n#define PNG_FILTER_AVG   3\n#define PNG_FILTER_PAETH 4\n\n/**\n * Read a byte from the IDAT chunk.\n * Tracks when an IDAT has been read to completion and\n * can load the next IDAT (or bail of this was the last one)\n */\nstatic uint8_t _get(struct inflate_context * ctx) {\n\tstruct png_ctx * c = (ctx->input_priv);\n\tif (c->size == 0) {\n\n\t\t/* Read the CRC32 from the end of this IDAT */\n\t\tunsigned int check = read_32(c->f);\n\t\t(void)check; /* ... and in theory check it... */\n\n\t\t/* Read the next IDAT chunk header */\n\t\tunsigned int size = read_32(c->f);\n\t\tunsigned int type = read_32(c->f);\n\n\t\tc->size = size;\n\n\t\tif (type != PNG_IDAT) {\n\t\t\t/* This isn't an IDAT? That's wrong! */\n\t\t\tfprintf(stderr, \"And this is the wrong type (0x%x), I'm just bailing.\\n\", type);\n\t\t\tfprintf(stderr, \"size read was 0x%x\\n\", size);\n\t\t\texit(0);\n\t\t}\n\t}\n\n\t/* Read one byte from the input */\n\tc->size--;\n\tint i = fgetc(c->f);\n\n\t/* If this was EOF, we should handle that error case... probably... */\n\tif (i < 0) fprintf(stderr, \"This is probably not good.\\n\");\n\n\treturn i;\n}\n\n/**\n * Paeth predictor\n * Described in section 6.6 of the RFC\n */\n#define ABS(a) ((a >= 0) ? (a) : -(a))\nstatic int paeth(int a, int b, int c) {\n\tint p = a + b - c;\n\tint pa = ABS(p - a);\n\tint pb = ABS(p - b);\n\tint pc = ABS(p - c);\n\tif (pa <= pb && pa <= pc) return a;\n\telse if (pb <= pc) return b;\n\treturn c;\n}\n\nstatic void write_pixel(struct png_ctx * c, uint32_t color) {\n\tSPRITE((c->sprite), (c->x), (c->y)) = color;\n\n\t/* Reset the short buffer */\n\tc->buf_off = 0;\n\n\t/* Advance to next pixel */\n\tc->x++;\n\tif (c->x == (int)c->width) {\n\t\t/* Advance to next line; next read is scanline filter type */\n\t\tc->x = -1;\n\t\tc->y++;\n\t}\n}\n\nstatic void process_pixel_type_6(struct png_ctx * c) {\n\t/*\n\t * Obtain pixel data from short buffer;\n\t * For color type 6, this is always in R G B A order in the\n\t * bytestream, so we don't have to worry about subpixel ordering\n\t * or weird color masks.\n\t */\n\tunsigned int r = c->buffer[0];\n\tunsigned int g = c->buffer[1];\n\tunsigned int b = c->buffer[2];\n\tunsigned int a = c->buffer[3];\n\n\t/* Apply filters */\n\tif (c->sf == PNG_FILTER_SUB) {\n\t\t/* Add raw value to the pixel on the left */\n\t\tif (c->x > 0) {\n\t\t\tuint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y));\n\t\t\tr += _RED(left);\n\t\t\tg += _GRE(left);\n\t\t\tb += _BLU(left);\n\t\t\ta += _ALP(left);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_UP) {\n\t\t/* Add raw value to the pixel above */\n\t\tif (c->y > 0) {\n\t\t\tuint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1));\n\t\t\tr += _RED(up);\n\t\t\tg += _GRE(up);\n\t\t\tb += _BLU(up);\n\t\t\ta += _ALP(up);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_AVG) {\n\t\t/* Add raw value to the average of the pixel above and left */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\n\t\tr += ((int)_RED(left) + (int)_RED(up)) / 2;\n\t\tg += ((int)_GRE(left) + (int)_GRE(up)) / 2;\n\t\tb += ((int)_BLU(left) + (int)_BLU(up)) / 2;\n\t\ta += ((int)_ALP(left) + (int)_ALP(up)) / 2;\n\t} else if (c->sf == PNG_FILTER_PAETH) {\n\t\t/* Use the Paeth predictor */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\t\tuint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0;\n\n\t\tr = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256;\n\t\tg = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256;\n\t\tb = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256;\n\t\ta = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256;\n\t}\n\n\t/* Write new pixel to the image */\n\twrite_pixel(c, rgba(r,g,b,a));\n}\n\nstatic void process_pixel_type_2(struct png_ctx * c) {\n\t/*\n\t * Obtain pixel data from short buffer;\n\t * For color type 6, this is always in R G B A order in the\n\t * bytestream, so we don't have to worry about subpixel ordering\n\t * or weird color masks.\n\t */\n\tunsigned int r = c->buffer[0];\n\tunsigned int g = c->buffer[1];\n\tunsigned int b = c->buffer[2];\n\n\t/* Apply filters */\n\tif (c->sf == PNG_FILTER_SUB) {\n\t\t/* Add raw value to the pixel on the left */\n\t\tif (c->x > 0) {\n\t\t\tuint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y));\n\t\t\tr += _RED(left);\n\t\t\tg += _GRE(left);\n\t\t\tb += _BLU(left);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_UP) {\n\t\t/* Add raw value to the pixel above */\n\t\tif (c->y > 0) {\n\t\t\tuint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1));\n\t\t\tr += _RED(up);\n\t\t\tg += _GRE(up);\n\t\t\tb += _BLU(up);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_AVG) {\n\t\t/* Add raw value to the average of the pixel above and left */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\n\t\tr += ((int)_RED(left) + (int)_RED(up)) / 2;\n\t\tg += ((int)_GRE(left) + (int)_GRE(up)) / 2;\n\t\tb += ((int)_BLU(left) + (int)_BLU(up)) / 2;\n\t} else if (c->sf == PNG_FILTER_PAETH) {\n\t\t/* Use the Paeth predictor */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\t\tuint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0;\n\n\t\tr = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256;\n\t\tg = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256;\n\t\tb = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256;\n\t}\n\n\t/* Write new pixel to the image */\n\twrite_pixel(c, rgb(r,g,b));\n}\n\nstatic void process_pixel_type_4(struct png_ctx * c) {\n\t/*\n\t * Obtain pixel data from short buffer;\n\t * For color type 6, this is always in R G B A order in the\n\t * bytestream, so we don't have to worry about subpixel ordering\n\t * or weird color masks.\n\t */\n\tunsigned int b = c->buffer[0];\n\tunsigned int a = c->buffer[1];\n\n\t/* Apply filters */\n\tif (c->sf == PNG_FILTER_SUB) {\n\t\t/* Add raw value to the pixel on the left */\n\t\tif (c->x > 0) {\n\t\t\tuint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y));\n\t\t\tb += _BLU(left);\n\t\t\ta += _ALP(left);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_UP) {\n\t\t/* Add raw value to the pixel above */\n\t\tif (c->y > 0) {\n\t\t\tuint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1));\n\t\t\tb += _BLU(up);\n\t\t\ta += _ALP(up);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_AVG) {\n\t\t/* Add raw value to the average of the pixel above and left */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\n\t\tb += ((int)_BLU(left) + (int)_BLU(up)) / 2;\n\t\ta += ((int)_ALP(left) + (int)_ALP(up)) / 2;\n\t} else if (c->sf == PNG_FILTER_PAETH) {\n\t\t/* Use the Paeth predictor */\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\t\tuint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0;\n\n\t\tb = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256;\n\t\ta = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256;\n\t}\n\n\t/* Write new pixel to the image */\n\twrite_pixel(c, rgba(b,b,b,a));\n}\n\nstatic void process_pixel_type_0(struct png_ctx * c) {\n\tunsigned int b = c->buffer[0];\n\tif (c->sf == PNG_FILTER_SUB) {\n\t\tif (c->x > 0) {\n\t\t\tuint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y));\n\t\t\tb += _BLU(left);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_UP) {\n\t\tif (c->y > 0) {\n\t\t\tuint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1));\n\t\t\tb += _BLU(up);\n\t\t}\n\t} else if (c->sf == PNG_FILTER_AVG) {\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\t\tb += ((int)_BLU(left) + (int)_BLU(up)) / 2;\n\t} else if (c->sf == PNG_FILTER_PAETH) {\n\t\tuint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0;\n\t\tuint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0;\n\t\tuint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0;\n\t\tb = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256;\n\t}\n\n\t/* Write new pixel to the image */\n\twrite_pixel(c, rgb(b,b,b));\n}\n\n\n/**\n * Handle decompressed output from the inflater\n *\n * Writes pixel data to the image, and applies relevant filters.\n */\nstatic void _write(struct inflate_context * ctx, unsigned int sym) {\n\tstruct png_ctx * c = (ctx->input_priv);\n\n\t/* Put this byte into the short buffer */\n\tc->buffer[c->buf_off] = sym;\n\tc->buf_off++;\n\n\t/* If this is the beginning of a scanline... */\n\tif (c->x == -1 && c->buf_off == 1) {\n\t\t/* Then this is the scanline filter type */\n\t\tc->sf = sym;\n\n\t\t/* Reset the buffer, advance to the beginning of the actual scanline */\n\t\tc->x = 0;\n\t\tc->buf_off = 0;\n\t} else if (c->buf_off == 1 && c->color_type == 0) {\n\t\tprocess_pixel_type_0(c);\n\t} else if (c->buf_off == 2 && c->color_type == 4) {\n\t\tprocess_pixel_type_4(c);\n\t} else if (c->buf_off == 3 && c->color_type == 2) {\n\t\tprocess_pixel_type_2(c);\n\t} else if (c->buf_off == 4 && c->color_type == 6) {\n\t\tprocess_pixel_type_6(c);\n\t}\n}\n\nstatic int color_type_has_alpha(int c) {\n\tswitch (c) {\n\t\tcase 4:\n\t\tcase 6:\n\t\t\treturn ALPHA_EMBEDDED;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nint load_sprite_png(sprite_t * sprite, char * filename) {\n\tFILE * f = fopen(filename,\"r\");\n\tif (!f) {\n\t\tfprintf(stderr, \"Failed to open file %s\\n\", filename);\n\t\treturn 1;\n\t}\n\n\t/* Read the PNG signature */\n\tunsigned char sig[] = {137, 80, 78, 71, 13, 10, 26, 10};\n\tfor (int i = 0; i < 8; ++i) {\n\t\tunsigned char c = fgetc(f);\n\t\tif (c != sig[i]) {\n\t\t\tfprintf(stderr, \"byte %d (%d) does not match expected (%d)\\n\", i, c, sig[i]);\n\t\t\tgoto _error;\n\t\t}\n\t}\n\n\t/* Set up context for future calls to inflate */\n\tstruct png_ctx c;\n\tc.sprite = sprite;\n\tc.x = -1;\n\tc.y = 0;\n\tc.f = f;\n\tc.buf_off = 0;\n\tc.seen_ihdr = 0;\n\n\twhile (1) {\n\t\t/* read chunks */\n\t\tunsigned int size = read_32(f);\n\t\tunsigned int type = read_32(f);\n\n\t\tif (feof(f)) break;\n\n\t\tswitch (type) {\n\t\t\tcase PNG_IHDR:\n\t\t\t\t{\n\t\t\t\t\t/* Image should only have one IHDR */\n\t\t\t\t\tif (c.seen_ihdr) return 1;\n\n\t\t\t\t\tc.seen_ihdr = 1;\n\t\t\t\t\tc.width = read_32(f); /* 4 */\n\t\t\t\t\tc.height = read_32(f); /* 8 */\n\t\t\t\t\tc.bit_depth = fgetc(f); /* 9 */\n\t\t\t\t\tc.color_type = fgetc(f); /* 10 */\n\t\t\t\t\tc.compression = fgetc(f); /* 11 */\n\t\t\t\t\tc.filter = fgetc(f); /* 12 */\n\t\t\t\t\tc.interlace = fgetc(f); /* 13 */\n\n\t\t\t\t\t/* Invalid / non-standard compression and filter types */\n\t\t\t\t\tif (c.compression != 0) return 1;\n\t\t\t\t\tif (c.filter != 0) return 1;\n\n\t\t\t\t\t/* 0 for none, 1 for Adam7 */\n\t\t\t\t\tif (c.interlace != 0 && c.interlace != 1) return 1;\n\n\t\t\t\t\tif (c.bit_depth != 8) return 1; /* Sorry */\n\t\t\t\t\tif (c.color_type < 0 || c.color_type > 6 || (c.color_type & 1)) return 1; /* Sorry, no indexed support */\n\n\t\t\t\t\t/* Allocate space */\n\t\t\t\t\tsprite->width  = c.width;\n\t\t\t\t\tsprite->height = c.height;\n\t\t\t\t\tsprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height);\n\t\t\t\t\tsprite->masks = NULL;\n\t\t\t\t\tsprite->alpha = color_type_has_alpha(c.color_type);\n\t\t\t\t\tsprite->blank = 0;\n\n\n\t\t\t\t\t/* Skip */\n\t\t\t\t\tfor (unsigned int i = 13; i < size; ++i) fgetc(f);\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase PNG_IDAT:\n\t\t\t\t{\n\t\t\t\t\t/* First two bytes of IDAT data are ZLIB header */\n\t\t\t\t\tunsigned int cflags = fgetc(f);\n\t\t\t\t\tif ((cflags & 0xF) != 8) {\n\t\t\t\t\t\t/* Compression type must be 8 */\n\t\t\t\t\t\tfprintf(stderr, \"Expected flags to be 8 but it's 0x%x\\n\", cflags);\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t\tunsigned int aflags = fgetc(f);\n\t\t\t\t\tif (aflags & (1 << 5)) {\n\t\t\t\t\t\tfprintf(stderr, \"There are preset bytes and I don't know what to do.\\n\");\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\n\t\t\t\t\tstruct inflate_context ctx;\n\t\t\t\t\tctx.input_priv = &c;\n\t\t\t\t\tctx.output_priv = &c;\n\t\t\t\t\tctx.get_input = _get;\n\t\t\t\t\tctx.write_output = _write;\n\t\t\t\t\tctx.ring = NULL; /* use builtin */\n\n\t\t\t\t\tc.size = size - 2; /* 2 for the bytes we already read */\n\n\t\t\t\t\tdeflate_decompress(&ctx);\n\n\t\t\t\t\t/* The IDATs contain a ZLIB stream, so they end with an\n\t\t\t\t\t * adler32 checksum. Skip that. */\n\t\t\t\t\tunsigned int adler = read_32(f);\n\t\t\t\t\t(void)adler;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase PNG_IEND:\n\t\t\t\t/* We don't actually have anything to do here. */\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t/* IHDR must be first */\n\t\t\t\tif (!c.seen_ihdr) return 1;\n\t\t\t\t//fprintf(stderr, \"I don't know what this is! %4s 0x%x\\n\", reorder_type(type), type);\n\t\t\t\t/* Skip */\n\t\t\t\tfor (unsigned int i = 0; i < size; ++i) fgetc(f);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tunsigned int crc32 = read_32(f);\n\t\t(void)crc32;\n\t}\n\n\t/*\n\t * Data in PNGs is unpremultiplied, but our sprites expect\n\t * premultiplied alpha, so convert the image data\n\t */\n\tfor (int y = 0; y < sprite->height; ++y) {\n\t\tfor (int x = 0; x < sprite->width; ++x) {\n\t\t\tSPRITE(sprite,x,y) = premultiply(SPRITE(sprite,x,y));\n\t\t}\n\t}\n\n\tfclose(f);\n\treturn 0;\n\n_error:\n\tfclose(f);\n\treturn 1;\n}\n"
  },
  {
    "path": "lib/rline.c",
    "content": "/**\n * @brief Line editor\n *\n * Interactive line input editor with syntax highlighting for\n * a handful of languages. Based on an old version of Bim.\n * Used by the shell and Kuroko.\n *\n * This library is generally usable on Linux and even Windows.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2018-2022 K. Lange\n */\n#define _XOPEN_SOURCE\n#define _DEFAULT_SOURCE\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <ctype.h>\n#include <string.h>\n#include <wchar.h>\n#include <unistd.h>\n#include <locale.h>\n#include <signal.h>\n#ifndef _WIN32\n#include <termios.h>\n#include <sys/ioctl.h>\n#else\n#include <windows.h>\n#include <io.h>\n#include \"wcwidth._h\"\n#endif\n#ifdef __toaru__\n#include <toaru/rline.h>\n#else\n#include \"rline.h\"\n#endif\n\nstatic __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); }\nstatic __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); }\n\n#undef isdigit\n#undef isxdigit\n#define isdigit(c) _isdigit(c)\n#define isxdigit(c) _isxdigit(c)\n\nchar * rline_history[RLINE_HISTORY_ENTRIES];\nint rline_history_count  = 0;\nint rline_history_offset = 0;\nint rline_scroll = 0;\nchar * rline_exit_string = \"exit\\n\";\nint rline_terminal_width = 0;\nchar * rline_preload = NULL;\n\nvoid rline_history_insert(char * str) {\n\tif (str[strlen(str)-1] == '\\n') {\n\t\tstr[strlen(str)-1] = '\\0';\n\t}\n\tif (rline_history_count) {\n\t\tif (!strcmp(str, rline_history_prev(1))) {\n\t\t\tfree(str);\n\t\t\treturn;\n\t\t}\n\t}\n\tif (rline_history_count == RLINE_HISTORY_ENTRIES) {\n\t\tfree(rline_history[rline_history_offset]);\n\t\trline_history[rline_history_offset] = str;\n\t\trline_history_offset = (rline_history_offset + 1) % RLINE_HISTORY_ENTRIES;\n\t} else {\n\t\trline_history[rline_history_count] = str;\n\t\trline_history_count++;\n\t}\n}\n\nvoid rline_history_append_line(char * str) {\n\tif (rline_history_count) {\n\t\tchar ** s = &rline_history[(rline_history_count - 1 + rline_history_offset) % RLINE_HISTORY_ENTRIES];\n\t\tsize_t len = strlen(*s) + strlen(str) + 2;\n\t\tchar * c = malloc(len);\n\t\tsnprintf(c, len, \"%s\\n%s\", *s, str);\n\t\tif (c[strlen(c)-1] == '\\n') {\n\t\t\tc[strlen(c)-1] = '\\0';\n\t\t}\n\t\tfree(*s);\n\t\t*s = c;\n\t} else {\n\t\t/* wat */\n\t}\n}\n\nchar * rline_history_get(int item) {\n\treturn rline_history[(item + rline_history_offset) % RLINE_HISTORY_ENTRIES];\n}\n\nchar * rline_history_prev(int item) {\n\treturn rline_history_get(rline_history_count - item);\n}\n\n#define UTF8_ACCEPT 0\n#define UTF8_REJECT 1\n\n/**\n * Conceptually similar to its predecessor, this implementation is much\n * less cool, as it uses three separate state tables and more shifts.\n */\nstatic inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {\n\tstatic int state_table[32] = {\n\t\t0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */\n\t\t1,1,1,1,1,1,1,1,                 /* 10xxxxxx */\n\t\t2,2,2,2,                         /* 110xxxxx */\n\t\t3,3,                             /* 1110xxxx */\n\t\t4,                               /* 11110xxx */\n\t\t1                                /* 11111xxx */\n\t};\n\n\tstatic int mask_bytes[32] = {\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,\n\t\t0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,\n\t\t0x1F,0x1F,0x1F,0x1F,\n\t\t0x0F,0x0F,\n\t\t0x07,\n\t\t0x00\n\t};\n\n\tstatic int next[5] = {\n\t\t0,\n\t\t1,\n\t\t0,\n\t\t2,\n\t\t3\n\t};\n\n\tif (*state == UTF8_ACCEPT) {\n\t\t*codep = byte & mask_bytes[byte >> 3];\n\t\t*state = state_table[byte >> 3];\n\t} else if (*state > 0) {\n\t\t*codep = (byte & 0x3F) | (*codep << 6);\n\t\t*state = next[*state];\n\t}\n\treturn *state;\n}\n\n#define ENTER_KEY     '\\n'\n#define BACKSPACE_KEY 0x08\n#define DELETE_KEY    0x7F\n#define MINIMUM_SIZE  10\n\n/**\n * Same structures as in bim.\n * A single character has:\n * - A codepoint (Unicode) of up to 21 bits.\n * - Flags for syntax highlighting.\n * - A display width for rendering.\n */\ntypedef struct {\n\tuint32_t display_width:4;\n\tuint32_t flags:7;\n\tuint32_t codepoint:21;\n} __attribute__((packed)) char_t;\n\n/**\n * We generally only have the one line,\n * but this matches bim for compatibility reasons.\n */\ntypedef struct {\n\tint available;\n\tint actual;\n\tint istate;\n\tchar_t   text[];\n} line_t;\n\n/**\n * We operate on a single line of text.\n * Maybe we can expand this in the future\n * for continuations of edits such as when\n * a quote is unclosed?\n */\nstatic line_t * the_line = NULL;\n\n/**\n * Line editor state\n */\nstatic int loading = 0;\nstatic int column = 0;\nstatic int offset = 0;\nstatic int width =  0;\nstatic int show_right_side = 0;\nstatic int show_left_side = 0;\nstatic int prompt_width_calc = 0;\nstatic int buf_size_max = 0;\n\n/**\n * Prompt strings.\n * Defaults to just a \"> \" prompt with no right side.\n * Support for right side prompts is important\n * for the ToaruOS shell.\n */\nstatic int prompt_width = 2;\nstatic char * prompt = \"> \";\nstatic int prompt_right_width = 0;\nstatic char * prompt_right = \"\";\n\nint rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) {\n\tprompt = left;\n\tprompt_right = right;\n\tprompt_width = left_width;\n\tprompt_right_width = right_width;\n\treturn 0;\n}\n\n/**\n * Extra shell commands to highlight as keywords.\n * These are basically just copied from the\n * shell's tab completion database on startup.\n */\nstatic char ** shell_commands = {0};\nstatic int shell_commands_len = 0;\n\nint rline_exp_set_shell_commands(char ** cmds, int len) {\n\tshell_commands = cmds;\n\tshell_commands_len = len;\n\treturn 0;\n}\n\n/**\n * Tab completion callback.\n * Compatible with the original rline version.\n */\nstatic rline_callback_t tab_complete_func = NULL;\n\nint rline_exp_set_tab_complete_func(rline_callback_t func) {\n\ttab_complete_func = func;\n\treturn 0;\n}\n\nstatic int have_unget = -1;\nstatic int getch(int timeout) {\n\tif (have_unget >= 0) {\n\t\tint out = have_unget;\n\t\thave_unget = -1;\n\t\treturn out;\n\t}\n#ifndef _WIN32\n\treturn fgetc(stdin);\n#else\n\tstatic int bytesRead = 0;\n\tstatic char  buf8[8];\n\tstatic uint8_t * b;\n\n\tif (bytesRead) {\n\t\tbytesRead--;\n\t\treturn *(b++);\n\t}\n\n\tDWORD dwRead;\n\tuint16_t buf16[8] = {0};\n\tif (ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE),buf16,2,&dwRead,NULL)) {\n\t\tint r = WideCharToMultiByte(CP_UTF8, 0, buf16, -1, buf8, 8, 0, 0);\n\t\tif (r > 1 && buf8[r-1] == '\\0') r--;\n\t\tb = (uint8_t*)buf8;\n\t\tbytesRead = r - 1;\n\t\treturn *(b++);\n\t} else {\n\t\tfprintf(stderr, \"error on console read\\n\");\n\t\treturn -1;\n\t}\n#endif\n}\n\n/**\n * Convert from Unicode string to utf-8.\n */\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\n/**\n * Obtain codepoint display width.\n *\n * This is copied from bim. Supports a few useful\n * things like rendering escapes as codepoints.\n */\nstatic int codepoint_width(int codepoint) {\n\tif (codepoint == '\\t') {\n\t\treturn 1; /* Recalculate later */\n\t}\n\tif (codepoint < 32) {\n\t\t/* We render these as ^@ */\n\t\treturn 2;\n\t}\n\tif (codepoint == 0x7F) {\n\t\t/* Renders as ^? */\n\t\treturn 2;\n\t}\n\tif (codepoint > 0x7f && codepoint < 0xa0) {\n\t\t/* Upper control bytes <xx> */\n\t\treturn 4;\n\t}\n\tif (codepoint == 0xa0) {\n\t\t/* Non-breaking space _ */\n\t\treturn 1;\n\t}\n\t/* Skip wcwidth for anything under 256 */\n\tif (codepoint > 256) {\n\t\t/* Higher codepoints may be wider (eg. Japanese) */\n\t\tint out = wcwidth(codepoint);\n\t\tif (out >= 1) return out;\n\t\t/* Invalid character, render as [U+ABCD] or [U+ABCDEF] */\n\t\treturn (codepoint < 0x10000) ? 8 : 10;\n\t}\n\treturn 1;\n}\n\nstatic void recalculate_tabs(line_t * line) {\n\tint j = 0;\n\tfor (int i = 0; i < line->actual; ++i) {\n\t\tif (line->text[i].codepoint == '\\t') {\n\t\t\tline->text[i].display_width = 4 - (j % 4);\n\t\t}\n\t\tj += line->text[i].display_width;\n\t}\n}\n\n\n/**\n * Color themes have also been copied from bim.\n *\n * Slimmed down to only the ones we use for syntax\n * highlighting; the UI colors have been removed.\n */\nstatic const char * COLOR_FG        = \"@9\";\nstatic const char * COLOR_BG        = \"@9\";\nstatic const char * COLOR_ALT_FG    = \"@5\";\nstatic const char * COLOR_ALT_BG    = \"@9\";\nstatic const char * COLOR_KEYWORD   = \"@4\";\nstatic const char * COLOR_STRING    = \"@2\";\nstatic const char * COLOR_COMMENT   = \"@5\";\nstatic const char * COLOR_TYPE      = \"@3\";\nstatic const char * COLOR_PRAGMA    = \"@1\";\nstatic const char * COLOR_NUMERAL   = \"@1\";\nstatic const char * COLOR_RED       = \"@1\";\nstatic const char * COLOR_GREEN     = \"@2\";\nstatic const char * COLOR_ESCAPE    = \"@2\";\nstatic const char * COLOR_SEARCH_FG = \"@0\";\nstatic const char * COLOR_SEARCH_BG = \"@3\";\nstatic const char * COLOR_ERROR_FG  = \"@9\";\nstatic const char * COLOR_ERROR_BG  = \"@9\";\nstatic const char * COLOR_BOLD      = \"@9\";\nstatic const char * COLOR_LINK      = \"@9\";\n\n/**\n * Themes are selected from the $RLINE_THEME\n * environment variable.\n */\nstatic void rline_exp_load_colorscheme_default(void) {\n\tCOLOR_FG        = \"@9\";\n\tCOLOR_BG        = \"@9\";\n\tCOLOR_ALT_FG    = \"@10\";\n\tCOLOR_ALT_BG    = \"@9\";\n\tCOLOR_KEYWORD   = \"@14\";\n\tCOLOR_STRING    = \"@2\";\n\tCOLOR_COMMENT   = \"@10\";\n\tCOLOR_TYPE      = \"@3\";\n\tCOLOR_PRAGMA    = \"@1\";\n\tCOLOR_NUMERAL   = \"@1\";\n\tCOLOR_RED       = \"@1\";\n\tCOLOR_GREEN     = \"@2\";\n\tCOLOR_ESCAPE    = \"@12\";\n\tCOLOR_SEARCH_FG = \"@0\";\n\tCOLOR_SEARCH_BG = \"@13\";\n\tCOLOR_ERROR_FG  = \"@17\";\n\tCOLOR_ERROR_BG  = \"@1\";\n\tCOLOR_BOLD      = \"@9\";\n\tCOLOR_LINK      = \"@14\";\n}\n\nstatic void rline_exp_load_colorscheme_sunsmoke(void) {\n\tCOLOR_FG        = \"2;230;230;230\";\n\tCOLOR_BG        = \"@9\";\n\tCOLOR_ALT_FG    = \"2;122;122;122\";\n\tCOLOR_ALT_BG    = \"2;46;43;46\";\n\tCOLOR_KEYWORD   = \"2;51;162;230\";\n\tCOLOR_STRING    = \"2;72;176;72\";\n\tCOLOR_COMMENT   = \"2;158;153;129;3\";\n\tCOLOR_TYPE      = \"2;230;206;110\";\n\tCOLOR_PRAGMA    = \"2;194;70;54\";\n\tCOLOR_NUMERAL   = \"2;230;43;127\";\n\tCOLOR_RED       = \"2;222;53;53\";\n\tCOLOR_GREEN     = \"2;55;167;0\";\n\tCOLOR_ESCAPE    = \"2;113;203;173\";\n\tCOLOR_SEARCH_FG = \"5;234\";\n\tCOLOR_SEARCH_BG = \"5;226\";\n\tCOLOR_ERROR_FG  = \"5;15\";\n\tCOLOR_ERROR_BG  = \"5;196\";\n\tCOLOR_BOLD      = \"2;230;230;230;1\";\n\tCOLOR_LINK      = \"2;51;162;230;4\";\n}\n\n/**\n * Syntax highlighting flags.\n */\n#define FLAG_NONE      0\n#define FLAG_KEYWORD   1\n#define FLAG_STRING    2\n#define FLAG_COMMENT   3\n#define FLAG_TYPE      4\n#define FLAG_PRAGMA    5\n#define FLAG_NUMERAL   6\n#define FLAG_ERROR     7\n#define FLAG_DIFFPLUS  8\n#define FLAG_DIFFMINUS 9\n#define FLAG_NOTICE    10\n#define FLAG_BOLD      11\n#define FLAG_LINK      12\n#define FLAG_ESCAPE    13\n\n#define FLAG_SELECT    (1 << 5)\n\nstruct syntax_state {\n\tline_t * line;\n\tint line_no;\n\tint state;\n\tint i;\n};\n\n#define paint(length, flag) do { for (int i = 0; i < (length) && state->i < state->line->actual; i++, state->i++) { state->line->text[state->i].flags = (flag); } } while (0)\n#define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1)\n#define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1)\n#define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1)\n#define skip() (state->i++)\n#define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1)\n\n/**\n * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise,\n * so it can be used for prefix checking for things that need further special handling.\n */\nstatic int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) {\n\tif (keyword_qualifier(lastchar())) return 0;\n\tif (!keyword_qualifier(charat())) return 0;\n\tint i = state->i;\n\tint slen = 0;\n\twhile (i < state->line->actual || *keyword == '\\0') {\n\t\tif (*keyword == '\\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) {\n\t\t\tfor (int j = 0; j < slen; ++j) {\n\t\t\t\tpaint(1, flag);\n\t\t\t}\n\t\t\treturn 1;\n\t\t}\n\t\tif (*keyword != state->line->text[i].codepoint) return 0;\n\n\t\ti++;\n\t\tkeyword++;\n\t\tslen++;\n\t}\n\treturn 0;\n}\n\n/**\n * Find keywords from a list and paint them, assuming they aren't in the middle of other words.\n * Returns 1 if a keyword from the last was found, otherwise 0.\n */\nstatic int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) {\n\tif (keyword_qualifier(lastchar())) return 0;\n\tif (!keyword_qualifier(charat())) return 0;\n\tfor (char ** keyword = keywords; *keyword; ++keyword) {\n\t\tint d = 0;\n\t\twhile (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++;\n\t\tif ((*keyword)[d] == '\\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) {\n\t\t\tpaint((int)strlen(*keyword), flag);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * This is a basic character matcher for \"keyword\" characters.\n */\nstatic int simple_keyword_qualifier(int c) {\n\treturn isalnum(c) || (c == '_');\n}\n\n\nstatic int common_comment_buzzwords(struct syntax_state * state) {\n\tif (match_and_paint(state, \"TODO\", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; }\n\telse if (match_and_paint(state, \"XXX\", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; }\n\telse if (match_and_paint(state, \"FIXME\", FLAG_ERROR, simple_keyword_qualifier)) { return 1; }\n\treturn 0;\n}\n\n/**\n * Paint a comment until end of line, assumes this comment can not continue.\n * (Some languages have comments that can continue with a \\ - don't use this!)\n * Assumes you've already painted your comment start characters.\n */\nstatic int paint_comment(struct syntax_state * state) {\n\twhile (charat() != -1) {\n\t\tif (common_comment_buzzwords(state)) continue;\n\t\telse { paint(1, FLAG_COMMENT); }\n\t}\n\treturn -1;\n}\n\nstatic int c_keyword_qualifier(int c) {\n\treturn isalnum(c) || (c == '_');\n}\n\nstatic void paintNHex(struct syntax_state * state, int n) {\n\tpaint(2, FLAG_ESCAPE);\n\t/* Why is my FLAG_ERROR not valid in rline? */\n\tfor (int i = 0; i < n; ++i) {\n\t\tpaint(1, isxdigit(charat()) ? FLAG_ESCAPE : FLAG_DIFFMINUS);\n\t}\n}\n\nstatic char * syn_krk_keywords[] = {\n\t\"and\",\"class\",\"def\",\"else\",\"for\",\"if\",\"in\",\"import\",\"del\",\n\t\"let\",\"not\",\"or\",\"return\",\"while\",\"try\",\"except\",\"raise\",\n\t\"continue\",\"break\",\"as\",\"from\",\"elif\",\"lambda\",\"with\",\"is\",\n\t\"pass\",\"assert\",\"yield\",\"finally\",\"async\",\"await\",\n\tNULL\n};\n\nstatic char * syn_krk_types[] = {\n\t/* built-in functions */\n\t\"self\", \"super\", /* implicit in a class method */\n\t\"len\", \"str\", \"int\", \"float\", \"dir\", \"repr\", /* global functions from __builtins__ */\n\t\"list\",\"dict\",\"range\", /* builtin classes */\n\t\"object\",\"isinstance\",\"type\",\"tuple\",\"reversed\",\n\t\"print\",\"set\",\"any\",\"all\",\"bool\",\"ord\",\"chr\",\"hex\",\"oct\",\"filter\",\n\t\"sorted\",\"bytes\",\"getattr\",\"sum\",\"min\",\"max\",\"id\",\"hash\",\"map\",\"bin\",\n\t\"enumerate\",\"zip\",\"setattr\",\"property\",\"staticmethod\",\"classmethod\",\n\t\"issubclass\",\"hasattr\",\"delattr\",\"NotImplemented\",\"abs\",\"slice\",\"long\",\n\tNULL\n};\n\nstatic char * syn_krk_special[] = {\n\t\"True\",\"False\",\"None\",\n\t/* Exception names */\n\tNULL\n};\n\nstatic char * syn_krk_exception[] = {\n\t\"Exception\", \"TypeError\", \"ArgumentError\", \"IndexError\", \"KeyError\",\n\t\"AttributeError\", \"NameError\", \"ImportError\", \"IOError\", \"ValueError\",\n\t\"KeyboardInterrupt\", \"ZeroDivisionError\", \"NotImplementedError\", \"SyntaxError\",\n\t\"AssertionError\", \"BaseException\", \"OSError\", \"SystemError\",\n\tNULL\n};\n\nstatic void paint_krk_string_shared(struct syntax_state * state, int type, int isFormat, int isTriple) {\n\tif (charat() == '\\\\') {\n\t\tif (nextchar() == 'x') {\n\t\t\tpaintNHex(state, 2);\n\t\t} else if (nextchar() == 'u') {\n\t\t\tpaintNHex(state, 4);\n\t\t} else if (nextchar() == 'U') {\n\t\t\tpaintNHex(state, 8);\n\t\t} else if (nextchar() >= '0' && nextchar() <= '7') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t\tif (charat() >= '0' && charat() <= '7') {\n\t\t\t\tpaint(1, FLAG_ESCAPE);\n\t\t\t\tif (charat() >= '0' && charat() <= '7') {\n\t\t\t\t\tpaint(1, FLAG_ESCAPE);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t}\n\t} else if (isFormat && charat() == '{') {\n\t\tif (nextchar() == '{') {\n\t\t\tpaint(2, FLAG_STRING);\n\t\t\treturn;\n\t\t}\n\t\tpaint(1, FLAG_ESCAPE);\n\t\tif (charat() == '}') {\n\t\t\tstate->i--;\n\t\t\tpaint(2, FLAG_ERROR); /* Can't do that. */\n\t\t} else {\n\t\t\tint x = 0;\n\t\t\twhile (charat() != -1) {\n\t\t\t\tif (charat() == '{') {\n\t\t\t\t\tx++;\n\t\t\t\t} else if (charat() == '}') {\n\t\t\t\t\tif (x == 0) {\n\t\t\t\t\t\tpaint(1, FLAG_ESCAPE);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tx--;\n\t\t\t\t} else if (charat() == type && !isTriple) {\n\t\t\t\t\twhile (charat() != -1) {\n\t\t\t\t\t\tpaint(1, FLAG_ERROR);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t} else if (find_keywords(state, syn_krk_keywords, FLAG_ESCAPE, c_keyword_qualifier)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t} else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\t}\n\t\t}\n\t} else {\n\t\tpaint(1, FLAG_STRING);\n\t}\n}\n\nstatic void paint_krk_string(struct syntax_state * state, int type, int isFormat) {\n\t/* Assumes you came in from a check of charat() == '\"' */\n\tpaint(1, FLAG_STRING);\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\\\' && nextchar() == type) {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == type) {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn;\n\t\t} else {\n\t\t\tpaint_krk_string_shared(state,type,isFormat,0);\n\t\t}\n\t}\n}\n\nstatic int paint_krk_numeral(struct syntax_state * state) {\n\tif (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) {\n\t\tpaint(2, FLAG_NUMERAL);\n\t\twhile (isxdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL);\n\t} else if (charat() == '0' && (nextchar() == 'o' || nextchar() == 'O')) {\n\t\tpaint(2, FLAG_NUMERAL);\n\t\twhile ((charat() >= '0' && charat() <= '7') || charat() == '_') paint(1, FLAG_NUMERAL);\n\t} else if (charat() == '0' && (nextchar() == 'b' || nextchar() == 'B')) {\n\t\tpaint(2, FLAG_NUMERAL);\n\t\twhile (charat() == '0' || charat() == '1' || charat() == '_') paint(1, FLAG_NUMERAL);\n\t} else {\n\t\twhile (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL);\n\t\tif (charat() == '.' && isdigit(nextchar())) {\n\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t}\n\t\tif (charat() == 'e' || charat() == 'E') {\n\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\tif (charat() == '-' || charat() == '+') paint(1, FLAG_NUMERAL);\n\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int paint_krk_triple_string(struct syntax_state * state, int type, int isFormat) {\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\\\' && nextchar() == type) {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == type) {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\tif (charat() == type && nextchar() == type) {\n\t\t\t\tpaint(2, FLAG_STRING);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} else {\n\t\t\tpaint_krk_string_shared(state,type,isFormat,1);\n\t\t}\n\t}\n\treturn (type == '\"') ? 1 : 2; /* continues */\n}\n\nstatic int syn_krk_calculate(struct syntax_state * state) {\n\tswitch (state->state) {\n\t\tcase -1:\n\t\tcase 0:\n\t\t\tif (charat() == '#') {\n\t\t\t\tpaint_comment(state);\n\t\t\t} else if (charat() == '@') {\n\t\t\t\tpaint(1, FLAG_TYPE);\n\t\t\t\twhile (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE);\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() == '\"' || charat() == '\\'') {\n\t\t\t\tint isFormat = (lastchar() == 'f');\n\t\t\t\tif (nextchar() == charat() && charrel(2) == charat()) {\n\t\t\t\t\tint type = charat();\n\t\t\t\t\tpaint(3, FLAG_STRING);\n\t\t\t\t\treturn paint_krk_triple_string(state, type, isFormat);\n\t\t\t\t} else {\n\t\t\t\t\tpaint_krk_string(state, charat(), isFormat);\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t} else if (find_keywords(state, syn_krk_keywords, FLAG_KEYWORD, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (find_keywords(state, syn_krk_special, FLAG_NUMERAL, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) {\n\t\t\t\tpaint_krk_numeral(state);\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() != -1) {\n\t\t\t\tskip();\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\t/* rline doesn't support multiline editing anyway */\n\t\tcase 1:\n\t\t\treturn paint_krk_triple_string(state, '\"', 0);\n\t\tcase 2:\n\t\t\treturn paint_krk_triple_string(state, '\\'', 0);\n\t}\n\treturn -1;\n}\n\nstatic char * syn_krk_dbg_commands[] = {\n\t\"s\", \"skip\",\n\t\"c\", \"continue\",\n\t\"q\", \"quit\",\n\t\"e\", \"enable\",\n\t\"d\", \"disable\",\n\t\"r\", \"remove\",\n\t\"bt\", \"backtrace\",\n\t\"break\",\n\t\"abort\",\n\t\"help\",\n\tNULL,\n};\n\nstatic char * syn_krk_dbg_info_types[] = {\n\t\"breakpoints\",\n\tNULL,\n};\n\nstatic int syn_krk_dbg_calculate(struct syntax_state * state) {\n\tif (state->state < 1) {\n\t\tif (state->i == 0) {\n\t\t\tif (match_and_paint(state, \"p\", FLAG_KEYWORD, c_keyword_qualifier) ||\n\t\t\t    match_and_paint(state, \"print\", FLAG_KEYWORD, c_keyword_qualifier)) {\n\t\t\t\twhile (1) {\n\t\t\t\t\tint result = syn_krk_calculate(state);\n\t\t\t\t\tif (result == 0) continue;\n\t\t\t\t\tif (result == -1) return -1;\n\t\t\t\t\treturn result + 1;\n\t\t\t\t}\n\t\t\t} else if (match_and_paint(state,\"info\", FLAG_KEYWORD, c_keyword_qualifier) ||\n\t\t\t           match_and_paint(state,\"i\", FLAG_KEYWORD, c_keyword_qualifier)) {\n\t\t\t\tskip();\n\t\t\t\tfind_keywords(state,syn_krk_dbg_info_types, FLAG_TYPE, c_keyword_qualifier);\n\t\t\t\treturn -1;\n\t\t\t} else if (find_keywords(state, syn_krk_dbg_commands, FLAG_KEYWORD, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t} else {\n\t\tstate->state -= 1;\n\t\treturn syn_krk_calculate(state) + 1;\n\t}\n}\n\n#ifdef __toaru__\nstatic int esh_variable_qualifier(int c) {\n\treturn (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_');\n}\n\nstatic int paint_esh_variable(struct syntax_state * state) {\n\tif (charat() == '{') {\n\t\tpaint(1, FLAG_TYPE);\n\t\twhile (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE);\n\t\tif (charat() == '}') paint(1, FLAG_TYPE);\n\t} else {\n\t\tif (charat() == '?' || charat() == '$' || charat() == '#') {\n\t\t\tpaint(1, FLAG_TYPE);\n\t\t} else {\n\t\t\twhile (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE);\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int paint_esh_string(struct syntax_state * state) {\n\tint last = -1;\n\twhile (charat() != -1) {\n\t\tif (last != '\\\\' && charat() == '\"') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn 0;\n\t\t} else if (charat() == '$') {\n\t\t\tpaint(1, FLAG_TYPE);\n\t\t\tpaint_esh_variable(state);\n\t\t\tlast = -1;\n\t\t} else if (charat() != -1) {\n\t\t\tlast = charat();\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n\treturn 2;\n}\n\nstatic int paint_esh_single_string(struct syntax_state * state) {\n\tint last = -1;\n\twhile (charat() != -1) {\n\t\tif (last != '\\\\' && charat() == '\\'') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn 0;\n\t\t} else if (charat() != -1) {\n\t\t\tlast = charat();\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n\treturn 1;\n}\n\nstatic int esh_keyword_qualifier(int c) {\n\treturn (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */\n}\n\nstatic char * esh_keywords[] = {\n\t\"cd\",\"exit\",\"export\",\"help\",\"history\",\"if\",\"empty?\",\n\t\"equals?\",\"return\",\"export-cmd\",\"source\",\"exec\",\"not\",\"while\",\n\t\"then\",\"else\",\"echo\",\n\tNULL\n};\n\nstatic int syn_esh_calculate(struct syntax_state * state) {\n\tif (state->state == 1) {\n\t\treturn paint_esh_single_string(state);\n\t} else if (state->state == 2) {\n\t\treturn paint_esh_string(state);\n\t}\n\tif (charat() == '#') {\n\t\twhile (charat() != -1) {\n\t\t\tif (common_comment_buzzwords(state)) continue;\n\t\t\telse paint(1, FLAG_COMMENT);\n\t\t}\n\t\treturn -1;\n\t} else if (charat() == '$') {\n\t\tpaint(1, FLAG_TYPE);\n\t\tpaint_esh_variable(state);\n\t\treturn 0;\n\t} else if (charat() == '\\'') {\n\t\tpaint(1, FLAG_STRING);\n\t\treturn paint_esh_single_string(state);\n\t} else if (charat() == '\"') {\n\t\tpaint(1, FLAG_STRING);\n\t\treturn paint_esh_string(state);\n\t} else if (match_and_paint(state, \"export\", FLAG_KEYWORD, esh_keyword_qualifier)) {\n\t\twhile (charat() == ' ') skip();\n\t\twhile (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE);\n\t\treturn 0;\n\t} else if (match_and_paint(state, \"export-cmd\", FLAG_KEYWORD, esh_keyword_qualifier)) {\n\t\twhile (charat() == ' ') skip();\n\t\twhile (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE);\n\t\treturn 0;\n\t} else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) {\n\t\treturn 0;\n\t} else if (find_keywords(state, shell_commands, FLAG_KEYWORD, esh_keyword_qualifier)) {\n\t\treturn 0;\n\t} else if (isdigit(charat())) {\n\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\treturn 0;\n\t} else if (charat() != -1) {\n\t\tskip();\n\t\treturn 0;\n\t}\n\treturn -1;\n}\n\nstatic char * syn_py_keywords[] = {\n\t\"class\",\"def\",\"return\",\"del\",\"if\",\"else\",\"elif\",\"for\",\"while\",\"continue\",\n\t\"break\",\"assert\",\"as\",\"and\",\"or\",\"except\",\"finally\",\"from\",\"global\",\n\t\"import\",\"in\",\"is\",\"lambda\",\"with\",\"nonlocal\",\"not\",\"pass\",\"raise\",\"try\",\"yield\",\n\tNULL\n};\n\nstatic char * syn_py_types[] = {\n\t/* built-in functions */\n\t\"abs\",\"all\",\"any\",\"ascii\",\"bin\",\"bool\",\"breakpoint\",\"bytes\",\n\t\"bytearray\",\"callable\",\"compile\",\"complex\",\"delattr\",\"chr\",\n\t\"dict\",\"dir\",\"divmod\",\"enumerate\",\"eval\",\"exec\",\"filter\",\"float\",\n\t\"format\",\"frozenset\",\"getattr\",\"globals\",\"hasattr\",\"hash\",\"help\",\n\t\"hex\",\"id\",\"input\",\"int\",\"isinstance\",\"issubclass\",\"iter\",\"len\",\n\t\"list\",\"locals\",\"map\",\"max\",\"memoryview\",\"min\",\"next\",\"object\",\n\t\"oct\",\"open\",\"ord\",\"pow\",\"print\",\"property\",\"range\",\"repr\",\"reverse\",\n\t\"round\",\"set\",\"setattr\",\"slice\",\"sorted\",\"staticmethod\",\"str\",\"sum\",\n\t\"super\",\"tuple\",\"type\",\"vars\",\"zip\",\n\tNULL\n};\n\nstatic char * syn_py_special[] = {\n\t\"True\",\"False\",\"None\",\n\tNULL\n};\n\nstatic int paint_py_triple_double(struct syntax_state * state) {\n\twhile (charat() != -1) {\n\t\tif (charat() == '\"') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\tif (charat() == '\"' && nextchar() == '\"') {\n\t\t\t\tpaint(2, FLAG_STRING);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} else {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n\treturn 1; /* continues */\n}\n\nstatic int paint_py_triple_single(struct syntax_state * state) {\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\'') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\tif (charat() == '\\'' && nextchar() == '\\'') {\n\t\t\t\tpaint(2, FLAG_STRING);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} else {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n\treturn 2; /* continues */\n}\n\nstatic int paint_py_single_string(struct syntax_state * state) {\n\tpaint(1, FLAG_STRING);\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\\\' && nextchar() == '\\'') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == '\\'') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn 0;\n\t\t} else if (charat() == '\\\\') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n\treturn 0;\n}\n\nstatic int paint_py_numeral(struct syntax_state * state) {\n\tif (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) {\n\t\tpaint(2, FLAG_NUMERAL);\n\t\twhile (isxdigit(charat())) paint(1, FLAG_NUMERAL);\n\t} else if (charat() == '0' && nextchar() == '.') {\n\t\tpaint(2, FLAG_NUMERAL);\n\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\tif ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) {\n\t\t\tpaint(2, FLAG_NUMERAL);\n\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t} else if (charat() == 'e' || charat() == 'E') {\n\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t}\n\t\tif (charat() == 'j') paint(1, FLAG_NUMERAL);\n\t\treturn 0;\n\t} else {\n\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\tif (charat() == '.') {\n\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t\tif ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) {\n\t\t\t\tpaint(2, FLAG_NUMERAL);\n\t\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t\t} else if (charat() == 'e' || charat() == 'E') {\n\t\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\t\twhile (isdigit(charat())) paint(1, FLAG_NUMERAL);\n\t\t\t}\n\t\t\tif (charat() == 'j') paint(1, FLAG_NUMERAL);\n\t\t\treturn 0;\n\t\t}\n\t\tif (charat() == 'j') paint(1, FLAG_NUMERAL);\n\t}\n\twhile (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL);\n\treturn 0;\n}\n\nstatic void paint_py_format_string(struct syntax_state * state, char type) {\n\tpaint(1, FLAG_STRING);\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\\\' && nextchar() == type) {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == type) {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn;\n\t\t} else if (charat() == '\\\\') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == '{') {\n\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\tif (charat() == '}') {\n\t\t\t\tstate->i--;\n\t\t\t\tpaint(2, FLAG_ERROR); /* Can't do that. */\n\t\t\t} else {\n\t\t\t\twhile (charat() != -1 && charat() != '}') {\n\t\t\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\t\t}\n\t\t\t\tpaint(1, FLAG_NUMERAL);\n\t\t\t}\n\t\t} else {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n}\n\nstatic void paint_simple_string(struct syntax_state * state) {\n\t/* Assumes you came in from a check of charat() == '\"' */\n\tpaint(1, FLAG_STRING);\n\twhile (charat() != -1) {\n\t\tif (charat() == '\\\\' && nextchar() == '\"') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else if (charat() == '\"') {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t\treturn;\n\t\t} else if (charat() == '\\\\') {\n\t\t\tpaint(2, FLAG_ESCAPE);\n\t\t} else {\n\t\t\tpaint(1, FLAG_STRING);\n\t\t}\n\t}\n}\n\nstatic int syn_py_calculate(struct syntax_state * state) {\n\tswitch (state->state) {\n\t\tcase -1:\n\t\tcase 0:\n\t\t\tif (charat() == '#') {\n\t\t\t\tpaint_comment(state);\n\t\t\t} else if (state->i == 0 && match_and_paint(state, \"import\", FLAG_PRAGMA, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() == '@') {\n\t\t\t\tpaint(1, FLAG_PRAGMA);\n\t\t\t\twhile (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA);\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() == '\"') {\n\t\t\t\tif (nextchar() == '\"' && charrel(2) == '\"') {\n\t\t\t\t\tpaint(3, FLAG_STRING);\n\t\t\t\t\treturn paint_py_triple_double(state);\n\t\t\t\t} else if (lastchar() == 'f') {\n\t\t\t\t\t/* I don't like backtracking like this, but it makes this parse easier */\n\t\t\t\t\tstate->i--;\n\t\t\t\t\tpaint(1,FLAG_TYPE);\n\t\t\t\t\tpaint_py_format_string(state,'\"');\n\t\t\t\t\treturn 0;\n\t\t\t\t} else {\n\t\t\t\t\tpaint_simple_string(state);\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t} else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) {\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() == '\\'') {\n\t\t\t\tif (nextchar() == '\\'' && charrel(2) == '\\'') {\n\t\t\t\t\tpaint(3, FLAG_STRING);\n\t\t\t\t\treturn paint_py_triple_single(state);\n\t\t\t\t} else if (lastchar() == 'f') {\n\t\t\t\t\t/* I don't like backtracking like this, but it makes this parse easier */\n\t\t\t\t\tstate->i--;\n\t\t\t\t\tpaint(1,FLAG_TYPE);\n\t\t\t\t\tpaint_py_format_string(state,'\\'');\n\t\t\t\t\treturn 0;\n\t\t\t\t} else {\n\t\t\t\t\treturn paint_py_single_string(state);\n\t\t\t\t}\n\t\t\t} else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) {\n\t\t\t\tpaint_py_numeral(state);\n\t\t\t\treturn 0;\n\t\t\t} else if (charat() != -1) {\n\t\t\t\tskip();\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 1: /* multiline \"\"\" string */\n\t\t\treturn paint_py_triple_double(state);\n\t\tcase 2: /* multiline ''' string */\n\t\t\treturn paint_py_triple_single(state);\n\t}\n\treturn -1;\n}\n\nvoid * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) {\n\n\trline_exp_set_prompts(prompt, \"\", strlen(prompt), 0);\n\n\tchar * buf = malloc(1024);\n\tmemset(buf, 0, 1024);\n\n\trline_exp_set_syntax(\"python\");\n\trline_exit_string = \"\";\n\trline(buf, 1024);\n\trline_history_insert(strdup(buf));\n\trline_scroll = 0;\n\n\treturn buf;\n}\n#endif\n\nvoid rline_redraw(rline_context_t * context) {\n\tif (context->quiet) return;\n\tprintf(\"\\033[u%s\\033[K\", context->buffer);\n\tfor (int i = context->offset; i < context->collected; ++i) {\n\t\tprintf(\"\\033[D\");\n\t}\n\tfflush(stdout);\n}\n\n\n/**\n * Convert syntax hilighting flag to color code\n */\nstatic const char * flag_to_color(int _flag) {\n\tint flag = _flag & 0xF;\n\tswitch (flag) {\n\t\tcase FLAG_KEYWORD:\n\t\t\treturn COLOR_KEYWORD;\n\t\tcase FLAG_STRING:\n\t\t\treturn COLOR_STRING;\n\t\tcase FLAG_COMMENT:\n\t\t\treturn COLOR_COMMENT;\n\t\tcase FLAG_TYPE:\n\t\t\treturn COLOR_TYPE;\n\t\tcase FLAG_NUMERAL:\n\t\t\treturn COLOR_NUMERAL;\n\t\tcase FLAG_PRAGMA:\n\t\t\treturn COLOR_PRAGMA;\n\t\tcase FLAG_DIFFPLUS:\n\t\t\treturn COLOR_GREEN;\n\t\tcase FLAG_DIFFMINUS:\n\t\t\treturn COLOR_RED;\n\t\tcase FLAG_BOLD:\n\t\t\treturn COLOR_BOLD;\n\t\tcase FLAG_LINK:\n\t\t\treturn COLOR_LINK;\n\t\tcase FLAG_ESCAPE:\n\t\t\treturn COLOR_ESCAPE;\n\t\tdefault:\n\t\t\treturn COLOR_FG;\n\t}\n}\n\nstatic struct syntax_definition {\n\tchar * name;\n\tint (*calculate)(struct syntax_state *);\n\tint tabIndents;\n} syntaxes[] = {\n\t{\"krk\",syn_krk_calculate, 1},\n\t{\"krk-dbg\",syn_krk_dbg_calculate, 1},\n#ifdef __toaru__\n\t{\"python\",syn_py_calculate, 1},\n\t{\"esh\",syn_esh_calculate, 0},\n#endif\n\t{NULL, NULL, 0},\n};\n\nstatic struct syntax_definition * syntax;\n\nint rline_exp_set_syntax(char * name) {\n\tif (!name) {\n\t\tsyntax = NULL;\n\t\treturn 0;\n\t}\n\tfor (struct syntax_definition * s = syntaxes; s->name; ++s) {\n\t\tif (!strcmp(name,s->name)) {\n\t\t\tsyntax = s;\n\t\t\treturn 0;\n\t\t}\n\t}\n\treturn 1;\n}\n\n/**\n * Syntax highlighting\n * Slimmed down from the bim implementation a bit,\n * but generally compatible with the same definitions.\n *\n * Type highlighting has been removed as the sh highlighter\n * didn't use it. This should be made pluggable again, and\n * the bim syntax highlighters should probably be broken\n * out into dynamically-loaded libraries?\n */\nstatic void recalculate_syntax(line_t * line) {\n\t/* Clear syntax for this line first */\n\tint line_no = 0;\n\t//int is_original = 1;\n\twhile (1) {\n\t\tfor (int i = 0; i < line->actual; ++i) {\n\t\t\tline->text[i].flags = 0;\n\t\t}\n\n\t\tif (!syntax) {\n\t\t\treturn;\n\t\t}\n\n\t\t/* Start from the line's stored in initial state */\n\t\tstruct syntax_state state;\n\t\tstate.line = line;\n\t\tstate.line_no = line_no;\n\t\tstate.state = line->istate;\n\t\tstate.i = 0;\n\n\t\twhile (1) {\n\t\t\tstate.state = syntax->calculate(&state);\n\n\t\t\tif (state.state != 0) {\n\t\t\t\t/* TODO: Figure out a way to make this work for multiline input */\n#if 0\n\t\t\t\tif (line_no == -1) return;\n\t\t\t\tif (!is_original) {\n\t\t\t\t\tredraw_line(line_no);\n\t\t\t\t}\n\t\t\t\tif (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) {\n\t\t\t\t\tline_no++;\n\t\t\t\t\tline = env->lines[line_no];\n\t\t\t\t\tline->istate = state.state;\n\t\t\t\t\tif (env->loading) return;\n\t\t\t\t\tis_original = 0;\n\t\t\t\t\tgoto _next;\n\t\t\t\t}\n#endif\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n//_next:\n//\t\t(void)0;\n\t}\n}\n\n/**\n * Set colors\n */\nstatic void set_colors(const char * fg, const char * bg) {\n\tprintf(\"\\033[22;23;\");\n\tif (*bg == '@') {\n\t\tint _bg = atoi(bg+1);\n\t\tif (_bg < 10) {\n\t\t\tprintf(\"4%d;\", _bg);\n\t\t} else {\n\t\t\tprintf(\"10%d;\", _bg-10);\n\t\t}\n\t} else {\n\t\tprintf(\"48;%s;\", bg);\n\t}\n\tif (*fg == '@') {\n\t\tint _fg = atoi(fg+1);\n\t\tif (_fg < 10) {\n\t\t\tprintf(\"3%dm\", _fg);\n\t\t} else {\n\t\t\tprintf(\"9%dm\", _fg-10);\n\t\t}\n\t} else {\n\t\tprintf(\"38;%sm\", fg);\n\t}\n\tfflush(stdout);\n}\n\n/**\n * Set just the foreground color\n *\n * (See set_colors above)\n */\nstatic void set_fg_color(const char * fg) {\n\tprintf(\"\\033[22;23;\");\n\tif (*fg == '@') {\n\t\tint _fg = atoi(fg+1);\n\t\tif (_fg < 10) {\n\t\t\tprintf(\"3%dm\", _fg);\n\t\t} else {\n\t\t\tprintf(\"9%dm\", _fg-10);\n\t\t}\n\t} else {\n\t\tprintf(\"38;%sm\", fg);\n\t}\n\tfflush(stdout);\n}\n\nvoid rline_set_colors(rline_style_t style) {\n\tswitch (style) {\n\t\tcase RLINE_STYLE_MAIN:\n\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_ALT:\n\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_KEYWORD:\n\t\t\tset_fg_color(COLOR_KEYWORD);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_STRING:\n\t\t\tset_fg_color(COLOR_STRING);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_COMMENT:\n\t\t\tset_fg_color(COLOR_COMMENT);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_TYPE:\n\t\t\tset_fg_color(COLOR_TYPE);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_PRAGMA:\n\t\t\tset_fg_color(COLOR_PRAGMA);\n\t\t\tbreak;\n\t\tcase RLINE_STYLE_NUMERAL:\n\t\t\tset_fg_color(COLOR_NUMERAL);\n\t\t\tbreak;\n\t}\n}\n\n/**\n * Mostly copied from bim, but with some minor\n * alterations and removal of selection support.\n */\nstatic void render_line(void) {\n\tprintf(\"\\033[?25l\");\n\tif (show_left_side) {\n\t\tprintf(\"\\033[0m\\r%s\", prompt);\n\t} else {\n\t\tprintf(\"\\033[0m\\r$\");\n\t}\n\n\tif (offset && prompt_width_calc) {\n\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\tprintf(\"\\b<\");\n\t}\n\n\tint i = 0; /* Offset in char_t line data entries */\n\tint j = 0; /* Offset in terminal cells */\n\n\tconst char * last_color = NULL;\n\tint was_searching = 0;\n\n\t/* Set default text colors */\n\tset_colors(COLOR_FG, COLOR_BG);\n\n\t/*\n\t * When we are rendering in the middle of a wide character,\n\t * we render -'s to fill the remaining amount of the \n\t * charater's width\n\t */\n\tint remainder = 0;\n\n\tint is_spaces = 1;\n\n\tline_t * line = the_line;\n\n\t/* For each character in the line ... */\n\twhile (i < line->actual) {\n\n\t\t/* If there is remaining text... */\n\t\tif (remainder) {\n\n\t\t\t/* If we should be drawing by now... */\n\t\t\tif (j >= offset) {\n\t\t\t\t/* Fill remainder with -'s */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"-\");\n\t\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t\t}\n\n\t\t\t/* One less remaining width cell to fill */\n\t\t\tremainder--;\n\n\t\t\t/* Terminal offset moves forward */\n\t\t\tj++;\n\n\t\t\t/*\n\t\t\t * If this was the last remaining character, move to\n\t\t\t * the next codepoint in the line\n\t\t\t */\n\t\t\tif (remainder == 0) {\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Get the next character to draw */\n\t\tchar_t c = line->text[i];\n\t\tif (c.codepoint != ' ') is_spaces = 0;\n\n\t\t/* If we should be drawing by now... */\n\t\tif (j >= offset) {\n\n\t\t\t/* If this character is going to fall off the edge of the screen... */\n\t\t\tif (j - offset + c.display_width >= width - prompt_width_calc) {\n\t\t\t\t/* We draw this with special colors so it isn't ambiguous */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\n\t\t\t\t/* If it's wide, draw ---> as needed */\n\t\t\t\twhile (j - offset < width - prompt_width_calc - 1) {\n\t\t\t\t\tprintf(\"-\");\n\t\t\t\t\tj++;\n\t\t\t\t}\n\n\t\t\t\t/* End the line with a > to show it overflows */\n\t\t\t\tprintf(\">\");\n\t\t\t\tset_colors(COLOR_FG, COLOR_BG);\n\t\t\t\tj++;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t/* Syntax hilighting */\n\t\t\tconst char * color = flag_to_color(c.flags);\n\t\t\tif (c.flags & FLAG_SELECT) {\n\t\t\t\tset_colors(color, COLOR_BG);\n\t\t\t\tfprintf(stdout,\"\\033[7m\");\n\t\t\t\twas_searching = 1;\n\t\t\t} else if (c.flags == FLAG_NOTICE) {\n\t\t\t\tset_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG);\n\t\t\t\twas_searching = 1;\n\t\t\t} else if (c.flags == FLAG_ERROR) {\n\t\t\t\tset_colors(COLOR_ERROR_FG, COLOR_ERROR_BG);\n\t\t\t\twas_searching = 1; /* co-opting this should work... */\n\t\t\t} else if (was_searching) {\n\t\t\t\tfprintf(stdout,\"\\033[0m\");\n\t\t\t\tset_colors(color, COLOR_BG);\n\t\t\t\tlast_color = color;\n\t\t\t} else if (!last_color || strcmp(color, last_color)) {\n\t\t\t\tset_fg_color(color);\n\t\t\t\tlast_color = color;\n\t\t\t}\n\n\t\t\t/* Render special characters */\n\t\t\tif (c.codepoint == '\\t') {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"»\");\n\t\t\t\tfor (int i = 1; i < c.display_width; ++i) {\n\t\t\t\t\tprintf(\"·\");\n\t\t\t\t}\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint < 32) {\n\t\t\t\t/* Codepoints under 32 to get converted to ^@ escapes */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"^%c\", '@' + c.codepoint);\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint == 0x7f) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"^?\");\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint > 0x7f && c.codepoint < 0xa0) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"<%2x>\", c.codepoint);\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.codepoint == 0xa0) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"_\");\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.display_width == 8) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"[U+%04x]\", c.codepoint);\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else if (c.display_width == 10) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"[U+%06x]\", c.codepoint);\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n#if 0\n\t\t\t} else if (c.codepoint == ' ' && i == line->actual - 1) {\n\t\t\t\t/* Special case: space at end of line */\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\tprintf(\"·\");\n\t\t\t\tset_colors(COLOR_FG, COLOR_BG);\n#endif\n\t\t\t} else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % 4)) {\n\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */\n\t\t\t\tprintf(\"▏\");\n\t\t\t\tset_colors(last_color ? last_color : COLOR_FG, COLOR_BG);\n\t\t\t} else {\n\t\t\t\t/* Normal characters get output */\n\t\t\t\tchar tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */\n\t\t\t\tto_eight(c.codepoint, tmp);\n\t\t\t\tprintf(\"%s\", tmp);\n\t\t\t}\n\n\t\t\t/* Advance the terminal cell offset by the render width of this character */\n\t\t\tj += c.display_width;\n\n\t\t\t/* Advance to the next character */\n\t\t\ti++;\n\t\t} else if (c.display_width > 1) {\n\t\t\t/*\n\t\t\t * If this is a wide character but we aren't ready to render yet,\n\t\t\t * we may need to draw some filler text for the remainder of its\n\t\t\t * width to ensure we don't jump around when horizontally scrolling\n\t\t\t * past wide characters.\n\t\t\t */\n\t\t\tremainder = c.display_width - 1;\n\t\t\tj++;\n\t\t} else {\n\t\t\t/* Regular character, not ready to draw, advance without doing anything */\n\t\t\tj++;\n\t\t\ti++;\n\t\t}\n\t}\n\n\tprintf(\"\\033[0m\");\n\tset_colors(COLOR_FG, COLOR_BG);\n\n\tif (show_right_side && prompt_right_width) {\n\t\t/* Fill to end right hand side */\n\t\tfor (; j < width + offset - prompt_width_calc; ++j) {\n\t\t\tprintf(\" \");\n\t\t}\n\n\t\t/* Print right hand side */\n\t\tprintf(\"\\033[0m%s\", prompt_right);\n\t} else {\n\t\tprintf(\"\\033[0K\");\n\t}\n\tfflush(stdout);\n}\n\n/**\n * Create a line_t\n */\nstatic line_t * line_create(void) {\n\tline_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32);\n\tline->available = 32;\n\tline->actual    = 0;\n\tline->istate    = 0;\n\treturn line;\n}\n\n/**\n * Insert a character into a line\n */\nstatic line_t * line_insert(line_t * line, char_t c, int offset) {\n\n\t/* If there is not enough space... */\n\tif (line->actual == line->available) {\n\t\t/* Expand the line buffer */\n\t\tline->available *= 2;\n\t\tline = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available);\n\t}\n\n\t/* If this was not the last character, then shift remaining characters forward. */\n\tif (offset < line->actual) {\n\t\tmemmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset));\n\t}\n\n\t/* Insert the new character */\n\tline->text[offset] = c;\n\n\t/* There is one new character in the line */\n\tline->actual += 1;\n\n\tif (!loading) {\n\t\trecalculate_tabs(line);\n\t\trecalculate_syntax(line);\n\t}\n\n\treturn line;\n}\n\n/**\n * Update terminal size\n *\n * We don't listen for sigwinch for various reasons...\n */\nstatic void get_size(void) {\n#ifndef _WIN32\n\tstruct winsize w;\n\tioctl(STDOUT_FILENO, TIOCGWINSZ, &w);\n\trline_terminal_width = w.ws_col;\n#else\n\tCONSOLE_SCREEN_BUFFER_INFO csbi;\n\tGetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);\n\trline_terminal_width = csbi.srWindow.Right - csbi.srWindow.Left + 1;\n#endif\n\tif (rline_terminal_width - prompt_right_width - prompt_width > MINIMUM_SIZE) {\n\t\tshow_right_side = 1;\n\t\tshow_left_side = 1;\n\t\tprompt_width_calc = prompt_width;\n\t\twidth = rline_terminal_width - prompt_right_width;\n\t} else {\n\t\tshow_right_side = 0;\n\t\tif (rline_terminal_width - prompt_width > MINIMUM_SIZE) {\n\t\t\tshow_left_side = 1;\n\t\t\tprompt_width_calc = prompt_width;\n\t\t} else {\n\t\t\tshow_left_side = 0;\n\t\t\tprompt_width_calc = 1;\n\t\t}\n\t\twidth = rline_terminal_width;\n\t}\n}\n\n/**\n * Place the cursor within the line\n */\nvoid rline_place_cursor(void) {\n\tint x = prompt_width_calc + 1 - offset;\n\tfor (int i = 0; i < column; ++i) {\n\t\tchar_t * c = &the_line->text[i];\n\t\tx += c->display_width;\n\t}\n\n\tif (x > width - 1) {\n\t\t/* Adjust the offset appropriately to scroll horizontally */\n\t\tint diff = x - (width - 1);\n\t\toffset += diff;\n\t\tx -= diff;\n\t\trender_line();\n\t}\n\n\t/* Same for scrolling horizontally to the left */\n\tif (x < prompt_width_calc + 1) {\n\t\tint diff = (prompt_width_calc + 1) - x;\n\t\toffset -= diff;\n\t\tx += diff;\n\t\trender_line();\n\t}\n\n\tprintf(\"\\033[?25h\\033[%dG\", x);\n\tfflush(stdout);\n}\n\n/**\n * Delete a character\n */\nstatic void line_delete(line_t * line, int offset) {\n\n\t/* Can't delete character before start of line. */\n\tif (offset == 0) return;\n\n\t/* If this isn't the last character, we need to move all subsequent characters backwards */\n\tif (offset < line->actual) {\n\t\tmemmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset));\n\t}\n\n\t/* The line is one character shorter */\n\tline->actual -= 1;\n\n\tif (!loading) {\n\t\trecalculate_tabs(line);\n\t\trecalculate_syntax(line);\n\t}\n}\n\n/**\n * Backspace from the cursor position\n */\nstatic void delete_at_cursor(void) {\n\tif (column > 0) {\n\t\tline_delete(the_line, column);\n\t\tcolumn--;\n\t\tif (offset > 0) offset--;\n\t}\n}\n\nstatic void smart_backspace(void) {\n\tif (column > 0) {\n\t\tint i;\n\t\tfor (i = 0; i < column; ++i) {\n\t\t\tif (the_line->text[i].codepoint != ' ') break;\n\t\t}\n\t\tif (i == column) {\n\t\t\tdelete_at_cursor();\n\t\t\twhile (column > 0 && (column % 4)) delete_at_cursor();\n\t\t\treturn;\n\t\t}\n\t}\n\tdelete_at_cursor();\n}\n\n/**\n * Delete whole word\n */\nstatic void delete_word(void) {\n\tif (!the_line->actual) return;\n\tif (!column) return;\n\n\twhile (column > 0 && the_line->text[column-1].codepoint == ' ') {\n\t\tdelete_at_cursor();\n\t}\n\n\tdo {\n\t\tif (column > 0) {\n\t\t\tdelete_at_cursor();\n\t\t}\n\t} while (column > 0 && the_line->text[column-1].codepoint != ' ');\n}\n\n/**\n * Insert at cursor position\n */\nstatic void insert_char(uint32_t c) {\n\tchar_t _c;\n\t_c.codepoint = c;\n\t_c.flags = 0;\n\t_c.display_width = codepoint_width(c);\n\n\tthe_line = line_insert(the_line, _c, column);\n\n\tcolumn++;\n}\n\nstatic char * paren_pairs = \"()[]{}<>\";\n\nstatic int is_paren(int c) {\n\tchar * p = paren_pairs;\n\twhile (*p) {\n\t\tif (c == *p) return 1;\n\t\tp++;\n\t}\n\treturn 0;\n}\n\nstatic void find_matching_paren(int * out_col, int in_col) {\n\tif (column - in_col > the_line->actual) {\n\t\treturn; /* Invalid cursor position */\n\t}\n\n\tint paren_match = 0;\n\tint direction = 0;\n\tint start = the_line->text[column-in_col].codepoint;\n\tint flags = the_line->text[column-in_col].flags & 0x1F;\n\tint count = 0;\n\n\t/* TODO what about unicode parens? */\n\tfor (int i = 0; paren_pairs[i]; ++i) {\n\t\tif (start == paren_pairs[i]) {\n\t\t\tdirection = (i % 2 == 0) ? 1 : -1;\n\t\t\tparen_match = paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)];\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!paren_match) return;\n\n\t/* Scan for match */\n\tint col = column - in_col;\n\n\twhile (col > -1 && col < the_line->actual) {\n\t\t/* Only match on same syntax */\n\t\tif ((the_line->text[col].flags & 0x1F) == flags) {\n\t\t\t/* Count up on same direction */\n\t\t\tif (the_line->text[col].codepoint == start) count++;\n\t\t\t/* Count down on opposite direction */\n\t\t\tif (the_line->text[col].codepoint == paren_match) {\n\t\t\t\tcount--;\n\t\t\t\t/* When count == 0 we have a match */\n\t\t\t\tif (count == 0) goto _match_found;\n\t\t\t}\n\t\t}\n\t\tcol += direction;\n\t}\n\n_match_found:\n\t*out_col = col;\n}\n\nstatic void redraw_matching_paren(int col) {\n\tfor (int j = 0; j < the_line->actual; ++j) {\n\t\tif (j == col) {\n\t\t\tthe_line->text[j].flags |= FLAG_SELECT;\n\t\t} else {\n\t\t\tthe_line->text[j].flags &= ~(FLAG_SELECT);\n\t\t}\n\t}\n}\n\nstatic void highlight_matching_paren(void) {\n\tint col = -1;\n\tif (column < the_line->actual && is_paren(the_line->text[column].codepoint)) {\n\t\tfind_matching_paren(&col, 0);\n\t} else if (column > 0 && is_paren(the_line->text[column-1].codepoint)) {\n\t\tfind_matching_paren(&col, 1);\n\t}\n\tredraw_matching_paren(col);\n}\n\n\n/**\n * Move cursor left\n */\nstatic void cursor_left(void) {\n\tif (column > 0) column--;\n\trline_place_cursor();\n}\n\n/**\n * Move cursor right\n */\nstatic void cursor_right(void) {\n\tif (column < the_line->actual) column++;\n\trline_place_cursor();\n}\n\n/**\n * Move cursor one whole word left\n */\nstatic void word_left(void) {\n\tif (column == 0) return;\n\tcolumn--;\n\twhile (column && the_line->text[column].codepoint == ' ') {\n\t\tcolumn--;\n\t}\n\twhile (column > 0) {\n\t\tif (the_line->text[column-1].codepoint == ' ') break;\n\t\tcolumn--;\n\t}\n\trline_place_cursor();\n}\n\n/**\n * Move cursor one whole word right\n */\nstatic void word_right(void) {\n\twhile (column < the_line->actual && the_line->text[column].codepoint == ' ') {\n\t\tcolumn++;\n\t}\n\twhile (column < the_line->actual) {\n\t\tcolumn++;\n\t\tif (column < the_line->actual && the_line->text[column].codepoint == ' ') break;\n\t}\n\trline_place_cursor();\n}\n\n/**\n * Move cursor to start of line\n */\nstatic void cursor_home(void) {\n\tcolumn = 0;\n\trline_place_cursor();\n}\n\n/*\n * Move cursor to end of line\n */\nstatic void cursor_end(void) {\n\tcolumn = the_line->actual;\n\trline_place_cursor();\n}\n\n/**\n * Temporary buffer for holding utf-8 data\n */\nstatic char temp_buffer[1024];\n\n/**\n * Cycle to previous history entry\n */\nstatic void history_previous(void) {\n\tif (rline_scroll == 0) {\n\t\t/* Convert to temporaary buffer */\n\t\tunsigned int off = 0;\n\t\tmemset(temp_buffer, 0, sizeof(temp_buffer));\n\t\tfor (int j = 0; j < the_line->actual; j++) {\n\t\t\tchar_t c = the_line->text[j];\n\t\t\toff += to_eight(c.codepoint, &temp_buffer[off]);\n\t\t}\n\t}\n\n\tif (rline_scroll < rline_history_count) {\n\t\trline_scroll++;\n\n\t\t/* Copy in from history */\n\t\tthe_line->actual = 0;\n\t\tcolumn = 0;\n\t\tloading = 1;\n\t\tunsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll);\n\t\tuint32_t istate = 0, c = 0;\n\t\tfor (unsigned int i = 0; i < strlen((char *)buf); ++i) {\n\t\t\tif (!decode(&istate, &c, buf[i])) {\n\t\t\t\tinsert_char(c);\n\t\t\t}\n\t\t}\n\t\tloading = 0;\n\t}\n\t/* Set cursor at end */\n\tcolumn = the_line->actual;\n\toffset = 0;\n\trecalculate_tabs(the_line);\n\trecalculate_syntax(the_line);\n\trender_line();\n\trline_place_cursor();\n}\n\n/**\n * Cycle to next history entry\n */\nstatic void history_next(void) {\n\tif (rline_scroll >= 1) {\n\t\tunsigned char * buf;\n\t\tif (rline_scroll > 1) buf = (unsigned char *)rline_history_prev(rline_scroll-1);\n\t\telse buf = (unsigned char *)temp_buffer;\n\t\trline_scroll--;\n\n\t\t/* Copy in from history */\n\t\tthe_line->actual = 0;\n\t\tcolumn = 0;\n\t\tloading = 1;\n\t\tuint32_t istate = 0, c = 0;\n\t\tfor (unsigned int i = 0; i < strlen((char *)buf); ++i) {\n\t\t\tif (!decode(&istate, &c, buf[i])) {\n\t\t\t\tinsert_char(c);\n\t\t\t}\n\t\t}\n\t\tloading = 0;\n\t}\n\t/* Set cursor at end */\n\tcolumn = the_line->actual;\n\toffset = 0;\n\trecalculate_tabs(the_line);\n\trecalculate_syntax(the_line);\n\trender_line();\n\trline_place_cursor();\n}\n\n/**\n * Handle escape sequences (arrow keys, etc.)\n */\nstatic int handle_escape(int * this_buf, int * timeout, int c) {\n\tif (*timeout >=  1 && this_buf[*timeout-1] == '\\033' && c == '\\033') {\n\t\tthis_buf[0]= c;\n\t\t*timeout = 1;\n\t\treturn 1;\n\t}\n\tif (*timeout >= 1 && this_buf[*timeout-1] == '\\033' && c == '[') {\n\t\t*timeout = 1;\n\t\tthis_buf[*timeout] = c;\n\t\t(*timeout)++;\n\t\treturn 0;\n\t}\n\tif (*timeout >= 2 && this_buf[0] == '\\033' && this_buf[1] == '[' &&\n\t\t\t(isdigit(c) || c == ';')) {\n\t\tthis_buf[*timeout] = c;\n\t\t(*timeout)++;\n\t\treturn 0;\n\t}\n\tif (*timeout >= 2 && this_buf[0] == '\\033' && this_buf[1] == '[') {\n\t\tswitch (c) {\n\t\t\tcase 'A': // up\n\t\t\t\thistory_previous();\n\t\t\t\tbreak;\n\t\t\tcase 'B': // down\n\t\t\t\thistory_next();\n\t\t\t\tbreak;\n\t\t\tcase 'C': // right\n\t\t\t\tif (this_buf[*timeout-1] == '5') {\n\t\t\t\t\tword_right();\n\t\t\t\t} else {\n\t\t\t\t\tcursor_right();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'D': // left\n\t\t\t\tif (this_buf[*timeout-1] == '5') {\n\t\t\t\t\tword_left();\n\t\t\t\t} else {\n\t\t\t\t\tcursor_left();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'H': // home\n\t\t\t\tcursor_home();\n\t\t\t\tbreak;\n\t\t\tcase 'F': // end\n\t\t\t\tcursor_end();\n\t\t\t\tbreak;\n\t\t\tcase '~':\n\t\t\t\tswitch (this_buf[*timeout-1]) {\n\t\t\t\t\tcase '1':\n\t\t\t\t\t\tcursor_home();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '3':\n\t\t\t\t\t\t/* Delete forward */\n\t\t\t\t\t\tif (column < the_line->actual) {\n\t\t\t\t\t\t\tline_delete(the_line, column+1);\n\t\t\t\t\t\t\tif (offset > 0) offset--;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '4':\n\t\t\t\t\t\tcursor_end();\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t\t*timeout = 0;\n\t\treturn 0;\n\t}\n\n\t*timeout = 0;\n\treturn 0;\n}\n\n#ifndef _WIN32\nstatic unsigned int _INTR, _EOF;\nstatic struct termios old;\nstatic void set_unbuffered(void) {\n\ttcgetattr(STDOUT_FILENO, &old);\n\t_INTR = old.c_cc[VINTR];\n\t_EOF  = old.c_cc[VEOF];\n\tstruct termios new = old;\n\tnew.c_lflag &= (~ICANON & ~ECHO & ~ISIG);\n\ttcsetattr(STDOUT_FILENO, TCSADRAIN, &new);\n\tif (wcwidth(0x3042) != 2) setlocale(LC_CTYPE, \"\");\n}\n\nstatic void set_buffered(void) {\n\ttcsetattr(STDOUT_FILENO, TCSADRAIN, &old);\n}\n#else\nstatic unsigned int _INTR = 3;\nstatic unsigned int _EOF  = 4;\nstatic void set_unbuffered(void) {\n\t/* Disables line input, echo, ^C processing, and a few others. */\n\tSetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);\n\tSetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT);\n\tsetlocale(LC_CTYPE, \"C.UTF-8\");\n}\nstatic void set_buffered(void) {\n\t/* These are the defaults */\n\tSetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),\n\t\tENABLE_ECHO_INPUT |\n\t\tENABLE_INSERT_MODE |\n\t\tENABLE_LINE_INPUT |\n\t\tENABLE_MOUSE_INPUT |\n\t\tENABLE_PROCESSED_INPUT |\n\t\tENABLE_QUICK_EDIT_MODE |\n\t\tENABLE_VIRTUAL_TERMINAL_INPUT\n\t);\n}\n#endif\n\nstatic int tabbed;\n\nstatic void dummy_redraw(rline_context_t * context) {\n\t/* Do nothing */\n}\n\n/**\n * Juggle our buffer with an rline context so we can\n * call original rline functions such as a tab-completion callback\n * or reverse search.\n */\nstatic void call_rline_func(rline_callback_t func, rline_context_t * context) {\n\t/* Unicode parser state */\n\tuint32_t istate = 0;\n\tuint32_t c;\n\n\t/* Don't let rline draw things */\n\tcontext->quiet = 1;\n\n\t/* Allocate a temporary buffer */\n\tcontext->buffer = malloc(buf_size_max);\n\tmemset(context->buffer,0,buf_size_max);\n\n\t/* Convert current data to utf-8 */\n\tunsigned int off = 0;\n\tfor (int j = 0; j < the_line->actual; j++) {\n\t\tif (j == column) {\n\t\t\t/* Track cursor position */\n\t\t\tcontext->offset = off;\n\t\t}\n\t\tchar_t c = the_line->text[j];\n\t\toff += to_eight(c.codepoint, &context->buffer[off]);\n\t}\n\n\t/* If the cursor was at the end, the loop above didn't catch it */\n\tif (column == the_line->actual) context->offset = off;\n\n\t/*\n\t * Did we just press tab before this? This is actually managed\n\t * by the tab-completion function.\n\t */\n\tcontext->tabbed = tabbed;\n\n\t/* Empty callbacks */\n\trline_callbacks_t tmp = {0};\n\t/*\n\t * Because some clients expect this to be set...\n\t * (we don't need it, we'll redraw ourselves later)\n\t */\n\ttmp.redraw_prompt = dummy_redraw;\n\n\t/* Setup context */\n\tcontext->callbacks = &tmp;\n\tcontext->collected = off;\n\tcontext->buffer[off] = '\\0';\n\tcontext->requested = 1024;\n\n\t/* Reset colors (for tab completion candidates, etc. */\n\tprintf(\"\\033[0m\");\n\n\t/* Call the function */\n\tfunc(context);\n\n\t/* Now convert back */\n\tloading = 1;\n\tint final_column = 0;\n\tthe_line->actual = 0;\n\tcolumn = 0;\n\tistate = 0;\n\tfor (int i = 0; i < context->collected; ++i) {\n\t\tif (i == context->offset) {\n\t\t\tfinal_column = column;\n\t\t}\n\t\tif (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) {\n\t\t\tinsert_char(c);\n\t\t}\n\t}\n\n\tfree(context->buffer);\n\n\t/* Position cursor */\n\tif (context->offset == context->collected) {\n\t\tcolumn = the_line->actual;\n\t} else {\n\t\tcolumn = final_column;\n\t}\n\ttabbed = context->tabbed;\n\tloading = 0;\n\n\t/* Recalculate + redraw */\n\trecalculate_tabs(the_line);\n\trecalculate_syntax(the_line);\n\trender_line();\n\trline_place_cursor();\n}\n\nstatic int reverse_search(void) {\n\t/* Store state */\n\tchar * old_prompt = prompt;\n\tint old_prompt_width = prompt_width;\n\tint old_prompt_width_calc = prompt_width_calc;\n\tline_t * old_line = the_line;\n\n\tchar buffer[1024] = {0};\n\tunsigned int off = 0;\n\n\tthe_line = NULL;\n\n\tprompt = \"(r-search) \";\n\tprompt_width = strlen(prompt);\n\tprompt_width_calc = prompt_width;\n\n\tint cin, timeout = 0;\n\tuint32_t c = 0, istate = 0;\n\n\tint start_at = 0;\n\tint retval = 0;\n\n\twhile (1) {\n\t\t_next: (void)0;\n\n\t\toff = 0;\n\t\tbuffer[0] = '\\0';\n\t\tfor (int j = 0; j < old_line->actual; j++) {\n\t\t\tbuffer[off] = '\\0';\n\t\t\tchar_t c = old_line->text[j];\n\t\t\toff += to_eight(c.codepoint, &buffer[off]);\n\t\t}\n\n\t\tif (the_line) free(the_line);\n\t\tthe_line = line_create();\n\n\t\tint match_offset = 0;\n\n\t\tif (off) {\n\t\t\tfor (int i = start_at; i < rline_history_count; ++i) {\n\t\t\t\tchar * buf= rline_history_prev(i+1);\n\t\t\t\tchar * match = strstr(buf, buffer);\n\t\t\t\tif (match) {\n\t\t\t\t\tmatch_offset = i;\n\t\t\t\t\tcolumn = 0;\n\t\t\t\t\tloading = 1;\n\t\t\t\t\tuint32_t istate = 0, c = 0;\n\t\t\t\t\tint invert_start = 0;\n\t\t\t\t\tfor (unsigned int i = 0; i < strlen((char *)buf); ++i) {\n\t\t\t\t\t\tif (match == &buf[i]) invert_start = the_line->actual;\n\t\t\t\t\t\tif (!decode(&istate, &c, buf[i])) {\n\t\t\t\t\t\t\tinsert_char(c);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tloading = 0;\n\t\t\t\t\toffset = 0;\n\t\t\t\t\trecalculate_tabs(the_line);\n\t\t\t\t\trecalculate_syntax(the_line);\n\t\t\t\t\tfor (int i = 0; i < old_line->actual; ++i) {\n\t\t\t\t\t\tthe_line->text[invert_start+i].flags |= FLAG_SELECT;\n\t\t\t\t\t}\n\t\t\t\t\tcolumn = invert_start;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trender_line();\n\n\t\tif (the_line->actual == 0) {\n\t\t\toffset = 0;\n\t\t\tcolumn = 0;\n\t\t\trline_place_cursor();\n\t\t\tset_fg_color(COLOR_ALT_FG);\n\t\t\tprintf(\"%s\", buffer);\n\t\t\tfflush(stdout);\n\t\t}\n\n\t\twhile ((cin = getch(timeout))) {\n\t\t\tif (cin == -1) continue;\n\t\t\tif (!decode(&istate, &c, cin)) {\n\t\t\t\tif (_INTR && c == _INTR) {\n\t\t\t\t\tgoto _done;\n\t\t\t\t}\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase '\\033':\n\t\t\t\t\t\thave_unget = '\\033';\n\t\t\t\t\t\tgoto _done;\n\t\t\t\t\tcase DELETE_KEY:\n\t\t\t\t\tcase BACKSPACE_KEY:\n\t\t\t\t\t\tline_delete(old_line, old_line->actual);\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\tcase 13:\n\t\t\t\t\tcase ENTER_KEY:\n\t\t\t\t\t\tretval = 1;\n\t\t\t\t\t\tgoto _done;\n\t\t\t\t\tcase 18:\n\t\t\t\t\t\tstart_at = match_offset + 1;\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\tdefault: {\n\t\t\t\t\t\tchar_t _c;\n\t\t\t\t\t\t_c.codepoint = c;\n\t\t\t\t\t\t_c.flags = 0;\n\t\t\t\t\t\t_c.display_width = codepoint_width(c);\n\t\t\t\t\t\told_line = line_insert(old_line, _c, old_line->actual);\n\t\t\t\t\t\tgoto _next;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n_done:\n\tfree(old_line);\n\tprompt = old_prompt;\n\tprompt_width = old_prompt_width;\n\tprompt_width_calc = old_prompt_width_calc;\n\toffset = 0;\n\trender_line();\n\trline_place_cursor();\n\treturn retval;\n}\n\n/**\n * Perform actual interactive line editing.\n *\n * This is mostly a reimplementation of bim's\n * INSERT mode, but with some cleanups and fixes\n * to work on a single line and to add some new\n * key bindings we don't have in bim.\n */\nstatic int read_line(void) {\n\tint cin;\n\tuint32_t c = 0;\n\tint timeout = 0;\n\tint this_buf[20];\n\tuint32_t istate = 0;\n\n\t/* Let's disable this under Windows... */\n\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\tfprintf(stdout, \"◄\\033[0m\"); /* TODO: This could be retrieved from an envvar */\n\tfor (int i = 0; i < rline_terminal_width - 1; ++i) {\n\t\tfprintf(stdout, \" \");\n\t}\n\n\tif (rline_preload) {\n\t\tchar * c = rline_preload;\n\t\twhile (*c) {\n\t\t\tinsert_char(*c);\n\t\t\tc++;\n\t\t}\n\t\tfree(rline_preload);\n\t\trline_preload = NULL;\n\t}\n\n\trender_line();\n\trline_place_cursor();\n\n\twhile ((cin = getch(timeout))) {\n\t\tif (cin == -1) continue;\n\t\tget_size();\n\t\tif (!decode(&istate, &c, cin)) {\n\t\t\tif (timeout == 0) {\n\t\t\t\tif (c != '\\t') tabbed = 0;\n\t\t\t\tif (_INTR && c == _INTR) {\n\t\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\t\tprintf(\"^%c\", (int)('@' + c));\n\t\t\t\t\tprintf(\"\\033[0m\");\n\t\t\t\t\tloading = 1;\n\t\t\t\t\tthe_line->actual = 0;\n\t\t\t\t\tcolumn = 0;\n\t\t\t\t\tinsert_char('\\n');\n\t\t\t\t\traise(SIGINT);\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tif (_EOF && c == _EOF) {\n\t\t\t\t\tif (column == 0 && the_line->actual == 0) {\n\t\t\t\t\t\tfor (char *_c = rline_exit_string; *_c; ++_c) {\n\t\t\t\t\t\t\tinsert_char(*_c);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tredraw_matching_paren(-1);\n\t\t\t\t\t\trender_line();\n\t\t\t\t\t\trline_place_cursor();\n\t\t\t\t\t\tif (!*rline_exit_string) {\n\t\t\t\t\t\t\tset_colors(COLOR_ALT_FG, COLOR_ALT_BG);\n\t\t\t\t\t\t\tprintf(\"^D\\033[0m\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t} else { /* Otherwise act like delete */\n\t\t\t\t\t\tif (column < the_line->actual) {\n\t\t\t\t\t\t\tline_delete(the_line, column+1);\n\t\t\t\t\t\t\tif (offset > 0) offset--;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase '\\033':\n\t\t\t\t\t\tif (timeout == 0) {\n\t\t\t\t\t\t\tthis_buf[timeout] = c;\n\t\t\t\t\t\t\ttimeout++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase DELETE_KEY:\n\t\t\t\t\tcase BACKSPACE_KEY:\n\t\t\t\t\t\tsmart_backspace();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 13:\n\t\t\t\t\tcase ENTER_KEY:\n\t\t\t\t\t\t/* Finished */\n\t\t\t\t\t\tloading = 1;\n\t\t\t\t\t\tcolumn = the_line->actual;\n\t\t\t\t\t\tredraw_matching_paren(-1);\n\t\t\t\t\t\trender_line();\n\t\t\t\t\t\tinsert_char('\\n');\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\tcase 1: /* ^A */\n\t\t\t\t\t\tcursor_home();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5: /* ^E */\n\t\t\t\t\t\tcursor_end();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 22: /* ^V */\n\t\t\t\t\t\t/* Don't bother with unicode, just take the next byte */\n\t\t\t\t\t\trline_place_cursor();\n\t\t\t\t\t\tprintf(\"^\\b\");\n\t\t\t\t\t\tinsert_char(getc(stdin));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 23: /* ^W */\n\t\t\t\t\t\tdelete_word();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 18: /* ^R - Begin reverse search */\n\t\t\t\t\t\tif (reverse_search()) {\n\t\t\t\t\t\t\tloading = 1;\n\t\t\t\t\t\t\tcolumn = the_line->actual;\n\t\t\t\t\t\t\trecalculate_syntax(the_line);\n\t\t\t\t\t\t\trender_line();\n\t\t\t\t\t\t\tinsert_char('\\n');\n\t\t\t\t\t\t\treturn 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 12: /* ^L - Repaint the whole screen */\n\t\t\t\t\t\tprintf(\"\\033[2J\\033[H\");\n\t\t\t\t\t\trender_line();\n\t\t\t\t\t\trline_place_cursor();\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 11: /* ^K - Clear to end */\n\t\t\t\t\t\tthe_line->actual = column;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 21: /* ^U - Kill to beginning */\n\t\t\t\t\t\twhile (column) {\n\t\t\t\t\t\t\tdelete_at_cursor();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase '\\t':\n\t\t\t\t\t\tif ((syntax && syntax->tabIndents) && (column == 0 || the_line->text[column-1].codepoint == ' ')) {\n\t\t\t\t\t\t\t/* Insert tab character */\n\t\t\t\t\t\t\tinsert_char(' ');\n\t\t\t\t\t\t\tinsert_char(' ');\n\t\t\t\t\t\t\tinsert_char(' ');\n\t\t\t\t\t\t\tinsert_char(' ');\n\t\t\t\t\t\t} else if (tab_complete_func) {\n\t\t\t\t\t\t\t/* Tab complete */\n\t\t\t\t\t\t\trline_context_t context = {0};\n\t\t\t\t\t\t\tcall_rline_func(tab_complete_func, &context);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tinsert_char(c);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (handle_escape(this_buf,&timeout,c)) {\n\t\t\t\t\trender_line();\n\t\t\t\t\trline_place_cursor();\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\thighlight_matching_paren();\n\t\t\trender_line();\n\t\t\trline_place_cursor();\n\t\t} else if (istate == UTF8_REJECT) {\n\t\t\tistate = 0;\n\t\t}\n\t}\n\treturn 0;\n}\n\n/**\n * Read a line of text with interactive editing.\n */\nint rline(char * buffer, int buf_size) {\n\tset_unbuffered();\n\tget_size();\n\n\tcolumn = 0;\n\toffset = 0;\n\tbuf_size_max = buf_size;\n\n\tchar * theme = getenv(\"RLINE_THEME\");\n\tif (theme && !strcmp(theme,\"sunsmoke\")) { /* TODO bring back theme tables */\n\t\trline_exp_load_colorscheme_sunsmoke();\n\t} else {\n\t\trline_exp_load_colorscheme_default();\n\t}\n\n\tthe_line = line_create();\n\tloading = 0;\n\tread_line();\n\tprintf(\"\\r\\033[?25h\\033[0m\\n\");\n\n\tunsigned int off = 0;\n\tfor (int j = 0; j < the_line->actual; j++) {\n\t\tchar_t c = the_line->text[j];\n\t\toff += to_eight(c.codepoint, &buffer[off]);\n\t}\n\n\tfree(the_line);\n\n\tset_buffered();\n\n\treturn strlen(buffer);\n}\n\nvoid rline_insert(rline_context_t * context, const char * what) {\n\tsize_t insertion_length = strlen(what);\n\n\tif (context->collected + (int)insertion_length > context->requested) {\n\t\tinsertion_length = context->requested - context->collected;\n\t}\n\n\t/* Move */\n\tmemmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset);\n\tmemcpy(&context->buffer[context->offset], what, insertion_length);\n\tcontext->collected += insertion_length;\n\tcontext->offset += insertion_length;\n}\n"
  },
  {
    "path": "lib/rline_exp.c",
    "content": "/**\n * @brief Dummy library to provide rline to Python, but\n *        our Python port is currently on hold.\n *\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * @copyright 2018-2021 K. Lange\n */\n#include <string.h>\n#include <stdlib.h>\n#include <toaru/rline.h>\n\nvoid * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) {\n\n\trline_exp_set_prompts(prompt, \"\", strlen(prompt), 0);\n\n\tchar * buf = malloc(1024);\n\tmemset(buf, 0, 1024);\n\n\trline_exp_set_syntax(\"python\");\n\trline_exit_string = \"\";\n\trline(buf, 1024);\n\trline_history_insert(strdup(buf));\n\trline_scroll = 0;\n\n\treturn buf;\n}\n\nchar * rline_exit_string;\nint rline_history_count;\n"
  },
  {
    "path": "lib/termemu.c",
    "content": "/**\n * @brief Terrible little ANSI escape parser.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <stdlib.h>\n\n#include <math.h>\n#include <string.h>\n#include <stdio.h>\n\n#include <toaru/graphics.h>\n#include <toaru/termemu.h>\n\n#include <toaru/spinlock.h>\n\n#define MAX_ARGS 1024\n\nstatic wchar_t box_chars[] = L\"▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥\";\n\n/* Returns the lower of two shorts */\nstatic uint16_t min(uint16_t a, uint16_t b) {\n\treturn (a < b) ? a : b;\n}\n\n/* Returns the higher of two shorts */\nstatic uint16_t max(uint16_t a, uint16_t b) {\n\treturn (a > b) ? a : b;\n}\n\n/* Write the contents of the buffer, as they were all non-escaped data. */\nstatic void ansi_dump_buffer(term_state_t * s) {\n\tfor (int i = 0; i < s->buflen; ++i) {\n\t\ts->callbacks->writer(s->buffer[i]);\n\t}\n}\n\n/* Add to the internal buffer for the ANSI parser */\nstatic void ansi_buf_add(term_state_t * s, char c) {\n\tif (s->buflen >= TERM_BUF_LEN-1) return;\n\ts->buffer[s->buflen] = c;\n\ts->buflen++;\n\ts->buffer[s->buflen] = '\\0';\n}\n\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\n\nstatic void _ansi_put(term_state_t * s, char c) {\n\tterm_callbacks_t * callbacks = s->callbacks;\n\tswitch (s->escape) {\n\t\tcase 0:\n\t\t\t/* We are not escaped, check for escape character */\n\t\t\tif (c == ANSI_ESCAPE) {\n\t\t\t\t/*\n\t\t\t\t * Enable escape mode, setup a buffer,\n\t\t\t\t * fill the buffer, get out of here.\n\t\t\t\t */\n\t\t\t\ts->escape    = 1;\n\t\t\t\ts->buflen    = 0;\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t\treturn;\n\t\t\t} else if (c == 0) {\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\tif (s->box && c >= 'a' && c <= 'z') {\n\t\t\t\t\tchar buf[7];\n\t\t\t\t\tchar *w = (char *)&buf;\n\t\t\t\t\tto_eight(box_chars[c-'a'], w);\n\t\t\t\t\twhile (*w) {\n\t\t\t\t\t\tcallbacks->writer(*w);\n\t\t\t\t\t\tw++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tcallbacks->writer(c);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 1:\n\t\t\t/* We're ready for [ */\n\t\t\tif (c == ANSI_BRACKET) {\n\t\t\t\ts->escape = 2;\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t} else if (c == ANSI_BRACKET_RIGHT) {\n\t\t\t\ts->escape = 3;\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t} else if (c == ANSI_OPEN_PAREN) {\n\t\t\t\ts->escape = 4;\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t} else if (c == 'T') {\n\t\t\t\ts->escape = 5;\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t} else if (c == '7') {\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t\ts->save_x = callbacks->get_csr_x();\n\t\t\t\ts->save_y = callbacks->get_csr_y();\n\t\t\t} else if (c == '8') {\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t\tcallbacks->set_csr(s->save_x, s->save_y);\n\t\t\t} else if (c == 'c') {\n\t\t\t\t/*\n\t\t\t\t * \"Full reset\"\n\t\t\t\t * First, reset anything we own. Then call a callback\n\t\t\t\t * to inform the app to reset things it owns (buffers, cursors).\n\t\t\t\t */\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t\ts->mouse_on = 0;\n\t\t\t\ts->save_x = 0;\n\t\t\t\ts->save_y = 0;\n\t\t\t\ts->box = 0;\n\t\t\t\ts->fg = TERM_DEFAULT_FG;\n\t\t\t\ts->bg = TERM_DEFAULT_BG;\n\t\t\t\ts->flags = TERM_DEFAULT_FLAGS;\n\t\t\t\tif (callbacks->full_reset) callbacks->full_reset();\n\t\t\t} else {\n\t\t\t\t/* This isn't a bracket, we're not actually escaped!\n\t\t\t\t * Get out of here! */\n\t\t\t\tansi_dump_buffer(s);\n\t\t\t\tcallbacks->writer(c);\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tif (c >= ANSI_LOW && c <= ANSI_HIGH) {\n\t\t\t\t/* Woah, woah, let's see here. */\n\t\t\t\tchar * pch;  /* tokenizer pointer */\n\t\t\t\tchar * save; /* strtok_r pointer */\n\t\t\t\tchar * argv[MAX_ARGS]; /* escape arguments */\n\t\t\t\t/* Get rid of the front of the buffer */\n\t\t\t\tstrtok_r(s->buffer,\"[\",&save);\n\t\t\t\tpch = strtok_r(NULL,\";\",&save);\n\t\t\t\t/* argc = Number of arguments, obviously */\n\t\t\t\tint argc = 0;\n\t\t\t\twhile (pch != NULL) {\n\t\t\t\t\targv[argc] = (char *)pch;\n\t\t\t\t\t++argc;\n\t\t\t\t\tif (argc > MAX_ARGS)\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tpch = strtok_r(NULL,\";\",&save);\n\t\t\t\t}\n\t\t\t\t/* Alright, let's do this */\n\t\t\t\tswitch (c) {\n\t\t\t\t\tcase ANSI_EXT_IOCTL:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\tint arg = atoi(argv[0]);\n\t\t\t\t\t\t\t\tswitch (arg) {\n\t\t\t\t\t\t\t\t\tcase 1:\n\t\t\t\t\t\t\t\t\t\tcallbacks->redraw_cursor();\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_SCP:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ts->save_x = callbacks->get_csr_x();\n\t\t\t\t\t\t\ts->save_y = callbacks->get_csr_y();\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_RCP:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcallbacks->set_csr(s->save_x, s->save_y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_SGR:\n\t\t\t\t\t\t/* Set Graphics Rendition */\n\t\t\t\t\t\tif (argc == 0) {\n\t\t\t\t\t\t\t/* Default = 0 */\n\t\t\t\t\t\t\targv[0] = \"0\";\n\t\t\t\t\t\t\targc    = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor (int i = 0; i < argc; ++i) {\n\t\t\t\t\t\t\tint arg = atoi(argv[i]);\n\t\t\t\t\t\t\tif (arg >= 100 && arg < 110) {\n\t\t\t\t\t\t\t\t/* Bright background */\n\t\t\t\t\t\t\t\ts->bg = 8 + (arg - 100);\n\t\t\t\t\t\t\t\ts->flags |= ANSI_SPECBG;\n\t\t\t\t\t\t\t} else if (arg >= 90 && arg < 100) {\n\t\t\t\t\t\t\t\t/* Bright foreground */\n\t\t\t\t\t\t\t\ts->fg = 8 + (arg - 90);\n\t\t\t\t\t\t\t} else if (arg >= 40 && arg < 49) {\n\t\t\t\t\t\t\t\t/* Set background */\n\t\t\t\t\t\t\t\ts->bg = arg - 40;\n\t\t\t\t\t\t\t\ts->flags |= ANSI_SPECBG;\n\t\t\t\t\t\t\t} else if (arg == 49) {\n\t\t\t\t\t\t\t\ts->bg = TERM_DEFAULT_BG;\n\t\t\t\t\t\t\t\ts->flags &= ~ANSI_SPECBG;\n\t\t\t\t\t\t\t} else if (arg >= 30 && arg < 39) {\n\t\t\t\t\t\t\t\t/* Set Foreground */\n\t\t\t\t\t\t\t\ts->fg = arg - 30;\n\t\t\t\t\t\t\t} else if (arg == 39) {\n\t\t\t\t\t\t\t\t/* Default Foreground */\n\t\t\t\t\t\t\t\ts->fg = 7;\n\t\t\t\t\t\t\t} else if (arg == 24) {\n\t\t\t\t\t\t\t\t/* Underline off */\n\t\t\t\t\t\t\t\ts->flags &= ~ANSI_UNDERLINE;\n\t\t\t\t\t\t\t} else if (arg == 23) {\n\t\t\t\t\t\t\t\t/* Oblique off */\n\t\t\t\t\t\t\t\ts->flags &= ~ANSI_ITALIC;\n\t\t\t\t\t\t\t} else if (arg == 21 || arg == 22) {\n\t\t\t\t\t\t\t\t/* Bold off */\n\t\t\t\t\t\t\t\ts->flags &= ~ANSI_BOLD;\n\t\t\t\t\t\t\t} else if (arg == 9) {\n\t\t\t\t\t\t\t\t/* X-OUT */\n\t\t\t\t\t\t\t\ts->flags |= ANSI_CROSS;\n\t\t\t\t\t\t\t} else if (arg == 7) {\n\t\t\t\t\t\t\t\t/* INVERT: Swap foreground / background */\n\t\t\t\t\t\t\t\tuint32_t temp = s->fg;\n\t\t\t\t\t\t\t\ts->fg = s->bg;\n\t\t\t\t\t\t\t\ts->bg = temp;\n\t\t\t\t\t\t\t} else if (arg == 6) {\n\t\t\t\t\t\t\t\t/* proprietary RGBA color support */\n\t\t\t\t\t\t\t\tif (i == 0) { break; }\n\t\t\t\t\t\t\t\tif (i < argc) {\n\t\t\t\t\t\t\t\t\tint r = atoi(argv[i+1]);\n\t\t\t\t\t\t\t\t\tint g = atoi(argv[i+2]);\n\t\t\t\t\t\t\t\t\tint b = atoi(argv[i+3]);\n\t\t\t\t\t\t\t\t\tint a = atoi(argv[i+4]);\n\t\t\t\t\t\t\t\t\tif (a == 0) a = 1; /* Override a = 0 */\n\t\t\t\t\t\t\t\t\tuint32_t c = rgba(r,g,b,a);\n\t\t\t\t\t\t\t\t\tif (atoi(argv[i-1]) == 48) {\n\t\t\t\t\t\t\t\t\t\ts->bg = c;\n\t\t\t\t\t\t\t\t\t\ts->flags |= ANSI_SPECBG;\n\t\t\t\t\t\t\t\t\t} else if (atoi(argv[i-1]) == 38) {\n\t\t\t\t\t\t\t\t\t\ts->fg = c;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ti += 4;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (arg == 5) {\n\t\t\t\t\t\t\t\t/* Supposed to be blink; instead, support X-term 256 colors */\n\t\t\t\t\t\t\t\tif (i == 0) { break; }\n\t\t\t\t\t\t\t\tif (i < argc) {\n\t\t\t\t\t\t\t\t\tif (atoi(argv[i-1]) == 48) {\n\t\t\t\t\t\t\t\t\t\t/* Background to i+1 */\n\t\t\t\t\t\t\t\t\t\ts->bg = atoi(argv[i+1]);\n\t\t\t\t\t\t\t\t\t\ts->flags |= ANSI_SPECBG;\n\t\t\t\t\t\t\t\t\t} else if (atoi(argv[i-1]) == 38) {\n\t\t\t\t\t\t\t\t\t\t/* Foreground to i+1 */\n\t\t\t\t\t\t\t\t\t\ts->fg = atoi(argv[i+1]);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t++i;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (arg == 4) {\n\t\t\t\t\t\t\t\t/* UNDERLINE */\n\t\t\t\t\t\t\t\ts->flags |= ANSI_UNDERLINE;\n\t\t\t\t\t\t\t} else if (arg == 3) {\n\t\t\t\t\t\t\t\t/* ITALIC: Oblique */\n\t\t\t\t\t\t\t\ts->flags |= ANSI_ITALIC;\n\t\t\t\t\t\t\t} else if (arg == 2) {\n\t\t\t\t\t\t\t\t/* Konsole RGB color support */\n\t\t\t\t\t\t\t\tif (i == 0) { break; }\n\t\t\t\t\t\t\t\tif (i < argc - 2) {\n\t\t\t\t\t\t\t\t\tint r = atoi(argv[i+1]);\n\t\t\t\t\t\t\t\t\tint g = atoi(argv[i+2]);\n\t\t\t\t\t\t\t\t\tint b = atoi(argv[i+3]);\n\t\t\t\t\t\t\t\t\tuint32_t c = rgb(r,g,b);\n\t\t\t\t\t\t\t\t\tif (atoi(argv[i-1]) == 48) {\n\t\t\t\t\t\t\t\t\t\t/* Background to i+1 */\n\t\t\t\t\t\t\t\t\t\ts->bg = c;\n\t\t\t\t\t\t\t\t\t\ts->flags |= ANSI_SPECBG;\n\t\t\t\t\t\t\t\t\t} else if (atoi(argv[i-1]) == 38) {\n\t\t\t\t\t\t\t\t\t\t/* Foreground to i+1 */\n\t\t\t\t\t\t\t\t\t\ts->fg = c;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\ti += 3;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if (arg == 1) {\n\t\t\t\t\t\t\t\t/* BOLD/BRIGHT: Brighten the output color */\n\t\t\t\t\t\t\t\ts->flags |= ANSI_BOLD;\n\t\t\t\t\t\t\t} else if (arg == 0) {\n\t\t\t\t\t\t\t\t/* Reset everything */\n\t\t\t\t\t\t\t\ts->fg = TERM_DEFAULT_FG;\n\t\t\t\t\t\t\t\ts->bg = TERM_DEFAULT_BG;\n\t\t\t\t\t\t\t\ts->flags = TERM_DEFAULT_FLAGS;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_SHOW:\n\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\tif (!strcmp(argv[0], \"?1049\")) {\n\t\t\t\t\t\t\t\tif (callbacks->switch_buffer) callbacks->switch_buffer(1);\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?1000\")) {\n\t\t\t\t\t\t\t\ts->mouse_on |= TERMEMU_MOUSE_ENABLE;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?1002\")) {\n\t\t\t\t\t\t\t\ts->mouse_on |= TERMEMU_MOUSE_DRAG;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?1006\")) {\n\t\t\t\t\t\t\t\ts->mouse_on |= TERMEMU_MOUSE_SGR;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?25\")) {\n\t\t\t\t\t\t\t\tcallbacks->set_csr_on(1);\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?2004\")) {\n\t\t\t\t\t\t\t\ts->paste_mode = 1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_HIDE:\n\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\tif (!strcmp(argv[0], \"?1049\")) {\n\t\t\t\t\t\t\t\tif (callbacks->switch_buffer) callbacks->switch_buffer(0);\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?1000\")) {\n\t\t\t\t\t\t\t\ts->mouse_on &= ~TERMEMU_MOUSE_ENABLE;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?1002\")) {\n\t\t\t\t\t\t\t\ts->mouse_on &= ~TERMEMU_MOUSE_DRAG;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0],\"?1006\")) {\n\t\t\t\t\t\t\t\ts->mouse_on &= ~TERMEMU_MOUSE_SGR;\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?25\")) {\n\t\t\t\t\t\t\t\tcallbacks->set_csr_on(0);\n\t\t\t\t\t\t\t} else if (!strcmp(argv[0], \"?2004\")) {\n\t\t\t\t\t\t\t\ts->paste_mode = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CUF:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint i = 1;\n\t\t\t\t\t\t\tif (argc) {\n\t\t\t\t\t\t\t\ti = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->set_csr(min(callbacks->get_csr_x() + i, s->width - 1), callbacks->get_csr_y());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CUU:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint i = 1;\n\t\t\t\t\t\t\tif (argc) {\n\t\t\t\t\t\t\t\ti = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->set_csr(callbacks->get_csr_x(), max(callbacks->get_csr_y() - i, 0));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CUD:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint i = 1;\n\t\t\t\t\t\t\tif (argc) {\n\t\t\t\t\t\t\t\ti = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->set_csr(callbacks->get_csr_x(), min(callbacks->get_csr_y() + i, s->height - 1));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CUB:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint i = 1;\n\t\t\t\t\t\t\tif (argc) {\n\t\t\t\t\t\t\t\ti = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->set_csr(max(callbacks->get_csr_x() - i,0), callbacks->get_csr_y());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CHA:\n\t\t\t\t\t\tif (argc < 1) {\n\t\t\t\t\t\t\tcallbacks->set_csr(0,callbacks->get_csr_y());\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcallbacks->set_csr(min(max(atoi(argv[0]), 1), s->width) - 1, callbacks->get_csr_y());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_CUP:\n\t\t\t\t\t\tif (argc < 2) {\n\t\t\t\t\t\t\tcallbacks->set_csr(0,0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcallbacks->set_csr(min(max(atoi(argv[1]), 1), s->width) - 1, min(max(atoi(argv[0]), 1), s->height) - 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_ED:\n\t\t\t\t\t\tif (argc < 1) {\n\t\t\t\t\t\t\tcallbacks->cls(0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcallbacks->cls(atoi(argv[0]));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_EL:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint what = 0, x = 0, y = 0;\n\t\t\t\t\t\t\tif (argc >= 1) {\n\t\t\t\t\t\t\t\twhat = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (what == 0) {\n\t\t\t\t\t\t\t\tx = callbacks->get_csr_x();\n\t\t\t\t\t\t\t\ty = s->width;\n\t\t\t\t\t\t\t} else if (what == 1) {\n\t\t\t\t\t\t\t\tx = 0;\n\t\t\t\t\t\t\t\ty = callbacks->get_csr_x();\n\t\t\t\t\t\t\t} else if (what == 2) {\n\t\t\t\t\t\t\t\tx = 0;\n\t\t\t\t\t\t\t\ty = s->width;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (int i = x; i < y; ++i) {\n\t\t\t\t\t\t\t\tcallbacks->set_cell(i, callbacks->get_csr_y(), ' ');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_DSR:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tchar out[27];\n\t\t\t\t\t\t\tsprintf(out, \"\\033[%d;%dR\", callbacks->get_csr_y() + 1, callbacks->get_csr_x() + 1);\n\t\t\t\t\t\t\tcallbacks->input_buffer_stuff(out);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_SU:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint how_many = 1;\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\thow_many = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->scroll(how_many);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_SD:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint how_many = 1;\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\thow_many = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->scroll(-how_many);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_IL:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint how_many = 1;\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\thow_many = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->insert_delete_lines(how_many);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase ANSI_DL:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint how_many = 1;\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\thow_many = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcallbacks->insert_delete_lines(-how_many);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'X':\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tint how_many = 1;\n\t\t\t\t\t\t\tif (argc > 0) {\n\t\t\t\t\t\t\t\thow_many = atoi(argv[0]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfor (int i = 0; i < how_many; ++i) {\n\t\t\t\t\t\t\t\tcallbacks->writer(' ');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'd':\n\t\t\t\t\t\tif (argc < 1) {\n\t\t\t\t\t\t\tcallbacks->set_csr(callbacks->get_csr_x(), 0);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcallbacks->set_csr(callbacks->get_csr_x(), atoi(argv[0]) - 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t/* Meh */\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t/* Set the states */\n\t\t\t\tif (s->flags & ANSI_BOLD && s->fg < 9) {\n\t\t\t\t\tcallbacks->set_color(s->fg % 8 + 8, s->bg);\n\t\t\t\t} else {\n\t\t\t\t\tcallbacks->set_color(s->fg, s->bg);\n\t\t\t\t}\n\t\t\t\t/* Clear out the buffer */\n\t\t\t\ts->buflen = 0;\n\t\t\t\ts->escape = 0;\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t/* Still escaped */\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tif (c == '\\007') {\n\t\t\t\t/* Tokenize on semicolons, like we always do */\n\t\t\t\tchar * pch;  /* tokenizer pointer */\n\t\t\t\tchar * save; /* strtok_r pointer */\n\t\t\t\tchar * argv[MAX_ARGS]; /* escape arguments */\n\t\t\t\t/* Get rid of the front of the buffer */\n\t\t\t\tstrtok_r(s->buffer,\"]\",&save);\n\t\t\t\tpch = strtok_r(NULL,\";\",&save);\n\t\t\t\t/* argc = Number of arguments, obviously */\n\t\t\t\tint argc = 0;\n\t\t\t\twhile (pch != NULL) {\n\t\t\t\t\targv[argc] = (char *)pch;\n\t\t\t\t\t++argc;\n\t\t\t\t\tif (argc > MAX_ARGS) break;\n\t\t\t\t\tpch = strtok_r(NULL,\";\",&save);\n\t\t\t\t}\n\t\t\t\t/* Start testing the first argument for what command to use */\n\t\t\t\tif (argv[0]) {\n\t\t\t\t\tif (!strcmp(argv[0], \"1\")) {\n\t\t\t\t\t\tif (argc > 1) {\n\t\t\t\t\t\t\tcallbacks->set_title(argv[1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t} /* Currently, no other options */\n\t\t\t\t}\n\t\t\t\t/* Clear out the buffer */\n\t\t\t\ts->buflen = 0;\n\t\t\t\ts->escape = 0;\n\t\t\t\treturn;\n\t\t\t} else {\n\t\t\t\t/* Still escaped */\n\t\t\t\tif (c == '\\n' || s->buflen == 255) {\n\t\t\t\t\tansi_dump_buffer(s);\n\t\t\t\t\tcallbacks->writer(c);\n\t\t\t\t\ts->buflen = 0;\n\t\t\t\t\ts->escape = 0;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tansi_buf_add(s, c);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 4:\n\t\t\tif (c == '0') {\n\t\t\t\ts->box = 1;\n\t\t\t} else if (c == 'B') {\n\t\t\t\ts->box = 0;\n\t\t\t} else {\n\t\t\t\tansi_dump_buffer(s);\n\t\t\t\tcallbacks->writer(c);\n\t\t\t}\n\t\t\ts->escape = 0;\n\t\t\ts->buflen = 0;\n\t\t\tbreak;\n\t\tcase 5:\n\t\t\tif (c == 'q') {\n\t\t\t\tchar out[24];\n\t\t\t\tsprintf(out, \"\\033T%d;%dq\", callbacks->get_cell_width(), callbacks->get_cell_height());\n\t\t\t\tcallbacks->input_buffer_stuff(out);\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t} else if (c == 's') {\n\t\t\t\ts->img_collected = 0;\n\t\t\t\ts->escape = 6;\n\t\t\t\ts->img_size = sizeof(uint32_t) * callbacks->get_cell_width() * callbacks->get_cell_height();\n\t\t\t\tif (!s->img_data) {\n\t\t\t\t\ts->img_data = malloc(s->img_size);\n\t\t\t\t}\n\t\t\t\tmemset(s->img_data, 0x00, s->img_size);\n\t\t\t} else {\n\t\t\t\tansi_dump_buffer(s);\n\t\t\t\tcallbacks->writer(c);\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 6:\n\t\t\ts->img_data[s->img_collected++] = c;\n\t\t\tif (s->img_collected == s->img_size) {\n\t\t\t\tcallbacks->set_cell_contents(callbacks->get_csr_x(), callbacks->get_csr_y(), s->img_data);\n\t\t\t\tcallbacks->set_csr(min(callbacks->get_csr_x() + 1, s->width - 1), callbacks->get_csr_y());\n\t\t\t\ts->escape = 0;\n\t\t\t\ts->buflen = 0;\n\t\t\t}\n\t\t\tbreak;\n\t}\n}\n\nvoid ansi_put(term_state_t * s, char c) {\n\tspin_lock(&s->lock);\n\t_ansi_put(s, c);\n\tspin_unlock(&s->lock);\n}\n\nterm_state_t * ansi_init(term_state_t * s, int w, int y, term_callbacks_t * callbacks_in) {\n\n\tif (!s) {\n\t\ts = malloc(sizeof(term_state_t));\n\t}\n\n\tmemset(s, 0x00, sizeof(term_state_t));\n\n\t/* Terminal Defaults */\n\ts->fg     = TERM_DEFAULT_FG;    /* Light grey */\n\ts->bg     = TERM_DEFAULT_BG;    /* Black */\n\ts->flags  = TERM_DEFAULT_FLAGS; /* Nothing fancy*/\n\ts->width  = w;\n\ts->height = y;\n\ts->box    = 0;\n\ts->callbacks = callbacks_in;\n\ts->callbacks->set_color(s->fg, s->bg);\n\ts->mouse_on = 0;\n\n\treturn s;\n}\n"
  },
  {
    "path": "lib/text.c",
    "content": "/**\n * @brief Toaru Text library - TrueType parser.\n * @file lib/text.c\n * @author K. Lange <klange@toaruos.org>\n *\n * Implementation of TrueType font file parsing and basic\n * glyph rendering.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/shm.h>\n\n#include <math.h>\n\n#include <toaru/graphics.h>\n#include <toaru/hashmap.h>\n#include <toaru/decodeutf8.h>\n#include <toaru/spinlock.h>\n\n#include \"toaru/text.h\"\n\n#undef min\n#define min(a,b) ((a) < (b) ? (a) : (b))\n\nstruct TT_Table {\n\toff_t offset;\n\tsize_t length;\n};\n\nstruct TT_Coord {\n\tfloat x;\n\tfloat y;\n};\n\nstruct TT_Line {\n\tstruct TT_Coord start;\n\tstruct TT_Coord end;\n};\n\nstruct TT_Contour {\n\tsize_t edgeCount;\n\tsize_t nextAlloc;\n\tsize_t flags;\n\tsize_t last_start;\n\tstruct TT_Line edges[];\n};\n\nstruct TT_Intersection {\n\tfloat x;\n\tint affect;\n};\n\nstruct TT_Edge {\n\tstruct TT_Coord start;\n\tstruct TT_Coord end;\n\tint direction;\n};\n\nstruct TT_Shape {\n\tsize_t edgeCount;\n\tint lastY;\n\tint startY;\n\tint lastX;\n\tint startX;\n\tstruct TT_Edge edges[];\n};\n\nstruct TT_Vertex {\n\tunsigned char flags;\n\tint x;\n\tint y;\n};\n\nstruct TT_Font {\n\tint privFlags;\n\tFILE * filePtr;\n\tuint8_t * buffer;\n\tuint8_t * memPtr;\n\n\tstruct TT_Table head_ptr;\n\tstruct TT_Table cmap_ptr;\n\tstruct TT_Table loca_ptr;\n\tstruct TT_Table glyf_ptr;\n\tstruct TT_Table hhea_ptr;\n\tstruct TT_Table hmtx_ptr;\n\tstruct TT_Table name_ptr;\n\tstruct TT_Table os_2_ptr;\n\n\toff_t cmap_start;\n\n\tsize_t cmap_maxInd;\n\n\tfloat scale;\n\tfloat emSize;\n\n\tint cmap_type;\n\tint loca_type;\n};\n\n/* Currently, the edge sorter is disabled. It doesn't really help much,\n * and it's very slow with our horrible qsort implementation. */\n#if 0\nstatic int edge_sorter_high_scanline(const void * a, const void * b) {\n\tconst struct TT_Edge * left  = a;\n\tconst struct TT_Edge * right = b;\n\n\tif (left->start.y < right->start.y) return -1;\n\tif (left->start.y > right->start.y) return 1;\n\treturn 0;\n}\n\nstatic void sort_edges(size_t edgeCount, struct TT_Edge edges[edgeCount]) {\n\tqsort(edges, edgeCount, sizeof(struct TT_Edge), edge_sorter_high_scanline);\n}\n#endif\n\nstatic int intersection_sorter(const void * a, const void * b) {\n\tconst struct TT_Intersection * left  = a;\n\tconst struct TT_Intersection * right = b;\n\n\tif (left->x < right->x) return -1;\n\tif (left->x > right->x) return 1;\n\treturn 0;\n}\n\nstatic inline void sort_intersections(size_t cnt, struct TT_Intersection intersections[cnt]) {\n\tqsort(intersections, cnt, sizeof(struct TT_Intersection), intersection_sorter);\n}\n\nstatic inline float edge_at(float y, const struct TT_Edge * edge) {\n\tfloat u = (y - edge->start.y) / (edge->end.y - edge->start.y);\n\treturn edge->start.x + u * (edge->end.x - edge->start.x);\n}\n\n__attribute__((hot))\nstatic inline size_t prune_edges(size_t edgeCount, float y, const struct TT_Edge edges[edgeCount], struct TT_Intersection into[edgeCount]) {\n\tsize_t outWriter = 0;\n\tfor (size_t i = 0; i < edgeCount; ++i) {\n\t\tif (y > edges[i].end.y || y <= edges[i].start.y) continue;\n\t\tinto[outWriter].x = edge_at(y,&edges[i]);\n\t\tinto[outWriter].affect = edges[i].direction;\n\t\toutWriter++;\n\t}\n\treturn outWriter;\n}\n\nstatic void process_scanline(\n\t\tfloat _y,\n\t\tconst struct TT_Shape * shape,\n\t\tsize_t subsample_width,\n\t\tfloat subsamples[subsample_width],\n\t\tsize_t cnt,\n\t\tconst struct TT_Intersection crosses[cnt]\n\t) {\n\tint wind = 0;\n\tsize_t j = 0;\n\tfor (int x = shape->startX; x < shape->lastX && j < cnt; ++x) {\n\t\twhile (j < cnt && x > crosses[j].x) {\n\t\t\twind += crosses[j].affect;\n\t\t\tj++;\n\t\t}\n\t\tfloat last = x;\n\t\twhile (j < cnt && (x+1) > crosses[j].x) {\n\t\t\tif (wind != 0) {\n\t\t\t\tsubsamples[x - shape->startX] += crosses[j].x - last;\n\t\t\t}\n\t\t\tlast = crosses[j].x;\n\t\t\twind += crosses[j].affect;\n\t\t\tj++;\n\t\t}\n\t\tif (wind != 0) {\n\t\t\tsubsamples[x - shape->startX] += (x+1) - last;\n\t\t}\n\t}\n}\n\nstatic inline uint32_t tt_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {\n\treturn (a << 24U) | (r << 16) | (g << 8) | (b);\n}\n\nstatic inline uint32_t tt_apply_alpha(uint32_t color, uint16_t alpha) {\n\tuint8_t r = ((uint32_t)(_RED(color) * alpha + 0x80) * 0x101) >> 16UL;\n\tuint8_t g = ((uint32_t)(_GRE(color) * alpha + 0x80) * 0x101) >> 16UL;\n\tuint8_t b = ((uint32_t)(_BLU(color) * alpha + 0x80) * 0x101) >> 16UL;\n\tuint8_t a = ((uint32_t)(_ALP(color) * alpha + 0x80) * 0x101) >> 16UL;\n\treturn tt_rgba(r,g,b,a);\n}\n\nstatic inline uint32_t tt_alpha_blend_rgba(uint32_t bottom, uint32_t top) {\n\tif (_ALP(bottom) == 0) return top;\n\tif (_ALP(top) == 255) return top;\n\tif (_ALP(top) == 0) return bottom;\n\tuint8_t a = _ALP(top);\n\tuint16_t t = 0xFF ^ a;\n\tuint8_t d_r = _RED(top) + (((uint32_t)(_RED(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_g = _GRE(top) + (((uint32_t)(_GRE(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_b = _BLU(top) + (((uint32_t)(_BLU(bottom) * t + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_a = _ALP(top) + (((uint32_t)(_ALP(bottom) * t + 0x80) * 0x101) >> 16UL);\n\treturn tt_rgba(d_r, d_g, d_b, d_a);\n}\n\n\nstatic void paint_scanline(gfx_context_t * ctx, int y, const struct TT_Shape * shape, float * subsamples, uint32_t color) {\n\tfor (int x = shape->startX < 0 ? 0 : shape->startX; x < shape->lastX && x < ctx->width; ++x) {\n\t\tuint16_t na = (int)(255 * subsamples[x - shape->startX]) >> 2;\n\t\tuint32_t nc = tt_apply_alpha(color, na);\n\t\tGFX(ctx, x, y) = tt_alpha_blend_rgba(GFX(ctx, x, y), nc);\n\t\tsubsamples[x-shape->startX] = 0;\n\t}\n}\n\nstatic inline int _is_in_clip(gfx_context_t * ctx, int32_t y) {\n\tif (!ctx->clips) return 1;\n\tif (y < 0 || y >= ctx->clips_size) return 1;\n\treturn ctx->clips[y];\n}\n\nvoid tt_path_paint(gfx_context_t * ctx, const struct TT_Shape * shape, uint32_t color) {\n\tsize_t size = shape->edgeCount;\n\tstruct TT_Intersection * crosses = malloc(sizeof(struct TT_Intersection) * size);\n\n\tsize_t subsample_width = shape->lastX - shape->startX;\n\tfloat * subsamples = malloc(sizeof(float) * subsample_width);\n\tmemset(subsamples, 0, sizeof(float) * subsample_width);\n\n\tint startY = shape->startY < 0 ? 0 : shape->startY;\n\tint endY = shape->lastY <= ctx->height ? shape->lastY : ctx->height;\n\n\tfor (int y = startY; y < endY; ++y) {\n\t\tif (!_is_in_clip(ctx,y)) continue;\n\t\tfloat _y = y + 0.0001;\n\t\tfor (int l = 0; l < 4; ++l) {\n\t\t\tsize_t cnt;\n\t\t\tif ((cnt = prune_edges(size, _y, shape->edges, crosses))) {\n\t\t\t\tsort_intersections(cnt, crosses);\n\t\t\t\tprocess_scanline(_y, shape, subsample_width, subsamples, cnt, crosses);\n\t\t\t}\n\t\t\t_y += 1.0/4.0;\n\t\t}\n\t\tpaint_scanline(ctx, y, shape, subsamples, color);\n\t}\n\n\tfree(subsamples);\n\tfree(crosses);\n}\n\nstruct TT_Contour * tt_contour_line_to(struct TT_Contour * shape, float x, float y) {\n\tif (shape->flags & 1) {\n\t\tshape->edges[shape->edgeCount].end.x = x;\n\t\tshape->edges[shape->edgeCount].end.y = y;\n\t\tshape->edgeCount++;\n\t\tshape->flags &= ~1;\n\t} else {\n\t\tif (shape->edgeCount + 1 == shape->nextAlloc) {\n\t\t\tshape->nextAlloc *= 2;\n\t\t\tshape = realloc(shape, sizeof(struct TT_Contour) + sizeof(struct TT_Line) * (shape->nextAlloc));\n\t\t}\n\t\tshape->edges[shape->edgeCount].start.x = shape->edges[shape->edgeCount-1].end.x;\n\t\tshape->edges[shape->edgeCount].start.y = shape->edges[shape->edgeCount-1].end.y;\n\t\tshape->edges[shape->edgeCount].end.x = x;\n\t\tshape->edges[shape->edgeCount].end.y = y;\n\t\tshape->edgeCount++;\n\t\tshape->flags &= ~1;\n\t}\n\treturn shape;\n}\n\nstruct TT_Contour * tt_contour_move_to(struct TT_Contour * shape, float x, float y) {\n\tif (!(shape->flags & 1) && shape->edgeCount) {\n\t\tshape = tt_contour_line_to(shape, shape->edges[shape->last_start].start.x, shape->edges[shape->last_start].start.y);\n\t}\n\tif (shape->edgeCount + 1 == shape->nextAlloc) {\n\t\tshape->nextAlloc *= 2;\n\t\tshape = realloc(shape, sizeof(struct TT_Contour) + sizeof(struct TT_Line) * (shape->nextAlloc));\n\t}\n\tshape->edges[shape->edgeCount].start.x = x;\n\tshape->edges[shape->edgeCount].start.y = y;\n\tshape->last_start = shape->edgeCount;\n\tshape->flags |= 1;\n\treturn shape;\n}\n\nstruct TT_Contour * tt_contour_start(float x, float y) {\n\tstruct TT_Contour * shape = malloc(sizeof(struct TT_Contour) + sizeof(struct TT_Line) * 2);\n\tshape->edgeCount = 0;\n\tshape->nextAlloc = 2;\n\tshape->flags = 0;\n\tshape->last_start = 0;\n\tshape->edges[shape->edgeCount].start.x = x;\n\tshape->edges[shape->edgeCount].start.y = y;\n\tshape->last_start = shape->edgeCount;\n\tshape->flags |= 1;\n\treturn shape;\n}\n\nstruct TT_Shape * tt_contour_finish(const struct TT_Contour * in) {\n\tsize_t size = in->edgeCount + 1;\n\tstruct TT_Shape * tmp = malloc(sizeof(struct TT_Shape) + sizeof(struct TT_Edge) * size);\n\tfor (size_t i = 0; i < in->edgeCount; ++i) {\n\t\tmemcpy(&tmp->edges[i], &in->edges[i], sizeof(struct TT_Line));\n\t}\n\n\tif (in->flags & 1) {\n\t\tsize--;\n\t} else {\n\t\ttmp->edges[in->edgeCount].start.x = in->edges[in->edgeCount-1].end.x;\n\t\ttmp->edges[in->edgeCount].start.y = in->edges[in->edgeCount-1].end.y;\n\t\ttmp->edges[in->edgeCount].end.x   = in->edges[in->last_start].start.x;\n\t\ttmp->edges[in->edgeCount].end.y   = in->edges[in->last_start].start.y;\n\t}\n\n\tfor (size_t i = 0; i < size; ++i) {\n\t\tif (tmp->edges[i].start.y < tmp->edges[i].end.y) {\n\t\t\ttmp->edges[i].direction = 1;\n\t\t} else {\n\t\t\ttmp->edges[i].direction = -1;\n\t\t\tstruct TT_Coord j = tmp->edges[i].start;\n\t\t\ttmp->edges[i].start = tmp->edges[i].end;\n\t\t\ttmp->edges[i].end = j;\n\t\t}\n\t}\n\n\t//sort_edges(size, tmp->edges);\n\ttmp->edgeCount = size;\n\ttmp->startY = INT_MAX;\n\ttmp->lastY = INT_MIN;\n\ttmp->startX = INT_MAX;\n\ttmp->lastX = INT_MIN;\n\tfor (size_t i = 0; i < size; ++i) {\n\t\tif (tmp->edges[i].end.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].end.y + 1;\n\t\tif (tmp->edges[i].start.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].start.y + 1;\n\t\tif (tmp->edges[i].end.y < tmp->startY) tmp->startY = tmp->edges[i].end.y;\n\t\tif (tmp->edges[i].start.y < tmp->startY) tmp->startY = tmp->edges[i].start.y;\n\n\t\tif (tmp->edges[i].end.x + 2 > tmp->lastX) tmp->lastX = tmp->edges[i].end.x + 2;\n\t\tif (tmp->edges[i].start.x + 2 > tmp->lastX) tmp->lastX = tmp->edges[i].start.x + 2;\n\t\tif (tmp->edges[i].end.x < tmp->startX) tmp->startX = tmp->edges[i].end.x;\n\t\tif (tmp->edges[i].start.x < tmp->startX) tmp->startX = tmp->edges[i].start.x;\n\t}\n\n\tif (tmp->lastY < tmp->startY) tmp->startY = tmp->lastY;\n\tif (tmp->lastX < tmp->startX) tmp->startX = tmp->lastX;\n\n\treturn tmp;\n}\n\nstatic inline int tt_seek(struct TT_Font * font, off_t offset) {\n\tif (font->privFlags & 1) {\n\t\treturn fseek(font->filePtr, offset, SEEK_SET);\n\t} else {\n\t\tfont->memPtr = font->buffer + offset;\n\t\treturn 0;\n\t}\n}\n\nstatic inline long tt_tell(struct TT_Font * font) {\n\tif (font->privFlags & 1) {\n\t\treturn ftell(font->filePtr);\n\t} else {\n\t\treturn font->memPtr - font->buffer;\n\t}\n}\n\nstatic inline uint8_t tt_read_8(struct TT_Font * font) {\n\tif (font->privFlags & 1) {\n\t\treturn fgetc(font->filePtr);\n\t} else {\n\t\treturn *(font->memPtr++);\n\t}\n}\n\nstatic inline uint32_t tt_read_32(struct TT_Font * font) {\n\tint a = tt_read_8(font);\n\tint b = tt_read_8(font);\n\tint c = tt_read_8(font);\n\tint d = tt_read_8(font);\n\tif (a < 0 || b < 0 || c < 0 || d < 0) return 0;\n\treturn ((a & 0xFF) << 24) |\n\t       ((b & 0xFF) << 16) |\n\t       ((c & 0xFF) << 8) |\n\t       ((d & 0xFF) << 0);\n}\n\nstatic inline uint16_t tt_read_16(struct TT_Font * font) {\n\tint a = tt_read_8(font);\n\tint b = tt_read_8(font);\n\tif (a < 0 || b < 0) return 0;\n\treturn ((a & 0xFF) << 8) |\n\t       ((b & 0xFF) << 0);\n}\n\nint tt_measure_font(struct TT_Font * font, struct TT_FontMetrics * metrics) {\n\tint a, d, l;\n\tif (font->os_2_ptr.offset) {\n\t\ttt_seek(font, font->os_2_ptr.offset + 2 * 37);\n\t\ta = (int16_t)tt_read_16(font);\n\t\td = -(int16_t)tt_read_16(font);\n\n\t\ttt_seek(font, font->hhea_ptr.offset + 2 * 4);\n\t\tl = (int16_t)tt_read_16(font);\n\t} else {\n\t\ttt_seek(font, font->hhea_ptr.offset + 2 * 2);\n\t\ta = (int16_t)tt_read_16(font);\n\t\td = (int16_t)tt_read_16(font);\n\t\tl = (int16_t)tt_read_16(font);\n\t}\n\n\tmetrics->ascender  = a * font->scale;\n\tmetrics->descender = d * font->scale;\n\tmetrics->lineGap   = l * font->scale;\n\n\treturn 0;\n}\n\nint tt_xadvance_for_glyph(struct TT_Font * font, unsigned int ind) {\n\ttt_seek(font, font->hhea_ptr.offset + 2 * 17);\n\tuint16_t numLong = tt_read_16(font);\n\n\tif (ind < numLong) {\n\t\ttt_seek(font, font->hmtx_ptr.offset + ind * 4);\n\t\treturn tt_read_16(font);\n\t}\n\n\ttt_seek(font, font->hmtx_ptr.offset + (numLong - 1) * 4);\n\treturn tt_read_16(font);\n}\n\nvoid tt_set_size(struct TT_Font * font, float size) {\n\tfont->scale = size / font->emSize;\n}\n\nvoid tt_set_size_px(struct TT_Font * font, float size) {\n\ttt_set_size(font, size * 4.0 / 3.0);\n}\n\noff_t tt_get_glyph_offset(struct TT_Font * font, unsigned int glyph) {\n\tif (font->loca_type == 0) {\n\t\ttt_seek(font, font->loca_ptr.offset + glyph * 2);\n\t\treturn tt_read_16(font) * 2;\n\t} else {\n\t\ttt_seek(font, font->loca_ptr.offset + glyph * 4);\n\t\treturn tt_read_32(font);\n\t}\n}\n\nint tt_glyph_for_codepoint(struct TT_Font * font, unsigned int codepoint) {\n\tif (font->cmap_type == 12) {\n\t\t/* Get group count */\n\t\ttt_seek(font, font->cmap_start + 4 + 8);\n\t\tuint32_t ngroups = tt_read_32(font);\n\n\t\tfor (unsigned int i = 0; i < ngroups; ++i) {\n\t\t\tuint32_t start = tt_read_32(font);\n\t\t\tuint32_t end   = tt_read_32(font);\n\t\t\tuint32_t ind   = tt_read_32(font);\n\n\t\t\tif (codepoint >= start && codepoint <= end) {\n\t\t\t\treturn ind + (codepoint - start);\n\t\t\t}\n\t\t}\n\t} else if (font->cmap_type == 4) {\n\t\tif (codepoint > 0xFFFF) return 0;\n\n\t\ttt_seek(font, font->cmap_start + 6);\n\t\tuint16_t segCount = tt_read_16(font) / 2;\n\n\t\tfor (int i = 0; i < segCount; ++i) {\n\t\t\ttt_seek(font, font->cmap_start + 12 + 2 * i);\n\t\t\tuint16_t endCode = tt_read_16(font);\n\t\t\tif (endCode >= codepoint) {\n\t\t\t\ttt_seek(font, font->cmap_start + 12 + 2 * segCount + 2 + 2 * i);\n\t\t\t\tuint16_t startCode = tt_read_16(font);\n\t\t\t\tif (startCode > codepoint) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\ttt_seek(font, font->cmap_start + 12 + 4 * segCount + 2 + 2 * i);\n\t\t\t\tint16_t idDelta = tt_read_16(font);\n\t\t\t\ttt_seek(font, font->cmap_start + 12 + 6 * segCount + 2 + 2 * i);\n\t\t\t\tuint16_t idRangeOffset = tt_read_16(font);\n\t\t\t\tif (idRangeOffset == 0) {\n\t\t\t\t\treturn idDelta + codepoint;\n\t\t\t\t} else {\n\t\t\t\t\ttt_seek(font, font->cmap_start + 12 + 6 * segCount + 2 + 2 * i + idRangeOffset + (codepoint - startCode) * 2);\n\t\t\t\t\treturn tt_read_16(font);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic void midpoint(float x_0, float y_0, float cx, float cy, float x_1, float y_1, float t, float * outx, float * outy) {\n\tfloat t2 = t * t;\n\tfloat nt = 1.0 - t;\n\tfloat nt2 = nt * nt;\n\t*outx = nt2 * x_0 + 2 * t * nt * cx + t2 * x_1;\n\t*outy = nt2 * y_0 + 2 * t * nt * cy + t2 * y_1;\n}\n\n__attribute__((visibility(\"protected\")))\nstruct TT_Contour * tt_draw_glyph_into(struct TT_Contour * contour, struct TT_Font * font, float x_offset, float y_offset, unsigned int glyph) {\n\toff_t glyf_offset = tt_get_glyph_offset(font, glyph);\n\tif (tt_get_glyph_offset(font, glyph + 1) == glyf_offset) return contour;\n\n\ttt_seek(font, font->glyf_ptr.offset + glyf_offset);\n\n\tint16_t numContours = tt_read_16(font);\n\t/* int16_t xMin = */ tt_read_16(font);\n\t/* int16_t yMin = */ tt_read_16(font);\n\t/* int16_t xMax = */ tt_read_16(font);\n\t/* int16_t yMax = */ tt_read_16(font);\n\n\ttt_seek(font, font->glyf_ptr.offset + glyf_offset + 10);\n\n\tif (numContours > 0) {\n\t\tuint16_t endPt;\n\t\tfor (int i = 0; i < numContours; ++i) {\n\t\t\tendPt = tt_read_16(font);\n\t\t}\n\t\tuint16_t numInstr = tt_read_16(font);\n\t\tfor (unsigned int i = 0; i < numInstr; ++i) {\n\t\t\ttt_read_8(font);\n\t\t}\n\t\tstruct TT_Vertex * vertices = malloc(sizeof(struct TT_Vertex) * (endPt + 1));\n\t\tfor (int i = 0; i < endPt + 1; ) {\n\t\t\tuint8_t v = tt_read_8(font);\n\t\t\tvertices[i].flags = v;\n\t\t\ti++;\n\t\t\tif (v & 8) {\n\t\t\t\tuint8_t repC = tt_read_8(font);\n\t\t\t\twhile (repC) {\n\t\t\t\t\tvertices[i].flags = v;\n\t\t\t\t\trepC--;\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tint last_x = 0;\n\t\tint last_y = 0;\n\t\tfor (int i = 0; i < endPt + 1; i++) {\n\t\t\tunsigned char flags = vertices[i].flags;\n\t\t\tif (flags & (1 << 1)) {\n\t\t\t\t/* One byte */\n\t\t\t\tif (flags & (1 << 4)) {\n\t\t\t\t\t/* Positive */\n\t\t\t\t\tvertices[i].x = last_x + tt_read_8(font);\n\t\t\t\t} else {\n\t\t\t\t\tvertices[i].x = last_x - tt_read_8(font);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (flags & (1 << 4)) {\n\t\t\t\t\tvertices[i].x = last_x;\n\t\t\t\t} else {\n\t\t\t\t\tint16_t diff = tt_read_16(font);\n\t\t\t\t\tvertices[i].x = last_x + diff;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlast_x = vertices[i].x;\n\t\t}\n\t\tfor (int i = 0; i < endPt + 1; i++) {\n\t\t\tunsigned char flags = vertices[i].flags;\n\t\t\tif (flags & (1 << 2)) {\n\t\t\t\t/* One byte */\n\t\t\t\tif (flags & (1 << 5)) {\n\t\t\t\t\t/* Positive */\n\t\t\t\t\tvertices[i].y = last_y + tt_read_8(font);\n\t\t\t\t} else {\n\t\t\t\t\tvertices[i].y = last_y - tt_read_8(font);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (flags & (1 << 5)) {\n\t\t\t\t\tvertices[i].y = last_y;\n\t\t\t\t} else {\n\t\t\t\t\tint16_t diff = tt_read_16(font);\n\t\t\t\t\tvertices[i].y = last_y + diff;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlast_y = vertices[i].y;\n\t\t}\n\n\t\ttt_seek(font, font->glyf_ptr.offset + glyf_offset + 10);\n\n\t\tint move_next = 1;\n\t\tint next_end = tt_read_16(font);\n\n\t\tfloat lx = 0, ly = 0, cx = 0, cy = 0, x = 0, y = 0;\n\t\tfloat sx = 0, sy = 0;\n\t\tint wasControl = 0;\n\n\t\tfor (int i = 0; i < endPt + 1; ++i) {\n\t\t\tx = ((float)vertices[i].x) * font->scale + x_offset;\n\t\t\ty = (-(float)vertices[i].y) * font->scale + y_offset;\n\t\t\tint isCurve = !(vertices[i].flags & (1 << 0));\n\t\t\tif (move_next) {\n\t\t\t\tcontour = tt_contour_move_to(contour, x, y);\n\t\t\t\tif (isCurve) {\n\t\t\t\t\t/* Is the point before this on-curve? */\n\t\t\t\t\tfloat px = (float)vertices[next_end].x * font->scale + x_offset;\n\t\t\t\t\tfloat py = (-(float)vertices[next_end].y) * font->scale + y_offset;\n\t\t\t\t\tif (vertices[next_end].flags & (1 << 0)) {\n\t\t\t\t\t\t/* Else we're just a regular off-curve point? */\n\t\t\t\t\t\tsx = px;\n\t\t\t\t\t\tsy = py;\n\t\t\t\t\t\tlx = px;\n\t\t\t\t\t\tly = py;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfloat dx = (px + x) / 2.0;\n\t\t\t\t\t\tfloat dy = (py + y) / 2.0;\n\t\t\t\t\t\tlx = dx;\n\t\t\t\t\t\tly = dy;\n\t\t\t\t\t\tsx = dx;\n\t\t\t\t\t\tsy = dy;\n\t\t\t\t\t}\n\t\t\t\t\tcx = x;\n\t\t\t\t\tcy = y;\n\t\t\t\t\twasControl = 1;\n\t\t\t\t} else {\n\t\t\t\t\tlx = x;\n\t\t\t\t\tly = y;\n\t\t\t\t\tsx = x;\n\t\t\t\t\tsy = y;\n\t\t\t\t\twasControl = 0;\n\t\t\t\t}\n\t\t\t\tmove_next = 0;\n\t\t\t} else {\n\t\t\t\tif (isCurve) {\n\t\t\t\t\tif (wasControl) {\n\t\t\t\t\t\tfloat dx = (cx + x) / 2.0;\n\t\t\t\t\t\tfloat dy = (cy + y) / 2.0;\n\t\t\t\t\t\tfor (int i = 1; i < 10; ++i) {\n\t\t\t\t\t\t\tfloat mx, my;\n\t\t\t\t\t\t\tmidpoint(lx,ly,cx,cy,dx,dy,(float)i / 10.0,&mx,&my);\n\t\t\t\t\t\t\tcontour = tt_contour_line_to(contour, mx, my);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontour = tt_contour_line_to(contour, dx, dy);\n\t\t\t\t\t\tlx = dx;\n\t\t\t\t\t\tly = dy;\n\t\t\t\t\t}\n\t\t\t\t\tcx = x;\n\t\t\t\t\tcy = y;\n\t\t\t\t\twasControl = 1;\n\t\t\t\t} else {\n\t\t\t\t\tif (wasControl) {\n\t\t\t\t\t\tfor (int i = 1; i < 10; ++i) {\n\t\t\t\t\t\t\tfloat mx, my;\n\t\t\t\t\t\t\tmidpoint(lx,ly,cx,cy,x,y,(float)i / 10.0,&mx,&my);\n\t\t\t\t\t\t\tcontour = tt_contour_line_to(contour, mx, my);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontour = tt_contour_line_to(contour, x, y);\n\t\t\t\t\tlx = x;\n\t\t\t\t\tly = y;\n\t\t\t\t\twasControl = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (i == next_end) {\n\t\t\t\tif (wasControl) {\n\t\t\t\t\tfor (int i = 1; i < 10; ++i) {\n\t\t\t\t\t\tfloat mx, my;\n\t\t\t\t\t\tmidpoint(lx,ly,cx,cy,sx,sy,(float)i / 10.0,&mx,&my);\n\t\t\t\t\t\tcontour = tt_contour_line_to(contour, mx, my);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcontour = tt_contour_line_to(contour, sx, sy);\n\t\t\t\tmove_next = 1;\n\t\t\t\tnext_end = tt_read_16(font);\n\t\t\t}\n\t\t}\n\n\t\tfree(vertices);\n\t} else if (numContours < 0) {\n\t\twhile (1) {\n\t\t\tuint16_t flags = tt_read_16(font);\n\t\t\tuint16_t ind   = tt_read_16(font);\n\t\t\tint16_t x, y;\n\t\t\tif (flags & (1 << 0)) {\n\t\t\t\tx = tt_read_16(font);\n\t\t\t\ty = tt_read_16(font);\n\t\t\t} else {\n\t\t\t\tx = tt_read_8(font);\n\t\t\t\ty = tt_read_8(font);\n\t\t\t}\n\n\t\t\tfloat x_f = x_offset;\n\t\t\tfloat y_f = y_offset;\n\t\t\tif (flags & (1 << 1)) {\n\t\t\t\tx_f = x_offset + x * font->scale;\n\t\t\t\ty_f = y_offset - y * font->scale;\n\t\t\t}\n\n\t\t\tif (flags & (1 << 3)) {\n\t\t\t\t/* TODO */\n\t\t\t\ttt_read_16(font);\n\t\t\t} else if (flags & (1 << 6)) {\n\t\t\t\t/* TODO */\n\t\t\t\ttt_read_16(font);\n\t\t\t\ttt_read_16(font);\n\t\t\t} else if (flags & (1 << 7)) {\n\t\t\t\t/* TODO */\n\t\t\t\ttt_read_16(font);\n\t\t\t\ttt_read_16(font);\n\t\t\t\ttt_read_16(font);\n\t\t\t\ttt_read_16(font);\n\t\t\t} else {\n\t\t\t\tlong o = tt_tell(font);\n\t\t\t\tcontour = tt_draw_glyph_into(contour,font,x_f,y_f,ind);\n\t\t\t\ttt_seek(font, o);\n\t\t\t}\n\t\t\tif (!(flags & (1 << 5))) break;\n\t\t}\n\t}\n\n\treturn contour;\n}\n\nsprite_t * tt_bake_glyph(struct TT_Font * font, unsigned int glyph, uint32_t color, int *_x, int *_y, float xadjust) {\n\tstruct TT_Contour * contour = tt_contour_start(0, 0);\n\tcontour = tt_draw_glyph_into(contour,font,100+xadjust,100,glyph);\n\tif (!contour->edgeCount) {\n\t\t*_x = 0;\n\t\t*_y = 0;\n\t\tfree(contour);\n\t\treturn NULL;\n\t}\n\n\t/* Calculate bounds to render a sprite */\n\tstruct TT_Shape * shape = tt_contour_finish(contour);\n\tint width = shape->lastX - shape->startX + 3;\n\tint height = shape->lastY - shape->startY + 2;\n\n\tint off_x = shape->startX - 1; shape->startX -= off_x; shape->lastX -= off_x;\n\tint off_y = shape->startY - 1; shape->startY -= off_y; shape->lastY -= off_y;\n\n\t/* Adjust the entire shape */\n\tfor (size_t i = 0; i < shape->edgeCount; ++i) {\n\t\tshape->edges[i].start.x -= off_x;\n\t\tshape->edges[i].end.x -= off_x;\n\t\tshape->edges[i].start.y -= off_y;\n\t\tshape->edges[i].end.y -= off_y;\n\t}\n\n\t*_x = off_x - 100;\n\t*_y = off_y - 100;\n\n\t/* Create sprite */\n\tsprite_t * out = create_sprite(width,height,ALPHA_EMBEDDED);\n\tgfx_context_t * ctx = init_graphics_sprite(out);\n\n\t/* Fill to clear */\n\tdraw_fill(ctx, 0);\n\n\ttt_path_paint(ctx, shape, color);\n\n\tfree(ctx);\n\tfree(shape);\n\tfree(contour);\n\treturn out;\n}\n\nvoid tt_draw_glyph(gfx_context_t * ctx, struct TT_Font * font, int x, int y, unsigned int glyph, uint32_t color) {\n\tstruct TT_Contour * contour = tt_contour_start(0, 0);\n\tcontour = tt_draw_glyph_into(contour,font,x,y,glyph);\n\tif (contour->edgeCount) {\n\t\tstruct TT_Shape * shape = tt_contour_finish(contour);\n\t\ttt_path_paint(ctx, shape, color);\n\t\tfree(shape);\n\t}\n\tfree(contour);\n}\n\nint tt_string_width(struct TT_Font * font, const char * s) {\n\tfloat x_offset = 0;\n\tuint32_t cp = 0;\n\tuint32_t istate = 0;\n\n\tfor (const unsigned char * c = (const unsigned char*)s; *c; ++c) {\n\t\tif (!decode(&istate, &cp, *c)) {\n\t\t\tunsigned int glyph = tt_glyph_for_codepoint(font, cp);\n\t\t\tx_offset += tt_xadvance_for_glyph(font, glyph) * font->scale;\n\t\t}\n\t}\n\n\treturn x_offset;\n}\n\nint tt_string_width_int(struct TT_Font * font, const char * s) {\n\tint x_offset = 0;\n\tuint32_t cp = 0;\n\tuint32_t istate = 0;\n\n\tfor (const unsigned char * c = (const unsigned char*)s; *c; ++c) {\n\t\tif (!decode(&istate, &cp, *c)) {\n\t\t\tunsigned int glyph = tt_glyph_for_codepoint(font, cp);\n\t\t\tx_offset += tt_xadvance_for_glyph(font, glyph) * font->scale;\n\t\t}\n\t}\n\n\treturn x_offset;\n}\n\nfloat tt_glyph_width(struct TT_Font * font, unsigned int glyph) {\n\treturn tt_xadvance_for_glyph(font, glyph) * font->scale;\n}\n\n__attribute__((visibility(\"protected\")))\nstruct TT_Contour * tt_prepare_string_into(struct TT_Contour * contour, struct TT_Font * font, float x, float y, const char * s, float * out_width) {\n\tif (contour == NULL) {\n\t\tcontour = tt_contour_start(0, 0);\n\t}\n\n\tfloat x_offset = x;\n\tuint32_t cp = 0;\n\tuint32_t istate = 0;\n\n\tfor (const unsigned char * c = (const unsigned char*)s; *c; ++c) {\n\t\tif (!decode(&istate, &cp, *c)) {\n\t\t\tunsigned int glyph = tt_glyph_for_codepoint(font, cp);\n\t\t\tcontour = tt_draw_glyph_into(contour,font,x_offset,y,glyph);\n\t\t\tx_offset += tt_xadvance_for_glyph(font, glyph) * font->scale;\n\t\t}\n\t}\n\n\tif (out_width) *out_width = x_offset - x;\n\n\treturn contour;\n}\n\n__attribute__((visibility(\"protected\")))\nstruct TT_Contour * tt_prepare_string(struct TT_Font * font, float x, float y, const char * s, float * out_width) {\n\treturn tt_prepare_string_into(NULL, font, x, y, s, out_width);\n}\n\nint tt_draw_string(gfx_context_t * ctx, struct TT_Font * font, int x, int y, const char * s, uint32_t color) {\n\tfloat width;\n\tstruct TT_Contour * contour = tt_prepare_string(font,x,y,s,&width);\n\tif (contour->edgeCount) {\n\t\tstruct TT_Shape * shape = tt_contour_finish(contour);\n\t\ttt_path_paint(ctx, shape, color);\n\t\tfree(shape);\n\t}\n\tfree(contour);\n\treturn width;\n}\n\n\nstatic int tt_font_load(struct TT_Font * font) {\n\tif (tt_seek(font, 4)) {\n\t\tfprintf(stderr, \"tt: failed to seek to 4\\n\");\n\t\tgoto _fail_free;\n\t}\n\tuint16_t numTables = tt_read_16(font);\n\tif (tt_seek(font, 12)) {\n\t\tfprintf(stderr, \"tt: failed to seek to 12\\n\");\n\t\tgoto _fail_free;\n\t}\n\n\tfor (unsigned int i = 0; i < numTables; ++i) {\n\t\tuint32_t tag = tt_read_32(font);\n\t\t/* uint32_t checkSum = */ tt_read_32(font);\n\t\tuint32_t offset = tt_read_32(font);\n\t\tuint32_t length = tt_read_32(font);\n\n\t\tswitch (tag) {\n\t\t\tcase 0x68656164: /* head */\n\t\t\t\tfont->head_ptr.offset = offset;\n\t\t\t\tfont->head_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x636d6170: /* cmap */\n\t\t\t\tfont->cmap_ptr.offset = offset;\n\t\t\t\tfont->cmap_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x676c7966: /* glyf */\n\t\t\t\tfont->glyf_ptr.offset = offset;\n\t\t\t\tfont->glyf_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x6c6f6361: /* loca */\n\t\t\t\tfont->loca_ptr.offset = offset;\n\t\t\t\tfont->loca_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x68686561: /* hhea */\n\t\t\t\tfont->hhea_ptr.offset = offset;\n\t\t\t\tfont->hhea_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x686d7478: /* hmtx */\n\t\t\t\tfont->hmtx_ptr.offset = offset;\n\t\t\t\tfont->hmtx_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x6e616d65: /* name */\n\t\t\t\tfont->name_ptr.offset = offset;\n\t\t\t\tfont->name_ptr.length = length;\n\t\t\t\tbreak;\n\t\t\tcase 0x4f532f32: /* OS/2 */\n\t\t\t\tfont->os_2_ptr.offset = offset;\n\t\t\t\tfont->name_ptr.length = length;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!font->head_ptr.offset) { fprintf(stderr, \"tt: no head table\\n\"); goto _fail_free; }\n\tif (!font->glyf_ptr.offset) { fprintf(stderr, \"tt: no glyf table\\n\"); goto _fail_free; }\n\tif (!font->cmap_ptr.offset) { fprintf(stderr, \"tt: no cmap table\\n\"); goto _fail_free; }\n\tif (!font->loca_ptr.offset) { fprintf(stderr, \"tt: no loca table\\n\"); goto _fail_free; }\n\n\t/* Get emSize */\n\ttt_seek(font, font->head_ptr.offset + 18);\n\tfont->emSize = (float)tt_read_16(font);\n\n\t/* Try to pick a viable cmap */\n\ttt_seek(font, font->cmap_ptr.offset);\n\n\tuint32_t best = 0;\n\tint bestScore = 0;\n\n\t/* Read size */\n\t/* uint16_t cmap_vers = */ tt_read_16(font);\n\tuint16_t cmap_size = tt_read_16(font);\n\tfor (unsigned int i = 0; i < cmap_size; ++i) {\n\t\tuint16_t platform = tt_read_16(font);\n\t\tuint16_t type     = tt_read_16(font);\n\t\tuint32_t offset   = tt_read_32(font);\n\n\t\tif ((platform == 3 || platform == 0) && type == 10) {\n\t\t\tbest = offset;\n\t\t\tbestScore = 4;\n\t\t} else if (platform == 0 && type == 4) {\n\t\t\tbest = offset;\n\t\t\tbestScore = 4;\n\t\t} else if (((platform == 0 && type == 3) || (platform == 3 && type == 1)) && bestScore < 2) {\n\t\t\tbest = offset;\n\t\t\tbestScore = 2;\n\t\t}\n\t}\n\n\tif (!best) {\n\t\tfprintf(stderr, \"tt: TODO: unsupported cmap (best = %#x bestScore = %d)\\n\", best, bestScore);\n\t\tgoto _fail_free;\n\t}\n\n\t/* What type is this */\n\ttt_seek(font, font->cmap_ptr.offset + best);\n\n\tfont->cmap_type = tt_read_16(font);\n\tif (font->cmap_type != 12 && font->cmap_type != 4) {\n\t\tfprintf(stderr, \"tt: TODO: unsupported cmap indexing %d\\n\", font->cmap_type);\n\t\tgoto _fail_free;\n\t}\n\n\tfont->cmap_start = font->cmap_ptr.offset + best;\n\n\ttt_seek(font, font->head_ptr.offset + 50);\n\tfont->loca_type = tt_read_16(font);\n\n\treturn 1;\n\n_fail_free:\n\treturn 0;\n\tfree(font);\n}\n\nstruct TT_Font * tt_font_from_file(const char * fileName) {\n\tFILE * f = fopen(fileName, \"r\");\n\tif (!f) return NULL;\n\n\tstruct TT_Font * font = calloc(sizeof(struct TT_Font), 1);\n\tfont->filePtr = f;\n\tfont->privFlags = 1;\n\n\tif (!tt_font_load(font)) goto _fail_close;\n\n\treturn font;\n\n_fail_close:\n\tfclose(f);\n\treturn NULL;\n}\n\nstruct TT_Font * tt_font_from_memory(uint8_t * buffer) {\n\tstruct TT_Font * font = calloc(sizeof(struct TT_Font), 1);\n\tfont->privFlags = 0;\n\tfont->buffer = buffer;\n\tif (!tt_font_load(font)) return NULL;\n\treturn font;\n}\n\nstruct TT_Font * tt_font_from_file_mem(const char * fileName) {\n\tFILE * f = fopen(fileName, \"r\");\n\tif (!f) return NULL;\n\n\tfseek(f, 0, SEEK_END);\n\tlong size = ftell(f);\n\tfseek(f, 0, SEEK_SET);\n\n\tuint8_t * buf = malloc(size);\n\tfread(buf, 1, size, f);\n\n\tfclose(f);\n\n\treturn tt_font_from_memory(buf);\n}\n\nstatic hashmap_t * shm_font_cache = NULL;\nstatic int volatile shm_font_lock = 0;\n\nstruct TT_Font * tt_font_from_shm(const char * identifier) {\n\tspin_lock(&shm_font_lock);\n\n\tif (!shm_font_cache) {\n\t\tshm_font_cache = hashmap_create(10);\n\t}\n\n\tvoid * fontData = hashmap_get(shm_font_cache, (char*)identifier);\n\tif (fontData) goto shm_success;\n\n\tchar * display = getenv(\"DISPLAY\");\n\n\tif (!display) goto shm_fail;\n\n\tchar fullIdentifier[1024];\n\tsnprintf(fullIdentifier, 1023, \"sys.%s.fonts.%s\", display, identifier);\n\n\tsize_t fontSize = 0;\n\tfontData = shm_obtain(fullIdentifier, &fontSize);\n\n\tif (fontSize == 0) {\n\t\tshm_release(identifier);\n\t\tgoto shm_fail;\n\t}\n\n\thashmap_set(shm_font_cache, (char*)identifier, fontData);\n\nshm_success:\n\tspin_unlock(&shm_font_lock);\n\treturn tt_font_from_memory(fontData);\n\nshm_fail:\n\tspin_unlock(&shm_font_lock);\n\treturn NULL;\n}\n\nvoid tt_draw_string_shadow(gfx_context_t * ctx, struct TT_Font * font, char * string, int font_size, int left, int top, uint32_t text_color, uint32_t shadow_color, int blur) {\n\ttt_set_size(font, font_size);\n\tint w = tt_string_width(font, string);\n\t/* TODO: We need to check the bounds of descenders and ascenders so we can fit things more correctly... */\n\tsprite_t * _tmp_s = create_sprite(w + blur * 2, font_size + blur * 2 + 5, ALPHA_EMBEDDED);\n\tgfx_context_t * _tmp = init_graphics_sprite(_tmp_s);\n\tdraw_fill(_tmp, rgba(0,0,0,0));\n\ttt_draw_string(_tmp, font, blur, blur + font_size, string, shadow_color);\n\tblur_context_box(_tmp, blur);\n\tblur_context_box(_tmp, blur);\n\tfree(_tmp);\n\tdraw_sprite(ctx, _tmp_s, left - blur, top - blur);\n\tsprite_free(_tmp_s);\n\ttt_draw_string(ctx, font, left, top + font_size, string, text_color);\n}\n\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\n\nchar * tt_get_name_string(struct TT_Font * font, int identifier) {\n\tif (!font->name_ptr.offset) return NULL;\n\n\ttt_seek(font, font->name_ptr.offset);\n\tuint16_t nameFormat = tt_read_16(font);\n\tuint16_t count = tt_read_16(font);\n\tuint16_t stringOffset = tt_read_16(font);\n\n\tif (nameFormat != 0) return NULL; /* Unsupported table format */\n\n\t/* Read records until we find one that matches what we asked for, in a suitable format */\n\tfor (unsigned int i = 0; i < count; ++i) {\n\t\tuint16_t platformId = tt_read_16(font);\n\t\tuint16_t platformSpecificId = tt_read_16(font);\n\t\t/* uint16_t languageId = */ tt_read_16(font);\n\t\tuint16_t nameId = tt_read_16(font);\n\t\tuint16_t length = tt_read_16(font);\n\t\tuint16_t offset = tt_read_16(font);\n\n\t\tif (nameId != identifier) continue;\n\t\tif (!(platformId == 3 && platformSpecificId == 1)) continue;\n\n\t\tchar * tmp = calloc(length * 3 + 1,1); /* Should be enough ? */\n\t\tchar * c = tmp;\n\n\t\ttt_seek(font, stringOffset + offset + font->name_ptr.offset);\n\n\t\tfor (unsigned int j = 0; j < length; j += 2) {\n\t\t\tuint32_t cp = tt_read_16(font);\n\t\t\tif (cp > 0xD7FF && cp < 0xE000) {\n\t\t\t\tuint32_t highBits = cp - 0xD800;\n\t\t\t\tuint32_t lowBits = tt_read_16(font) - 0xDC00;\n\t\t\t\tcp = 0x10000 + (highBits << 10) + lowBits;\n\t\t\t\tj += 2;\n\t\t\t}\n\t\t\tc += to_eight(cp, c);\n\t\t}\n\n\t\treturn tmp;\n\t}\n\n\treturn NULL;\n}\n\nstruct PenPoly {\n\tfloat x;\n\tfloat y;\n\tfloat inner;\n\tfloat outer;\n\tfloat basis;\n};\n\nstatic float tangent(float x_0, float y_0, float x_1, float y_1) {\n\treturn fmod(atan2(y_0 - y_1, x_1 - x_0) + 2.0 * M_PI, 2.0 * M_PI);\n}\n\nstatic int angle_compare(float s, struct PenPoly * pen, int a) {\n\tif (s >= pen[a].inner && s < pen[a].outer) return 0;\n\tif (s >= pen[a].inner && pen[a].outer < pen[a].inner) return 0;\n\tif (s <  pen[a].outer && pen[a].outer < pen[a].inner) return 0;\n\tif (s <  pen[a].outer && (2.0 * M_PI + s - pen[a].outer > M_PI)) return 1;\n\tif (s >  pen[a].outer && (s - pen[a].outer > M_PI)) return 1;\n\treturn -1;\n}\n\nstatic int best_angle(int sides, struct PenPoly * pen, float s) {\n\tfor (int a = 0; a < sides; ++a) {\n\t\tif (angle_compare(s,pen,a) == 0) return a;\n\t}\n\treturn 0;\n}\n\n__attribute__((visibility(\"protected\")))\nstruct TT_Contour * tt_contour_stroke_contour(const struct TT_Contour * in, float width) {\n\tstruct TT_Contour * stroke = tt_contour_start(0,0);\n\n\tif (in->edgeCount) {\n\t\tint sides = width < 1.0 ? 4 : 16;\n\t\tfloat inner = 2.0 * M_PI / (float)sides;\n\t\tfloat outer = (M_PI - inner) / 2.0;\n\t\tstruct PenPoly * pen = malloc(sizeof(struct PenPoly) * sides); /* Arbitrary */\n\t\tfor (int i = 0; i < sides; ++i) {\n\t\t\tfloat angle = (float)i * 2.0 * M_PI / (float)sides;\n\t\t\tpen[i].x = cos(angle) * width;\n\t\t\tpen[i].y = -sin(angle) * width;\n\t\t\tpen[i].basis = angle;\n\t\t\tpen[i].inner = fmod(angle + outer, 2.0 * M_PI);\n\t\t\tpen[i].outer = fmod(angle + outer + inner, 2.0 * M_PI);\n\t\t}\n\n\n\t\tint start_of_segment = 0;\n\t\tint next_segment = (int)in->edgeCount;\n\n\t\tdo {\n\t\t\tint started = 0;\n\t\t\tint v = start_of_segment;\n\t\t\tfloat s = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y);\n\t\t\tint a = best_angle(sides,pen,s);\n\n\t\t\twhile (v < (int)in->edgeCount) {\n\t\t\t\ts = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y);\n\t\t\t\tstroke = (started ? tt_contour_line_to : tt_contour_move_to)(stroke, pen[a].x + in->edges[v].start.x, pen[a].y + in->edges[v].start.y);\n\t\t\t\tstarted = 1;\n\t\t\t\tint comp = angle_compare(s,pen,a);\n\t\t\t\tif (comp == 0) {\n\t\t\t\t\tif (v + 1 == (int)in->edgeCount) {\n\t\t\t\t\t\tnext_segment = in->edgeCount;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tif (in->edges[v+1].start.x != in->edges[v].end.x || in->edges[v+1].start.y != in->edges[v].end.y) {\n\t\t\t\t\t\tnext_segment = v + 1;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tv++;\n\t\t\t\t} else if (comp == 1) {\n\t\t\t\t\ta = (sides + a - 1) % sides;\n\t\t\t\t} else {\n\t\t\t\t\ta = (a + 1) % sides;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile (v >= start_of_segment) {\n\t\t\t\ts = tangent(in->edges[v].end.x, in->edges[v].end.y, in->edges[v].start.x, in->edges[v].start.y);\n\t\t\t\tstroke = tt_contour_line_to(stroke, in->edges[v].end.x + pen[a].x, in->edges[v].end.y + pen[a].y);\n\t\t\t\tint comp = angle_compare(s,pen,a);\n\t\t\t\tif (comp == 0) {\n\t\t\t\t\tif (v == start_of_segment) break;\n\t\t\t\t\tv--;\n\t\t\t\t} else if (comp == 1) {\n\t\t\t\t\ta = (sides + a - 1) % sides;\n\t\t\t\t} else {\n\t\t\t\t\ta = (a + 1) % sides;\n\t\t\t\t}\n\t\t\t}\n\t\t\twhile (v == start_of_segment) {\n\t\t\t\ts = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y);\n\t\t\t\tstroke = tt_contour_line_to(stroke, pen[a].x + in->edges[v].start.x, pen[a].y + in->edges[v].start.y);\n\t\t\t\tint comp = angle_compare(s,pen,a);\n\t\t\t\tif (comp == 0) {\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (comp == 1) {\n\t\t\t\t\ta = (sides + a - 1) % sides;\n\t\t\t\t} else {\n\t\t\t\t\ta = (a + 1) % sides;\n\t\t\t\t}\n\t\t\t}\n\t\t\tstart_of_segment = next_segment;\n\t\t} while (next_segment != (int)in->edgeCount);\n\n\t\tfree(pen);\n\t}\n\n\treturn stroke;\n}\n\nstruct TT_Shape * tt_contour_stroke_shape(const struct TT_Contour * in, float width) {\n\tstruct TT_Contour * stroke = tt_contour_stroke_contour(in,width);\n\tstruct TT_Shape * out = tt_contour_finish(stroke);\n\tfree(stroke);\n\treturn out;\n}\n\nvoid tt_contour_transform(struct TT_Contour * cnt, gfx_matrix_t matrix) {\n\tfor (size_t i = 0; i < cnt->edgeCount; i++) {\n\t\tdouble x, y;\n\t\tgfx_apply_matrix(cnt->edges[i].start.x, cnt->edges[i].start.y, matrix, &x, &y);\n\t\tcnt->edges[i].start.x = x;\n\t\tcnt->edges[i].start.y = y;\n\t\tgfx_apply_matrix(cnt->edges[i].end.x, cnt->edges[i].end.y, matrix, &x, &y);\n\t\tcnt->edges[i].end.x = x;\n\t\tcnt->edges[i].end.y = y;\n\t}\n}\n\nstatic inline int out_of_bounds(const sprite_t * tex, int x, int y) {\n\treturn x < 0 || y < 0 || x >= tex->width || y >= tex->height;\n}\n\nstatic inline uint32_t linear_interp(uint32_t left, uint32_t right, uint16_t pr) {\n\tuint16_t pl = 0xFF ^ pr;\n\tuint8_t d_r = (((uint32_t)(_RED(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_RED(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_g = (((uint32_t)(_GRE(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_GRE(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_b = (((uint32_t)(_BLU(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_BLU(left) * pl + 0x80) * 0x101) >> 16UL);\n\tuint8_t d_a = (((uint32_t)(_ALP(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_ALP(left) * pl + 0x80) * 0x101) >> 16UL);\n\treturn tt_rgba(d_r, d_g, d_b, d_a);\n}\n\nstatic inline uint32_t sprite_pixel_no_repeat(const sprite_t * tex, int x, int y) {\n\treturn out_of_bounds(tex,x,y) ? 0 : SPRITE(tex,x,y);\n}\n\nstatic inline int wrap(int x, int w) {\n\treturn x < 0 ? (w - 1 - (-x -1) % w) : (x % w);\n}\n\nstatic inline uint32_t sprite_pixel_repeat(const sprite_t * tex, int x, int y) {\n\tint w = tex->width;\n\tint h = tex->height;\n\treturn SPRITE(tex,wrap(x,w),wrap(y,h));\n}\n\nstatic inline uint32_t sprite_pixel_pad(const sprite_t * tex, int x, int y) {\n\tint w = tex->width;\n\tint h = tex->height;\n\tif (x < 0) x = 0;\n\tif (x >= w) x = w-1;\n\tif (y < 0) y = 0;\n\tif (y >= h) y = h-1;\n\treturn SPRITE(tex,x,y);\n}\n\ntypedef uint32_t (*pixel_getter_t)(const sprite_t*,int,int);\n\n__attribute__((hot))\nstatic inline uint32_t sprite_interpolate_bilinear(const sprite_t * tex, double u, double v, pixel_getter_t pixel_getter) {\n\tint x = floor(u);\n\tint y = floor(v);\n\tuint32_t ul = pixel_getter(tex,x,y);\n\tuint32_t ur = pixel_getter(tex,x+1,y);\n\tuint32_t ll = pixel_getter(tex,x,y+1);\n\tuint32_t lr = pixel_getter(tex,x+1,y+1);\n\tif ((ul | ur | ll | lr) == 0) return 0;\n\tuint8_t u_ratio = (u - x) * 0xFF;\n\tuint8_t v_ratio = (v - y) * 0xFF;\n\tuint32_t top = linear_interp(ul,ur,u_ratio);\n\tuint32_t bot = linear_interp(ll,lr,u_ratio);\n\treturn linear_interp(top,bot,v_ratio);\n}\n\nstatic inline uint32_t sprite_interpolate_nearest(const sprite_t * tex, double u, double v, pixel_getter_t pixel_getter) {\n\tint x = floor(u);\n\tint y = floor(v);\n\treturn pixel_getter(tex,x,y);\n}\n\ntypedef uint32_t (*sprite_interp_t)(const sprite_t *, double, double, pixel_getter_t);\n\n__attribute__((hot))\nstatic inline void paint_scanline_sprite(gfx_context_t * ctx, int y, const struct TT_Shape * shape, float * subsamples, sprite_t * sprite, double u, double v, double filter_dxx, double filter_dxy, sprite_interp_t sprite_interp, pixel_getter_t pixel_getter) {\n\tfor (int x = shape->startX < 0 ? 0 : shape->startX; x < shape->lastX && x < ctx->width; ++x) {\n\t\tuint16_t na = (int)(255 * subsamples[x - shape->startX]) >> 2;\n\t\tuint32_t color = sprite_interp(sprite, u, v, pixel_getter);\n\t\tuint32_t nc = tt_apply_alpha(color, na);\n\t\tGFX(ctx, x, y) = tt_alpha_blend_rgba(GFX(ctx, x, y), nc);\n\t\tsubsamples[x-shape->startX] = 0;\n\t\tu += filter_dxx;\n\t\tv += filter_dxy;\n\t}\n}\n\nstatic inline void tt_path_paint_sprite_internal(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix, sprite_interp_t sprite_interp, pixel_getter_t pixel_getter) {\n\tgfx_matrix_t inverse;\n\tgfx_matrix_invert(matrix,inverse);\n\tsize_t size = shape->edgeCount;\n\tstruct TT_Intersection * crosses = malloc(sizeof(struct TT_Intersection) * size);\n\n\tsize_t subsample_width = shape->lastX - shape->startX;\n\tfloat * subsamples = malloc(sizeof(float) * subsample_width);\n\tmemset(subsamples, 0, sizeof(float) * subsample_width);\n\n\tint startY = shape->startY < 0 ? 0 : shape->startY;\n\tint endY = shape->lastY <= ctx->height ? shape->lastY : ctx->height;\n\n\tdouble filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy;\n\tint _left = shape->startX < 0 ? 0 : shape->startX;\n\tgfx_apply_matrix(_left, startY, inverse, &filter_x, &filter_y);\n\tgfx_apply_matrix(_left+1, startY, inverse, &filter_dxx, &filter_dxy);\n\tfilter_dxx -= filter_x;\n\tfilter_dxy -= filter_y;\n\tgfx_apply_matrix(_left, startY+1, inverse, &filter_dyx, &filter_dyy);\n\tfilter_dyx -= filter_x;\n\tfilter_dyy -= filter_y;\n\n\tfor (int y = startY; y < endY; ++y) {\n\t\tfloat u = filter_x;\n\t\tfloat v = filter_y;\n\t\tfilter_x += filter_dyx;\n\t\tfilter_y += filter_dyy;\n\t\tif (!_is_in_clip(ctx,y)) continue;\n\t\tfloat _y = y + 0.0001;\n\t\tfor (int l = 0; l < 4; ++l) {\n\t\t\tsize_t cnt;\n\t\t\tif ((cnt = prune_edges(size, _y, shape->edges, crosses))) {\n\t\t\t\tsort_intersections(cnt, crosses);\n\t\t\t\tprocess_scanline(_y, shape, subsample_width, subsamples, cnt, crosses);\n\t\t\t}\n\t\t\t_y += 1.0/4.0;\n\t\t}\n\t\tpaint_scanline_sprite(ctx, y, shape, subsamples, sprite, u, v, filter_dxx, filter_dxy, sprite_interp, pixel_getter);\n\t}\n\n\tfree(subsamples);\n\tfree(crosses);\n}\n\nvoid tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix) {\n\ttt_path_paint_sprite_internal(ctx,shape,sprite,matrix,sprite_interpolate_bilinear,sprite_pixel_repeat);\n}\n\nvoid tt_path_paint_sprite_options(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix, int filter, int wrap) {\n\tsprite_interp_t sprite_interp = sprite_interpolate_bilinear;\n\tpixel_getter_t pixel_getter = sprite_pixel_repeat;\n\n\tswitch (filter) {\n\t\tcase TT_PATH_FILTER_BILINEAR:\n\t\tdefault:\n\t\t\tbreak;\n\t\tcase TT_PATH_FILTER_NEAREST:\n\t\t\tsprite_interp = sprite_interpolate_nearest;\n\t\t\tbreak;\n\t}\n\n\tswitch (wrap) {\n\t\tcase TT_PATH_WRAP_REPEAT:\n\t\tdefault:\n\t\t\tbreak;\n\t\tcase TT_PATH_WRAP_NONE:\n\t\t\tpixel_getter = sprite_pixel_no_repeat;\n\t\t\tbreak;\n\t\tcase TT_PATH_WRAP_PAD:\n\t\t\tpixel_getter = sprite_pixel_pad;\n\t\t\tbreak;\n\t}\n\n\ttt_path_paint_sprite_internal(ctx,shape,sprite,matrix,sprite_interp,pixel_getter);\n}\n\nchar * tt_ellipsify(const char * input, int font_size, struct TT_Font * font, int max_width, int * out_width) {\n\tint width;\n\tint len = strlen(input);\n\tchar * out = malloc(len + 4);\n\n\tif (max_width <= 0) {\n\t\tout[0] = '\\0';\n\t\twidth = 0;\n\t\tgoto _finish;\n\t}\n\n\tmemcpy(out, input, len + 1);\n\ttt_set_size(font, font_size);\n\twhile ((width = tt_string_width(font, out)) > max_width) {\n\t\tlen--;\n\t\tif (len+0>=0) out[len+0] = '.';\n\t\tif (len+1>=0) out[len+1] = '.';\n\t\tif (len+2>=0) out[len+2] = '.';\n\t\tout[len+3] = '\\0';\n\t}\n\n_finish:\n\tif (out_width) *out_width = width;\n\treturn out;\n}\n"
  },
  {
    "path": "lib/tree.c",
    "content": "/**\n * @brief General-purpose tree implementation\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2011-2018 K. Lange\n */\n\n#ifdef _KERNEL_\n#\tinclude <kernel/system.h>\n#else\n#\tinclude <stddef.h>\n#\tinclude <stdlib.h>\n#endif\n\n#include <toaru/tree.h>\n\ntree_t * tree_create(void) {\n\t/* Create a new tree */\n\ttree_t * out = malloc(sizeof(tree_t));\n\tout->nodes  = 0;\n\tout->root   = NULL;\n\treturn out;\n}\n\nvoid tree_set_root(tree_t * tree, void * value) {\n\t/* Set the root node for a new tree. */\n\ttree_node_t * root = tree_node_create(value);\n\ttree->root = root;\n\ttree->nodes = 1;\n}\n\nvoid tree_node_destroy(tree_node_t * node) {\n\t/* Free the contents of a node and its children, but not the nodes themselves */\n\tforeach(child, node->children) {\n\t\ttree_node_destroy((tree_node_t *)child->value);\n\t}\n\tfree(node->value);\n}\n\nvoid tree_destroy(tree_t * tree) {\n\t/* Free the contents of a tree, but not the nodes */\n\tif (tree->root) {\n\t\ttree_node_destroy(tree->root);\n\t}\n}\n\nvoid tree_node_free(tree_node_t * node) {\n\t/* Free a node and its children, but not their contents */\n\tif (!node) return;\n\tforeach(child, node->children) {\n\t\ttree_node_free(child->value);\n\t}\n\tfree(node);\n}\n\nvoid tree_free(tree_t * tree) {\n\t/* Free all of the nodes in a tree, but not their contents */\n\ttree_node_free(tree->root);\n}\n\ntree_node_t * tree_node_create(void * value) {\n\t/* Create a new tree node pointing to the given value */\n\ttree_node_t * out = malloc(sizeof(tree_node_t));\n\tout->value = value;\n\tout->children = list_create();\n\tout->parent = NULL;\n\treturn out;\n}\n\nvoid tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node) {\n\t/* Insert a node as a child of parent */\n\tlist_insert(parent->children, node);\n\tnode->parent = parent;\n\ttree->nodes++;\n}\n\ntree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value) {\n\t/* Insert a (fresh) node as a child of parent */\n\ttree_node_t * out = tree_node_create(value);\n\ttree_node_insert_child_node(tree, parent, out);\n\treturn out;\n}\n\ntree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle) {\n\t/* Recursive node part of tree_find_parent */\n\ttree_node_t * found = NULL;\n\tforeach(child, haystack->children) {\n\t\tif (child->value == needle) {\n\t\t\treturn haystack;\n\t\t}\n\t\tfound = tree_node_find_parent((tree_node_t *)child->value, needle);\n\t\tif (found) {\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn found;\n}\n\ntree_node_t * tree_find_parent(tree_t * tree, tree_node_t * node) {\n\t/* Return the parent of a node, inefficiently. */\n\tif (!tree->root) return NULL;\n\treturn tree_node_find_parent(tree->root, node);\n}\n\nsize_t tree_count_children(tree_node_t * node) {\n\t/* return the number of children this node has */\n\tif (!node) return 0;\n\tif (!node->children) return 0;\n\tsize_t out = node->children->length;\n\tforeach(child, node->children) {\n\t\tout += tree_count_children((tree_node_t *)child->value);\n\t}\n\treturn out;\n}\n\nvoid tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node) {\n\t/* remove a node when we know its parent; update node counts for the tree */\n\ttree->nodes -= tree_count_children(node) + 1;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\ttree_node_free(node);\n}\n\nvoid tree_node_remove(tree_t * tree, tree_node_t * node) {\n\t/* remove an entire branch given its root */\n\ttree_node_t * parent = node->parent;\n\tif (!parent) {\n\t\tif (node == tree->root) {\n\t\t\ttree->nodes = 0;\n\t\t\ttree->root  = NULL;\n\t\t\ttree_node_free(node);\n\t\t}\n\t}\n\ttree_node_parent_remove(tree, parent, node);\n}\n\nvoid tree_remove(tree_t * tree, tree_node_t * node) {\n\t/* Remove this node and move its children into its parent's list of children */\n\ttree_node_t * parent = node->parent;\n\t/* This is something we just can't do. We don't know how to merge our\n\t * children into our \"parent\" because then we'd have more than one root node.\n\t * A good way to think about this is actually what this tree struct\n\t * primarily exists for: processes. Trying to remove the root is equivalent\n\t * to trying to kill init! Which is bad. We immediately fault on such\n\t * a case anyway (\"Tried to kill init, shutting down!\").\n\t */\n\tif (!parent) return;\n\ttree->nodes--;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\tforeach(child, node->children) {\n\t\t/* Reassign the parents */\n\t\t((tree_node_t *)child->value)->parent = parent;\n\t}\n\tlist_merge(parent->children, node->children);\n\tfree(node);\n}\n\nvoid tree_remove_reparent_root(tree_t * tree, tree_node_t * node) {\n\t/* Remove this node and move its children into the root children */\n\ttree_node_t * parent = node->parent;\n\tif (!parent) return;\n\ttree->nodes--;\n\tlist_delete(parent->children, list_find(parent->children, node));\n\tforeach(child, node->children) {\n\t\t/* Reassign the parents */\n\t\t((tree_node_t *)child->value)->parent = tree->root;\n\t}\n\tlist_merge(tree->root->children, node->children);\n\tfree(node);\n}\n\nvoid tree_break_off(tree_t * tree, tree_node_t * node) {\n\ttree_node_t * parent = node->parent;\n\tif (!parent) return;\n\tlist_delete(parent->children, list_find(parent->children, node));\n}\n\ntree_node_t * tree_node_find(tree_node_t * node, void * search, tree_comparator_t comparator) {\n\tif (comparator(node->value,search)) {\n\t\treturn node;\n\t}\n\ttree_node_t * found;\n\tforeach(child, node->children) {\n\t\tfound = tree_node_find((tree_node_t *)child->value, search, comparator);\n\t\tif (found) return found;\n\t}\n\treturn NULL;\n}\n\ntree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator) {\n\treturn tree_node_find(tree->root, value, comparator);\n}\n"
  },
  {
    "path": "lib/yutani.c",
    "content": "/**\n * @brief Yutani Client Library\n *\n * Client library for the compositing window system.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2018 K. Lange\n */\n#include <string.h>\n#include <stdlib.h>\n#include <va_list.h>\n#include <sys/shm.h>\n\n#include <toaru/pex.h>\n#include <toaru/graphics.h>\n#include <toaru/kbd.h>\n#include <toaru/hashmap.h>\n#include <toaru/list.h>\n#include <toaru/yutani.h>\n#include <toaru/yutani-internal.h>\n#include <toaru/mouse.h>\n\n/* We need the flags but don't want the library dep (maybe the flags should be here?) */\n#include <toaru/./decorations.h>\n\n/**\n * yutani_wait_for\n *\n * Wait for a particular kind of message, queuing other types\n * of messages for processing later.\n */\nyutani_msg_t * yutani_wait_for(yutani_t * y, uint32_t type) {\n\tdo {\n\t\tyutani_msg_t * out;\n\t\tsize_t size;\n\t\t{\n\t\t\tchar tmp[MAX_PACKET_SIZE];\n\t\t\tsize = pex_recv(y->sock, tmp);\n\t\t\tout = malloc(size);\n\t\t\tmemcpy(out, tmp, size);\n\t\t}\n\n\t\tif (out->type == type) {\n\t\t\treturn out;\n\t\t} else {\n\t\t\tlist_insert(y->queued, out);\n\t\t}\n\t} while (1); /* XXX: (!y->abort) */\n}\n\n/**\n * yutani_query\n *\n * Check if there is an available message, either in the\n * internal queue or directly from the server interface.\n */\nsize_t yutani_query(yutani_t * y) {\n\tif (y->queued->length > 0) return 1;\n\treturn pex_query(y->sock);\n}\n\n/**\n * _handle_internal\n *\n * Some messages are processed internally. They are still\n * available to the client application, but some work will\n * be done before they are handed off.\n *\n * WELCOME: Update the display_width and display_height for the connection.\n * WINDOW_MOVE: Update the window location.\n */\nstatic void _handle_internal(yutani_t * y, yutani_msg_t * out) {\n\tswitch (out->type) {\n\t\tcase YUTANI_MSG_WELCOME:\n\t\t\t{\n\t\t\t\tstruct yutani_msg_welcome * mw = (void *)out->data;\n\t\t\t\ty->display_width = mw->display_width;\n\t\t\t\ty->display_height = mw->display_height;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_WINDOW_MOVE:\n\t\t\t{\n\t\t\t\tstruct yutani_msg_window_move * wm = (void *)out->data;\n\t\t\t\tyutani_window_t * win = hashmap_get(y->windows, (void *)(uintptr_t)wm->wid);\n\t\t\t\tif (win) {\n\t\t\t\t\twin->x = wm->x;\n\t\t\t\t\twin->y = wm->y;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\tcase YUTANI_MSG_RESIZE_OFFER:\n\t\t\t{\n\t\t\t\tstruct yutani_msg_window_resize * wr = (void *)out->data;\n\t\t\t\tyutani_window_t * win = hashmap_get(y->windows, (void *)(uintptr_t)wr->wid);\n\t\t\t\tif (win) {\n\t\t\t\t\twin->decorator_flags &= ~(DECOR_FLAG_TILED);\n\t\t\t\t\twin->decorator_flags |= (wr->flags & YUTANI_RESIZE_TILED) << 2;\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tbreak;\n\t}\n}\n\n/**\n * yutani_poll\n *\n * Wait for a message to be available, processing it if\n * it has internal processing requirements.\n */\nyutani_msg_t * yutani_poll(yutani_t * y) {\n\tyutani_msg_t * out;\n\n\tif (y->queued->length > 0) {\n\t\tnode_t * node = list_dequeue(y->queued);\n\t\tout = (yutani_msg_t *)node->value;\n\t\tfree(node);\n\t\t_handle_internal(y, out);\n\t\treturn out;\n\t}\n\n\tssize_t size;\n\t{\n\t\tchar tmp[MAX_PACKET_SIZE];\n\t\tsize = pex_recv(y->sock, tmp);\n\t\tif (size <= 0) return NULL;\n\t\tout = malloc(size);\n\t\tmemcpy(out, tmp, size);\n\t}\n\n\t_handle_internal(y, out);\n\n\treturn out;\n}\n\n/**\n * yutani_poll_async\n *\n * Get the next available message, if there is one, otherwise\n * return immediately. Generally should be called in a loop\n * after an initial call to yutani_poll in case processing\n * caused additional messages to be queued.\n */\nyutani_msg_t * yutani_poll_async(yutani_t * y) {\n\tif (yutani_query(y) > 0) {\n\t\treturn yutani_poll(y);\n\t}\n\treturn NULL;\n}\n\nvoid yutani_msg_buildx_hello(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_HELLO;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_flip(yutani_msg_t * msg, yutani_wid_t wid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_FLIP;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip);\n\n\tstruct yutani_msg_flip * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n}\n\n\nvoid yutani_msg_buildx_welcome(yutani_msg_t * msg, uint32_t width, uint32_t height) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WELCOME;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_welcome);\n\n\tstruct yutani_msg_welcome * mw = (void *)msg->data;\n\n\tmw->display_width = width;\n\tmw->display_height = height;\n}\n\n\nvoid yutani_msg_buildx_window_new(yutani_msg_t * msg, uint32_t width, uint32_t height) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_NEW;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new);\n\n\tstruct yutani_msg_window_new * mw = (void *)msg->data;\n\n\tmw->width = width;\n\tmw->height = height;\n}\n\n\nvoid yutani_msg_buildx_window_new_flags(yutani_msg_t * msg, uint32_t width, uint32_t height, uint32_t flags, yutani_wid_t parent_wid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_NEW_FLAGS;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new_flags);\n\n\tstruct yutani_msg_window_new_flags * mw = (void *)msg->data;\n\n\tmw->width = width;\n\tmw->height = height;\n\tmw->flags = flags;\n\tmw->parent_wid = parent_wid;\n}\n\n\nvoid yutani_msg_buildx_window_init(yutani_msg_t * msg, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_INIT;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_init);\n\n\tstruct yutani_msg_window_init * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->width = width;\n\tmw->height = height;\n\tmw->bufid = bufid;\n}\n\n\nvoid yutani_msg_buildx_window_close(yutani_msg_t * msg, yutani_wid_t wid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_CLOSE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_close);\n\n\tstruct yutani_msg_window_close * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n}\n\n\nvoid yutani_msg_buildx_key_event(yutani_msg_t * msg, yutani_wid_t wid, key_event_t * event, key_event_state_t * state) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_KEY_EVENT;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_event);\n\n\tstruct yutani_msg_key_event * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmemcpy(&mw->event, event, sizeof(key_event_t));\n\tmemcpy(&mw->state, state, sizeof(key_event_state_t));\n}\n\n\nvoid yutani_msg_buildx_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, mouse_device_packet_t * event, int32_t type) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_MOUSE_EVENT;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_mouse_event);\n\n\tstruct yutani_msg_mouse_event * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmemcpy(&mw->event, event, sizeof(mouse_device_packet_t));\n\tmw->type = type;\n}\n\n\nvoid yutani_msg_buildx_window_move(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_MOVE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move);\n\n\tstruct yutani_msg_window_move * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->x = x;\n\tmw->y = y;\n}\n\nvoid yutani_msg_buildx_window_move_relative(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2, int32_t x, int32_t y) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_MOVE_RELATIVE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move_relative);\n\n\tstruct yutani_msg_window_move_relative * mw = (void *)msg->data;\n\n\tmw->wid_to_move = wid;\n\tmw->wid_base = wid2;\n\tmw->x = x;\n\tmw->y = y;\n}\n\nvoid yutani_msg_buildx_window_set_parent(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_SET_PARENT;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_set_parent);\n\n\tstruct yutani_msg_window_set_parent * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->parent_wid = wid2;\n}\n\nvoid yutani_msg_buildx_window_stack(yutani_msg_t * msg, yutani_wid_t wid, int z) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_STACK;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_stack);\n\n\tstruct yutani_msg_window_stack * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->z = z;\n}\n\n\nvoid yutani_msg_buildx_window_focus_change(yutani_msg_t * msg, yutani_wid_t wid, int focused) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_FOCUS_CHANGE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus_change);\n\n\tstruct yutani_msg_window_focus_change * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->focused = focused;\n}\n\n\nvoid yutani_msg_buildx_window_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, int32_t new_x, int32_t new_y, int32_t old_x, int32_t old_y, uint8_t buttons, uint8_t command, uint8_t modifiers) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_MOUSE_EVENT;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_mouse_event);\n\n\tstruct yutani_msg_window_mouse_event * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->new_x = new_x;\n\tmw->new_y = new_y;\n\tmw->old_x = old_x;\n\tmw->old_y = old_y;\n\tmw->buttons = buttons;\n\tmw->command = command;\n\tmw->modifiers = modifiers;\n}\n\n\nvoid yutani_msg_buildx_flip_region(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y, int32_t width, int32_t height) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_FLIP_REGION;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip_region);\n\n\tstruct yutani_msg_flip_region * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->x = x;\n\tmw->y = y;\n\tmw->width = width;\n\tmw->height = height;\n}\n\n\nvoid yutani_msg_buildx_window_resize(yutani_msg_t * msg, uint32_t type, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid, uint32_t flags) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = type;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize);\n\n\tstruct yutani_msg_window_resize * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->width = width;\n\tmw->height = height;\n\tmw->bufid = bufid;\n\tmw->flags = flags;\n}\n\n\nvoid yutani_msg_buildx_window_advertise(yutani_msg_t * msg, yutani_wid_t wid, uint32_t flags, uint32_t icon, uint32_t bufid, uint32_t width, uint32_t height, size_t length, char * data) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_ADVERTISE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_advertise) + length;\n\n\tstruct yutani_msg_window_advertise * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->flags = flags;\n\tmw->size = length;\n\tmw->icon = icon;\n\tmw->bufid = bufid;\n\tmw->width = width;\n\tmw->height = height;\n\tif (data) {\n\t\tmemcpy(mw->strings, data, mw->size);\n\t}\n}\n\n\nvoid yutani_msg_buildx_subscribe(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_SUBSCRIBE;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_unsubscribe(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_UNSUBSCRIBE;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_query_windows(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_QUERY_WINDOWS;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_notify(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_NOTIFY;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_session_end(yutani_msg_t * msg) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_SESSION_END;\n\tmsg->size  = sizeof(struct yutani_message);\n}\n\n\nvoid yutani_msg_buildx_window_focus(yutani_msg_t * msg, yutani_wid_t wid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_FOCUS;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus);\n\n\tstruct yutani_msg_window_focus * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n}\n\n\nvoid yutani_msg_buildx_key_bind(yutani_msg_t * msg, kbd_key_t key, kbd_mod_t mod, int response) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_KEY_BIND;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_bind);\n\n\tstruct yutani_msg_key_bind * mw = (void *)msg->data;\n\n\tmw->key = key;\n\tmw->modifiers = mod;\n\tmw->response = response;\n}\n\n\nvoid yutani_msg_buildx_window_drag_start(yutani_msg_t * msg, yutani_wid_t wid) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_DRAG_START;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_drag_start);\n\n\tstruct yutani_msg_window_drag_start * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n}\n\n\nvoid yutani_msg_buildx_window_update_shape(yutani_msg_t * msg, yutani_wid_t wid, int set_shape) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_UPDATE_SHAPE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_update_shape);\n\n\tstruct yutani_msg_window_update_shape * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->set_shape = set_shape;\n}\n\n\nvoid yutani_msg_buildx_window_warp_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_WARP_MOUSE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_warp_mouse);\n\n\tstruct yutani_msg_window_warp_mouse * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->x = x;\n\tmw->y = y;\n}\n\n\nvoid yutani_msg_buildx_window_show_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t show_mouse) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_SHOW_MOUSE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_show_mouse);\n\n\tstruct yutani_msg_window_show_mouse * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->show_mouse = show_mouse;\n}\n\n\nvoid yutani_msg_buildx_window_resize_start(yutani_msg_t * msg, yutani_wid_t wid, yutani_scale_direction_t direction) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_RESIZE_START;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize_start);\n\n\tstruct yutani_msg_window_resize_start * mw = (void *)msg->data;\n\n\tmw->wid = wid;\n\tmw->direction = direction;\n}\n\n\nvoid yutani_msg_buildx_special_request(yutani_msg_t * msg, yutani_wid_t wid, uint32_t request) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_SPECIAL_REQUEST;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_special_request);\n\n\tstruct yutani_msg_special_request * sr = (void *)msg->data;\n\n\tsr->wid   = wid;\n\tsr->request = request;\n}\n\nvoid yutani_msg_buildx_clipboard(yutani_msg_t * msg, char * content) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_CLIPBOARD;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_clipboard) + strlen(content);\n\n\tstruct yutani_msg_clipboard * cl = (void *)msg->data;\n\n\tcl->size = strlen(content);\n\tmemcpy(cl->content, content, strlen(content));\n}\n\nvoid yutani_msg_buildx_window_panel_size(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h) {\n\tmsg->magic = YUTANI_MSG__MAGIC;\n\tmsg->type  = YUTANI_MSG_WINDOW_PANEL_SIZE;\n\tmsg->size  = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_panel_size);\n\n\tstruct yutani_msg_window_panel_size * ps = (void *)msg->data;\n\tps->wid = wid;\n\tps->x = x;\n\tps->y = y;\n\tps->w = w;\n\tps->h = h;\n}\n\nint yutani_msg_send(yutani_t * y, yutani_msg_t * msg) {\n\treturn pex_reply(y->sock, msg->size, (char *)msg);\n}\n\nyutani_t * yutani_context_create(FILE * socket) {\n\tyutani_t * out = malloc(sizeof(yutani_t));\n\n\tout->sock = socket;\n\tout->display_width  = 0;\n\tout->display_height = 0;\n\tout->windows = hashmap_create_int(10);\n\tout->queued = list_create();\n\treturn out;\n}\n\n/**\n * yutani_init\n *\n * Connect to the compositor.\n *\n * Connects and handles the initial welcome message.\n */\nyutani_t * yutani_init(void) {\n\tchar * server_name = getenv(\"DISPLAY\");\n\tif (!server_name) {\n\t\tserver_name = \"compositor\";\n\t}\n\tFILE * c = pex_connect(server_name);\n\n\tif (!c) {\n\t\treturn NULL; /* Connection failed. */\n\t}\n\n\tyutani_t * y = yutani_context_create(c);\n\tyutani_msg_buildx_hello_alloc(m);\n\tyutani_msg_buildx_hello(m);\n\tyutani_msg_send(y, m);\n\n\tyutani_msg_t * mm = yutani_wait_for(y, YUTANI_MSG_WELCOME);\n\tstruct yutani_msg_welcome * mw = (void *)&mm->data;\n\ty->display_width = mw->display_width;\n\ty->display_height = mw->display_height;\n\ty->server_ident = server_name;\n\tfree(mm);\n\n\treturn y;\n}\n\n/**\n * yutani_window_create_flags\n *\n * Create a window with certain pre-specified properties.\n */\nyutani_window_t * yutani_window_create_flags(yutani_t * y, int width, int height, uint32_t flags, ...) {\n\tva_list ap;\n\tva_start(ap, flags);\n\tyutani_wid_t parent_wid = 0;\n\tif (flags & YUTANI_WINDOW_FLAG_PARENT_WID) {\n\t\tyutani_window_t * parent = va_arg(ap, yutani_window_t *);\n\t\tif (parent) parent_wid = parent->wid;\n\t}\n\tva_end(ap);\n\n\tyutani_window_t * win = malloc(sizeof(yutani_window_t));\n\n\tyutani_msg_buildx_window_new_flags_alloc(m);\n\tyutani_msg_buildx_window_new_flags(m, width, height, flags, parent_wid);\n\tyutani_msg_send(y, m);\n\n\tyutani_msg_t * mm = yutani_wait_for(y, YUTANI_MSG_WINDOW_INIT);\n\tstruct yutani_msg_window_init * mw = (void *)&mm->data;\n\n\twin->width = mw->width;\n\twin->height = mw->height;\n\twin->bufid = mw->bufid;\n\twin->wid = mw->wid;\n\twin->focused = 0;\n\twin->decorator_flags = 0;\n\twin->x = 0;\n\twin->y = 0;\n\twin->user_data = NULL;\n\twin->ctx = y;\n\twin->mouse_state = -1;\n\tfree(mm);\n\n\thashmap_set(y->windows, (void*)(uintptr_t)win->wid, win);\n\n\tchar key[1024];\n\tYUTANI_SHMKEY(y->server_ident, key, 1024, win);\n\n\tsize_t size = (width * height * 4);\n\twin->buffer = shm_obtain(key, &size);\n\treturn win;\n\n}\n\n/**\n * yutani_window_create\n *\n * Create a basic window.\n */\nyutani_window_t * yutani_window_create(yutani_t * y, int width, int height) {\n\treturn yutani_window_create_flags(y,width,height,0);\n}\n\n/**\n * yutani_flip\n *\n * Ask the server to redraw the window.\n */\nvoid yutani_flip(yutani_t * y, yutani_window_t * win) {\n\tyutani_msg_buildx_flip_alloc(m);\n\tyutani_msg_buildx_flip(m, win->wid);\n\tyutani_msg_send(y, m);\n}\n\n/**\n * yutani_flip_region\n *\n * Ask the server to redraw a region relative the window.\n */\nvoid yutani_flip_region(yutani_t * yctx, yutani_window_t * win, int32_t x, int32_t y, int32_t width, int32_t height) {\n\tyutani_msg_buildx_flip_region_alloc(m);\n\tyutani_msg_buildx_flip_region(m, win->wid, x, y, width, height);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_close\n *\n * Close a window. A closed window should not be used again,\n * and its associated buffers will be freed.\n */\nvoid yutani_close(yutani_t * y, yutani_window_t * win) {\n\tyutani_msg_buildx_window_close_alloc(m);\n\tyutani_msg_buildx_window_close(m, win->wid);\n\tyutani_msg_send(y, m);\n\n\t/* Now destroy our end of the window */\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(y->server_ident, key, 1024, win->bufid);\n\t\tshm_release(key);\n\t}\n\n\thashmap_remove(y->windows, (void*)(uintptr_t)win->wid);\n\tfree(win);\n}\n\n/**\n * yutani_window_move\n *\n * Request a window be moved to new a location on screen.\n */\nvoid yutani_window_move(yutani_t * yctx, yutani_window_t * window, int x, int y) {\n\tyutani_msg_buildx_window_move_alloc(m);\n\tyutani_msg_buildx_window_move(m, window->wid, x, y);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_move_relative\n *\n * Move a window to a location based on the local coordinate space of a base window.\n */\nvoid yutani_window_move_relative(yutani_t * yctx, yutani_window_t * window, yutani_window_t * base, int x, int y) {\n\tyutani_msg_buildx_window_move_relative_alloc(m);\n\tyutani_msg_buildx_window_move_relative(m, window->wid, base->wid, x, y);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_set_parent\n *\n * Tell the compositor that this window has a parent that should inherit its focus state\n * for advertisements, and certain other properties (tbd.).\n */\nvoid yutani_window_set_parent(yutani_t * yctx, yutani_window_t * window, yutani_window_t * parent) {\n\tyutani_msg_buildx_window_set_parent_alloc(m);\n\tyutani_msg_buildx_window_set_parent(m, window->wid, parent ? parent->wid : 0);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_set_stack\n *\n * Set the stacking order of the window.\n */\nvoid yutani_set_stack(yutani_t * yctx, yutani_window_t * window, int z) {\n\tyutani_msg_buildx_window_stack_alloc(m);\n\tyutani_msg_buildx_window_stack(m, window->wid, z);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_resize\n *\n * Request that the server resize a window.\n */\nvoid yutani_window_resize(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) {\n\tyutani_msg_buildx_window_resize_alloc(m);\n\tyutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_REQUEST, window->wid, width, height, 0, 0);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_resize_offer\n *\n * In a response to a server resize message, offer an alternative size.\n * Allows the client to reject a user-provided resize request due to\n * size constraints or other reasons.\n */\nvoid yutani_window_resize_offer(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) {\n\tyutani_msg_buildx_window_resize_alloc(m);\n\tyutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_OFFER, window->wid, width, height, 0, 0);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_resize_accept\n *\n * Accept the server's resize request, initialize new buffers\n * and all the client to draw into the new buffers.\n */\nvoid yutani_window_resize_accept(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) {\n\tyutani_msg_buildx_window_resize_alloc(m);\n\tyutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_ACCEPT, window->wid, width, height, 0, 0);\n\tyutani_msg_send(yctx, m);\n\n\t/* Now wait for the new bufid */\n\tyutani_msg_t * mm = yutani_wait_for(yctx, YUTANI_MSG_RESIZE_BUFID);\n\tstruct yutani_msg_window_resize * wr = (void*)mm->data;\n\n\tif (window->wid != wr->wid) {\n\t\t/* I am not sure what to do here. */\n\t\treturn;\n\t}\n\n\t/* Update the window */\n\twindow->width = wr->width;\n\twindow->height = wr->height;\n\twindow->oldbufid = window->bufid;\n\twindow->bufid = wr->bufid;\n\tfree(mm);\n\n\t/* Allocate the buffer */\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY(yctx->server_ident, key, 1024, window);\n\n\t\tsize_t size = (window->width * window->height * 4);\n\t\twindow->buffer = shm_obtain(key, &size);\n\t}\n}\n\n/**\n * yutani_window_resize_done\n *\n * The client has finished drawing into the new buffers after\n * accepting a resize request and the server should now\n * discard the old buffer and switch to the new one.\n */\nvoid yutani_window_resize_done(yutani_t * yctx, yutani_window_t * window) {\n\t/* Destroy the old buffer */\n\t{\n\t\tchar key[1024];\n\t\tYUTANI_SHMKEY_EXP(yctx->server_ident, key, 1024, window->oldbufid);\n\t\tshm_release(key);\n\t}\n\n\tyutani_msg_buildx_window_resize_alloc(m);\n\tyutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_DONE, window->wid, window->width, window->height, window->bufid, 0);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_advertise\n *\n * Provide a title for a window to have it show up\n * in the panel window list.\n */\nvoid yutani_window_advertise(yutani_t * yctx, yutani_window_t * window, char * name) {\n\n\tuint32_t flags = 0; /* currently, no client flags */\n\tuint32_t length = 0;\n\tuint32_t icon = 0;\n\tchar * strings;\n\n\tif (!name) {\n\t\tlength = 1;\n\t\tstrings = \" \";\n\t} else {\n\t\tlength = strlen(name) + 1;\n\t\tstrings = name;\n\t\ticon = strlen(name);\n\t}\n\n\tyutani_msg_buildx_window_advertise_alloc(m, length);\n\tyutani_msg_buildx_window_advertise(m, window->wid, flags, icon, 0, 0, 0, length, strings);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_advertise_icon\n *\n * Provide a title and an icon for the panel to show.\n *\n * Note that three additional fields are available in the advertisement\n * messages which are not yet used. This is to allow for future expansion.\n */\nvoid yutani_window_advertise_icon(yutani_t * yctx, yutani_window_t * window, char * name, char * icon) {\n\n\tuint32_t flags = 0; /* currently no client flags */\n\tuint32_t iconx = 0;\n\tuint32_t length = strlen(name) + strlen(icon) + 2;\n\tchar * strings = malloc(length);\n\n\tif (name) {\n\t\tmemcpy(&strings[0], name, strlen(name)+1);\n\t\ticonx = strlen(name);\n\t}\n\tif (icon) {\n\t\tmemcpy(&strings[strlen(name)+1], icon, strlen(icon)+1);\n\t\ticonx = strlen(name)+1;\n\t}\n\n\tyutani_msg_buildx_window_advertise_alloc(m, length);\n\tyutani_msg_buildx_window_advertise(m, window->wid, flags, iconx, 0, 0, 0, length, strings);\n\tyutani_msg_send(yctx, m);\n\tfree(strings);\n}\n\n/**\n * yutani_subscribe_windows\n *\n * Subscribe to messages about new window advertisements.\n * Basically, if you're a panel, you want to do this, so\n * you can know when windows move around or change focus.\n */\nvoid yutani_subscribe_windows(yutani_t * y) {\n\tyutani_msg_buildx_subscribe_alloc(m);\n\tyutani_msg_buildx_subscribe(m);\n\tyutani_msg_send(y, m);\n}\n\n/**\n * yutani_unsubscribe_windows\n *\n * If you no longer wish to receive window change messages,\n * you can unsubscribe your client from them.\n */\nvoid yutani_unsubscribe_windows(yutani_t * y) {\n\tyutani_msg_buildx_unsubscribe_alloc(m);\n\tyutani_msg_buildx_unsubscribe(m);\n\tyutani_msg_send(y, m);\n}\n\n/**\n * yutani_query_windows\n *\n * When notified of changes, call this to request\n * the new information.\n */\nvoid yutani_query_windows(yutani_t * y) {\n\tyutani_msg_buildx_query_windows_alloc(m);\n\tyutani_msg_buildx_query_windows(m);\n\tyutani_msg_send(y, m);\n}\n\n/**\n * yutani_session_end\n *\n * For use by session managers, tell the compositor\n * that the session has ended and it should inform\n * other clients of this so they can exit.\n */\nvoid yutani_session_end(yutani_t * y) {\n\tyutani_msg_buildx_session_end_alloc(m);\n\tyutani_msg_buildx_session_end(m);\n\tyutani_msg_send(y, m);\n}\n\n/**\n * yutani_focus_window\n *\n * Change focus to the given window. Mostly used by\n * panels and other window management things, but if you\n * have a multi-window application, such as one with a\n * model dialog, and you want to force focus away from one\n * window and onto another, you can use this.\n */\nvoid yutani_focus_window(yutani_t * yctx, yutani_wid_t wid) {\n\tyutani_msg_buildx_window_focus_alloc(m);\n\tyutani_msg_buildx_window_focus(m, wid);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_key_bind\n *\n * Request a key combination always be sent to this client.\n * You can request for the combination to be sent only to\n * this client (steal binding) or to also go to other clients\n * (spy binding), the latter of which is useful for catching\n * changes to modifier keys.\n */\nvoid yutani_key_bind(yutani_t * yctx, kbd_key_t key, kbd_mod_t mod, int response) {\n\tyutani_msg_buildx_key_bind_alloc(m);\n\tyutani_msg_buildx_key_bind(m, key,mod,response);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_drag_start\n *\n * Begin a mouse-driven window movement action.\n * Typically used by decorators to start moving the window\n * when the user clicks and drags on the title bar.\n */\nvoid yutani_window_drag_start(yutani_t * yctx, yutani_window_t * window) {\n\tyutani_msg_buildx_window_drag_start_alloc(m);\n\tyutani_msg_buildx_window_drag_start(m, window->wid);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_drag_start_wid\n *\n * Same as above, but takes a wid (of a presumably-foreign window)\n * instead of a window pointer; used by the panel to initiate\n * window movement through a drop-down menu for other clients.\n */\nvoid yutani_window_drag_start_wid(yutani_t * yctx, yutani_wid_t wid) {\n\tyutani_msg_buildx_window_drag_start_alloc(m);\n\tyutani_msg_buildx_window_drag_start(m, wid);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_update_shape\n *\n * Change the window shaping threshold.\n * Allows partially-transparent windows to control whether they\n * should still receive mouse events in their transparent regions.\n */\nvoid yutani_window_update_shape(yutani_t * yctx, yutani_window_t * window, int set_shape) {\n\tyutani_msg_buildx_window_update_shape_alloc(m);\n\tyutani_msg_buildx_window_update_shape(m, window->wid, set_shape);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_warp_mouse\n *\n * Move the mouse to a locate relative to the window.\n * Only works with relative mouse cursor.\n * Useful for games.\n *\n * TODO: We still need a way to lock the cursor to a particular window.\n *       Even in games where warping happens quickly, we can still\n *       end up with the cursor outside of the window when a click happens.\n */\nvoid yutani_window_warp_mouse(yutani_t * yctx, yutani_window_t * window, int32_t x, int32_t y) {\n\tyutani_msg_buildx_window_warp_mouse_alloc(m);\n\tyutani_msg_buildx_window_warp_mouse(m, window->wid, x, y);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_window_show_mouse\n *\n * Set the cursor type. Used to change to risize and drag indicators.\n * Could be used to show a text insertion bar, or a link-clicking hand,\n * but those cursors need to be added in the server.\n *\n * TODO: We should add a way to use client-provided cursor textures.\n */\nvoid yutani_window_show_mouse(yutani_t * yctx, yutani_window_t * window, int32_t show_mouse) {\n\tif (window->mouse_state != show_mouse) {\n\t\twindow->mouse_state = show_mouse;\n\t\tyutani_msg_buildx_window_show_mouse_alloc(m);\n\t\tyutani_msg_buildx_window_show_mouse(m, window->wid, show_mouse);\n\t\tyutani_msg_send(yctx, m);\n\t}\n}\n\n/**\n * yutani_window_resize_start\n *\n * Start a mouse-driven window resize action.\n * Used by decorators.\n */\nvoid yutani_window_resize_start(yutani_t * yctx, yutani_window_t * window, yutani_scale_direction_t direction) {\n\tyutani_msg_buildx_window_resize_start_alloc(m);\n\tyutani_msg_buildx_window_resize_start(m, window->wid, direction);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_special_request\n *\n * Send one of the special request messages that aren't\n * important enough to get their own message types.\n *\n * (MAXIMIZE, PLEASE_CLOSE, CLIPBOARD)\n *\n * Note that, especially in the CLIPBOARD case, the\n * window does not to be set.\n */\nvoid yutani_special_request(yutani_t * yctx, yutani_window_t * window, uint32_t request) {\n\t/* wid isn't necessary; if window is null, set to 0 */\n\tyutani_msg_buildx_special_request_alloc(m);\n\tyutani_msg_buildx_special_request(m, window ? window->wid : 0, request);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_special_request_wid\n *\n * Same as above, but takes a wid instead of a window pointer,\n * for use with foreign windows.\n */\nvoid yutani_special_request_wid(yutani_t * yctx, yutani_wid_t wid, uint32_t request) {\n\t/* For working with other applications' windows */\n\tyutani_msg_buildx_special_request_alloc(m);\n\tyutani_msg_buildx_special_request(m, wid, request);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_set_clipboard\n *\n * Set the clipboard content.\n *\n * If the clipboard content is too large for a message,\n * it will be stored in a file and a special clipboard string\n * will be set to indicate the real contents are\n * in the file.\n *\n * To get the clipboard contents, send a CLIPBOARD special\n * request and wait for the CLIPBOARD response message.\n */\nvoid yutani_set_clipboard(yutani_t * yctx, char * content) {\n\t/* Set clipboard contents */\n\tint len = strlen(content);\n\tif (len > 511) {\n\t\tchar tmp_file[100];\n\t\tsprintf(tmp_file, \"/tmp/.clipboard.%s\", yctx->server_ident);\n\t\tFILE * tmp = fopen(tmp_file, \"w+\");\n\t\tfwrite(content, len, 1, tmp);\n\t\tfclose(tmp);\n\n\t\tchar tmp_data[100];\n\t\tsprintf(tmp_data, \"\\002 %d\", len);\n\t\tyutani_msg_buildx_clipboard_alloc(m, strlen(tmp_data));\n\t\tyutani_msg_buildx_clipboard(m, tmp_data);\n\t\tyutani_msg_send(yctx, m);\n\t} else {\n\t\tyutani_msg_buildx_clipboard_alloc(m, len);\n\t\tyutani_msg_buildx_clipboard(m, content);\n\t\tyutani_msg_send(yctx, m);\n\t}\n}\n\nvoid yutani_window_panel_size(yutani_t * yctx, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h) {\n\tyutani_msg_buildx_window_panel_size_alloc(m);\n\tyutani_msg_buildx_window_panel_size(m,wid,x,y,w,h);\n\tyutani_msg_send(yctx, m);\n}\n\n/**\n * yutani_open_clipboard\n *\n * Open the clipboard contents file.\n */\nFILE * yutani_open_clipboard(yutani_t * yctx) {\n\tchar tmp_file[100];\n\tsprintf(tmp_file, \"/tmp/.clipboard.%s\", yctx->server_ident);\n\treturn fopen(tmp_file, \"r\");\n}\n\n/**\n * init_graphics_yutani\n *\n * Create a graphical context around a Yutani window.\n */\ngfx_context_t * init_graphics_yutani(yutani_window_t * window) {\n\tgfx_context_t * out = malloc(sizeof(gfx_context_t));\n\tout->width  = window->width;\n\tout->height = window->height;\n\tout->stride = window->width * sizeof(uint32_t);\n\tout->depth  = 32;\n\tout->size   = GFX_H(out) * GFX_W(out) * GFX_B(out);\n\tout->buffer = window->buffer;\n\tout->backbuffer = out->buffer;\n\tout->clips  = NULL;\n\treturn out;\n}\n\n/**\n * init_graphics_yutani_double_buffer\n *\n * Create a graphics context around a Yutani window\n * with a separate backing store for double-buffering.\n */\ngfx_context_t *  init_graphics_yutani_double_buffer(yutani_window_t * window) {\n\tgfx_context_t * out = init_graphics_yutani(window);\n\tout->backbuffer = malloc(GFX_B(out) * GFX_W(out) * GFX_H(out));\n\treturn out;\n}\n\n/**\n * reinit_graphics_yutani\n *\n * Reinitialize a graphics context, such as when\n * the window size changes.\n */\nvoid reinit_graphics_yutani(gfx_context_t * out, yutani_window_t * window) {\n\tout->width  = window->width;\n\tout->height = window->height;\n\tout->stride = window->width * 4;\n\tout->depth  = 32;\n\tout->size   = GFX_H(out) * GFX_W(out) * GFX_B(out);\n\n\tif (out->clips && out->clips_size != out->height) {\n\t\tfree(out->clips);\n\t\tout->clips = NULL;\n\t\tout->clips_size = 0;\n\t}\n\n\tif (out->buffer == out->backbuffer) {\n\t\tout->buffer = window->buffer;\n\t\tout->backbuffer = out->buffer;\n\t} else {\n\t\tout->buffer = window->buffer;\n\t\tout->backbuffer = realloc(out->backbuffer, GFX_B(out) * GFX_W(out) * GFX_H(out));\n\t}\n}\n\n/**\n * release_graphics_yutani\n *\n * Release a graphics context.\n * XXX: This seems to work generically for any graphics context?\n */\nvoid release_graphics_yutani(gfx_context_t * gfx) {\n\tif (gfx->backbuffer != gfx->buffer) {\n\t\tfree(gfx->backbuffer);\n\t}\n\tfree(gfx);\n}\n\nvoid yutani_internal_refocus(yutani_t * yctx, yutani_window_t * window) {\n\t/* Check if a refocus is already in our queue to be processed */\n\tforeach(node, yctx->queued) {\n\t\tyutani_msg_t * out = (yutani_msg_t *)node->value;\n\t\tif (out->type == YUTANI_MSG_WINDOW_FOCUS_CHANGE) return;\n\t}\n\t/* Otherwise, produce an artificial one matching the reported focus state of the window */\n\tyutani_msg_t * msg = malloc(sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus_change));\n\tyutani_msg_buildx_window_focus_change(msg, window->wid, window->focused);\n\tlist_insert(yctx->queued, msg);\n}\n"
  },
  {
    "path": "libc/arch/aarch64/bad.c",
    "content": "/* bad math */\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n\ndouble sqrt(double x) {\n\tasm volatile (\"fsqrt %d0, %d1\" : \"=w\"(x) : \"w\"(x));\n\treturn x;\n}\n\ndouble tan(double theta) {\n\treturn sin(theta) / cos(theta);\n}\n\n/**\n * Polynomial approximation of arctangent\n *\n * @see https://www.dsprelated.com/showarticle/1052.php\n */\nstatic inline double _atan(double z) {;\n\tdouble n1 = 0.97239411;\n\tdouble n2 = -0.19194795;\n\treturn (n1 + n2 * z * z) * z;\n}\ndouble atan2(double y, double x) {\n\tif (x != 0.0) {\n\t\tif (fabs(x) > fabs(y)) {\n\t\t\tdouble z = y / x;\n\t\t\tif (x > 0.0) return _atan(z);\n\t\t\telse if (y >= 0.0) return _atan(z) + M_PI;\n\t\t\telse return _atan(z) - M_PI;\n\t\t} else {\n\t\t\tdouble z = x / y;\n\t\t\tif (y > 0.0) return -_atan(z) + M_PI/2.0;\n\t\t\telse return -_atan(z) - M_PI/2.0;\n\t\t}\n\t} else {\n\t\tif (y > 0.0) return M_PI/2.0;\n\t\telse if (y < 0.0) return -M_PI/2.0;\n\t}\n\treturn 0.0;\n}\n\ndouble pow(double x, double y) {\n\tif (getenv(\"LIBM_DEBUG\")) {\n\t\tfprintf(stderr, \"pow(%f, %f)\\n\", x, y);\n\t}\n\treturn x;\n}\n\ndouble fmod(double x, double y) {\n\tint _x = fpclassify(x);\n\tint _y = fpclassify(y);\n\n\tif (_y == FP_NAN) return NAN;\n\tif (_x == FP_INFINITE) return NAN;\n\tif (_y == FP_ZERO) return NAN;\n\tif (_x == FP_ZERO) return x;\n\n\tlong div = x / y;\n\treturn x - (double)div * y;\n}\n"
  },
  {
    "path": "libc/arch/aarch64/crt0.S",
    "content": ".global _start\n.type   _start, %function\n\n_start:\n\tmov x4, x0\n\tadr x0, main\n\tmov x3, x0\n\tmov x0, x4\n\tbl\tpre_main\n"
  },
  {
    "path": "libc/arch/aarch64/crti.S",
    "content": ".global _init\n.section .init\n_init:\n\tstp x29,x30,[sp,-16]!\n\tmov x29,sp\n\n.global _fini\n.section .fini\n_fini:\n\tstp x29,x30,[sp,-16]!\n\tmov x29,sp\n\n"
  },
  {
    "path": "libc/arch/aarch64/crtn.S",
    "content": ".section .init\n\tldp x29,x30,[sp],#16\n\tret\n\n.section .fini\n\tldp x29,x30,[sp],#16\n\tret\n\n"
  },
  {
    "path": "libc/arch/aarch64/memcpy.c",
    "content": "#include <stdint.h>\n#include <string.h>\n#include <stddef.h>\n\nvoid * memcpy(void * restrict dest, const void * restrict src, size_t n) {\n\tuint64_t * d_64 = dest;\n\tconst uint64_t * s_64 = src;\n\n\tfor (; n >= 8; n -= 8) {\n\t\t*d_64++ = *s_64++;\n\t}\n\n\tuint32_t * d_32 = (void*)d_64;\n\tconst uint32_t * s_32 = (const void*)s_64;\n\n\tfor (; n >= 4; n -= 4) {\n\t\t*d_32++ = *s_32++;\n\t}\n\n\tuint8_t * d = (void*)d_32;\n\tconst uint8_t * s = (const void*)s_32;\n\n\tfor (; n > 0; n--) {\n\t\t*d++ = *s++;\n\t}\n\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/arch/aarch64/memset.c",
    "content": "#include <stddef.h>\n\nvoid * memset(void * dest, int c, size_t n) {\n\tsize_t i = 0;\n\tfor ( ; i < n; ++i ) {\n\t\t((char *)dest)[i] = c;\n\t}\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/arch/aarch64/setjmp.c",
    "content": "#include <stdio.h>\n#include <setjmp.h>\n\nasm (\n\t\".globl setjmp\\n\"\n\t\"setjmp:\\n\"\n\t\"mov x2, sp\\n\"\n\t\"str x2,  [x0]\\n\"\n\t\"stp x19, x20, [x0, (2 * 16)]\\n\"\n\t\"stp x21, x22, [x0, (3 * 16)]\\n\"\n\t\"stp x23, x24, [x0, (4 * 16)]\\n\"\n\t\"stp x25, x26, [x0, (5 * 16)]\\n\"\n\t\"stp x27, x28, [x0, (6 * 16)]\\n\"\n\t\"stp x29, x30, [x0, (1 * 16)]\\n\"\n\t\"mov x0, 0\\n\"\n\t\"ret\\n\"\n\t\".globl longjmp\\n\"\n\t\"longjmp:\\n\"\n\t\"ldr x2, [x0]\\n\"\n\t\"ldp x19, x20, [x0, (2 * 16)]\\n\"\n\t\"ldp x21, x22, [x0, (3 * 16)]\\n\"\n\t\"ldp x23, x24, [x0, (4 * 16)]\\n\"\n\t\"ldp x25, x26, [x0, (5 * 16)]\\n\"\n\t\"ldp x27, x28, [x0, (6 * 16)]\\n\"\n\t\"ldp x29, x30, [x0, (1 * 16)]\\n\"\n\t\"mov sp, x2\\n\"\n\t\"mov x0, x1\\n\"\n\t\"ret\\n\"\n);\n\n#if 0\nint _setjmp(jmp_buf env);\nvoid _longjmp(jmp_buf env, int val);\n\nint setjmp(jmp_buf env) {\n\tfprintf(stderr, \"setjmp called\\n\");\n\treturn _setjmp(env);\n}\nvoid longjmp(jmp_buf env, int val) {\n\tfprintf(stderr, \"longjmp called\\n\");\n\t_longjmp(env,val);\n}\n#endif\n"
  },
  {
    "path": "libc/arch/x86_64/crt0.S",
    "content": ".global _start\n\n_start:\n  movq $main, %rcx\n  and $0xFFFFFFFFFFFFFFF0, %rsp\n  call pre_main /* call pre_main */\n\n\n"
  },
  {
    "path": "libc/arch/x86_64/crti.S",
    "content": ".global _init\n.section .init\n_init:\n\tpush %rbp\n\tmovq %rsp, %rbp\n\n.global _fini\n.section .fini\n_fini:\n\tpush %rbp\n\tmovq %rsp, %rbp\n"
  },
  {
    "path": "libc/arch/x86_64/crtn.S",
    "content": ".section .init\n\tpop %rbp\n\tret\n\n.section .fini\n\tpop %rbp\n\tret\n\n"
  },
  {
    "path": "libc/arch/x86_64/math.c",
    "content": "#include <math.h>\n\ndouble floor(double x) {\n\tif (x == 0.0) return x;\n\n\tdouble out;\n\tasm volatile (\n\t\t\"frndint\\n\"\n\t\t: \"=t\"(out) : \"0\"(x)\n\t);\n\tif (out > x) return out - 1.0;\n\treturn out;\n}\n\ndouble pow(double x, double y) {\n\tdouble out;\n\tasm volatile (\n\t\t\"fyl2x;\"\n\t\t\"fld %%st;\"\n\t\t\"frndint;\"\n\t\t\"fsub %%st,%%st(1);\"\n\t\t\"fxch;\"\n\t\t\"fchs;\"\n\t\t\"f2xm1;\"\n\t\t\"fld1;\"\n\t\t\"faddp;\"\n\t\t\"fxch;\"\n\t\t\"fld1;\"\n\t\t\"fscale;\"\n\t\t\"fstp %%st(1);\"\n\t\t\"fmulp;\" : \"=t\"(out) : \"0\"(x),\"u\"(y) : \"st(1)\" );\n\treturn out;\n}\n\ndouble fmod(double x, double y) {\n\tlong double out;\n\tasm volatile (\n\t\t\"1: fprem;\" /* Partial remainder */\n\t\t\"   fnstsw %%ax;\" /* store status word */\n\t\t\"   sahf;\" /* store AX (^ FPU status) into flags */\n\t\t\"   jp 1b;\" /* jump back to 1 above if parity flag==1 */\n\t\t: \"=t\"(out) : \"0\"(x), \"u\"(y) : \"ax\", \"cc\");\n\treturn out;\n}\n\ndouble tan(double x) {\n\tdouble out;\n\tdouble _x = x;\n\tdouble one;\n\tasm volatile (\n\t\t\"fldl %2\\n\"\n\t\t\"fptan\\n\"\n\t\t\"fstpl %1\\n\"\n\t\t\"fstpl %0\\n\"\n\t\t: \"=m\"(out), \"=m\"(one) : \"m\"(_x)\n\t);\n\treturn out;\n}\n\ndouble atan2(double y, double x) {\n\tdouble out;\n\tdouble _x = x;\n\tdouble _y = y;\n\tasm volatile (\n\t\t\"fldl %1\\n\"\n\t\t\"fldl %2\\n\"\n\t\t\"fpatan\\n\"\n\t\t\"fstpl %0\\n\"\n\t\t: \"=m\"(out) : \"m\"(_y), \"m\"(_x)\n\t);\n\treturn out;\n}\n\ndouble sqrt(double x) {\n\t/* This is what __builtin_sqrt was doing anyway? */\n\tasm volatile (\n\t\t\"sqrtsd %1, %0\\n\"\n\t\t: \"=x\"(x) : \"x\"(x)\n\t);\n\treturn x;\n}\n\n"
  },
  {
    "path": "libc/arch/x86_64/memcpy.c",
    "content": "#include <stddef.h>\n\nvoid * memcpy(void * restrict dest, const void * restrict src, size_t n) {\n\tasm volatile(\"cld; rep movsb\"\n\t            : \"=c\"((int){0})\n\t            : \"D\"(dest), \"S\"(src), \"c\"(n)\n\t            : \"flags\", \"memory\");\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/arch/x86_64/memset.c",
    "content": "#include <stddef.h>\n\nvoid * memset(void * dest, int c, size_t n) {\n\tasm volatile(\"cld; rep stosb\"\n\t             : \"=c\"((int){0})\n\t             : \"rdi\"(dest), \"a\"(c), \"c\"(n)\n\t             : \"flags\", \"memory\", \"rdi\");\n\treturn dest;\n}\n\n"
  },
  {
    "path": "libc/arch/x86_64/setjmp.c",
    "content": "#include <setjmp.h>\n\n\n__attribute__((naked))\n__attribute__((returns_twice))\nint setjmp(jmp_buf env) {\n\tasm volatile (\n\t\t\"leaq 8(%%rsp), %%rax\\n\" /* Return address into rax */\n\t\t\"movq %%rax, 0(%%rdi)\\n\" /* jmp_buf[0] = rsp */\n\t\t\"movq %%rbp, 8(%%rdi)\\n\" /* jmp_buf[1] = rbp */\n\t\t\"movq (%%rsp), %%rax\\n\"\n\t\t\"movq %%rax, 16(%%rdi)\\n\" /* jmp_buf[2] = return address */\n\t\t\"movq %%rbx, 24(%%rdi)\\n\" /* jmp_buf[3] = rbx */\n\t\t\"movq %%r12, 32(%%rdi)\\n\" /* jmp_buf[4] = r12 */\n\t\t\"movq %%r13, 40(%%rdi)\\n\" /* jmp_buf[5] = r12 */\n\t\t\"movq %%r14, 48(%%rdi)\\n\" /* jmp_buf[6] = r12 */\n\t\t\"movq %%r15, 56(%%rdi)\\n\" /* jmp_buf[7] = r12 */\n\t\t\"xor %%rax, %%rax\\n\" /* return 0 */\n\t\t\"retq\"\n\t\t:::\"memory\"\n\t);\n}\n\n__attribute__((naked))\n__attribute__((noreturn))\nvoid longjmp(jmp_buf env, int val) {\n\tasm volatile (\n\t\t\"movq 0(%%rdi),  %%rsp\\n\"\n\t\t\"movq 8(%%rdi),  %%rbp\\n\"\n\t\t\"movq 24(%%rdi), %%rbx\\n\"\n\t\t\"movq 32(%%rdi), %%r12\\n\"\n\t\t\"movq 40(%%rdi), %%r13\\n\"\n\t\t\"movq 48(%%rdi), %%r14\\n\"\n\t\t\"movq 56(%%rdi), %%r15\\n\"\n\t\t\"movq %%rsi, %%rax\\n\"\n\t\t\"jmpq *16(%%rdi)\\n\"\n\t\t:::\"memory\"\n\t);\n}\n\n"
  },
  {
    "path": "libc/assert/assert.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n\nvoid __assert_func(const char * file, int line, const char * func, const char * failedexpr) {\n fprintf(stderr, \"Assertion failed in %s:%d (%s): %s\\n\", file, line, func, failedexpr);\n exit(1);\n}\n"
  },
  {
    "path": "libc/ctype/_ctype.c",
    "content": "#include <ctype.h>\n\nunsigned char _ctype_[256]= {\n\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\n\t_C,\t_C|_S, _C|_S, _C|_S,\t_C|_S,\t_C|_S,\t_C,\t_C,\n\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\n\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\t_C,\n\t_S|_B,\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\n\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\n\t_N,\t_N,\t_N,\t_N,\t_N,\t_N,\t_N,\t_N,\n\t_N,\t_N,\t_P,\t_P,\t_P,\t_P,\t_P,\t_P,\n\t_P,\t_U|_X,\t_U|_X,\t_U|_X,\t_U|_X,\t_U|_X,\t_U|_X,\t_U,\n\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\n\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\t_U,\n\t_U,\t_U,\t_U,\t_P,\t_P,\t_P,\t_P,\t_P,\n\t_P,\t_L|_X,\t_L|_X,\t_L|_X,\t_L|_X,\t_L|_X,\t_L|_X,\t_L,\n\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\n\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\t_L,\n\t_L,\t_L,\t_L,\t_P,\t_P,\t_P,\t_P,\t_C\n};\n"
  },
  {
    "path": "libc/ctype/isalnum.c",
    "content": "#include <ctype.h>\n\nint isalnum(int c) {\n    return isalpha(c) || isdigit(c);\n}\n"
  },
  {
    "path": "libc/ctype/isalpha.c",
    "content": "int isalpha(int c) {\n    return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));\n}\n"
  },
  {
    "path": "libc/ctype/isascii.c",
    "content": "int isascii(int c) {\n\treturn (c <= 0x7f);\n}\n"
  },
  {
    "path": "libc/ctype/iscntrl.c",
    "content": "int iscntrl(int c) {\n    return ((c >= 0 && c <= 0x1f) || (c == 0x7f));\n}\n"
  },
  {
    "path": "libc/ctype/isdigit.c",
    "content": "int isdigit(int c) {\n\treturn (c >= '0' && c <= '9');\n}\n"
  },
  {
    "path": "libc/ctype/isgraph.c",
    "content": "int isgraph(int c) {\n    return (c >= '!' && c <= '~');\n}\n"
  },
  {
    "path": "libc/ctype/islower.c",
    "content": "int islower(int c) {\n    return (c >= 'a' && c <= 'z');\n}\n"
  },
  {
    "path": "libc/ctype/isprint.c",
    "content": "#include <ctype.h>\n\nint isprint(int c) {\n    return isgraph(c) || c == ' ';\n}\n"
  },
  {
    "path": "libc/ctype/ispunct.c",
    "content": "#include <ctype.h>\n\nint ispunct(int c) {\n    return isgraph(c) && !isalnum(c);\n}\n"
  },
  {
    "path": "libc/ctype/isspace.c",
    "content": "int isspace(int c) {\n    return (c == '\\f' || c == '\\n' || c == '\\r' || c == '\\t' || c == '\\v' || c == ' ');\n}\n"
  },
  {
    "path": "libc/ctype/isupper.c",
    "content": "int isupper(int c) {\n    return (c >= 'A' && c <= 'Z');\n}\n"
  },
  {
    "path": "libc/ctype/isxdigit.c",
    "content": "int isxdigit(int c) {\n    return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));\n}\n"
  },
  {
    "path": "libc/ctype/tolower.c",
    "content": "int tolower(int c) {\n\tif (c >= 'A' && c <= 'Z') {\n\t\treturn c - 'A' + 'a';\n\t}\n\treturn c;\n}\n"
  },
  {
    "path": "libc/ctype/toupper.c",
    "content": "int toupper(int c) {\n\tif (c >= 'a' && c <= 'z') {\n\t\treturn c - 'a' + 'A';\n\t}\n\treturn c;\n}\n\n"
  },
  {
    "path": "libc/dirent/dir.c",
    "content": "#include <unistd.h>\n#include <stdlib.h>\n#include <fcntl.h>\n#include <string.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n#include <bits/dirent.h>\n\nDEFN_SYSCALL3(readdir, SYS_READDIR, int, int, void *);\n\nDIR * opendir (const char * dirname) {\n\tint fd = open(dirname, O_RDONLY|O_DIRECTORY);\n\tif (fd < 0) {\n\t\t/* errno was set by open */\n\t\treturn NULL;\n\t}\n\n\tDIR * dir = (DIR *)malloc(sizeof(DIR));\n\tdir->fd = fd;\n\tdir->cur_entry = -1;\n\treturn dir;\n}\n\nint closedir (DIR * dir) {\n\tif (dir && (dir->fd != -1)) {\n\t\treturn close(dir->fd);\n\t} else {\n\t\treturn -EBADF;\n\t}\n}\n\nstruct dirent * readdir (DIR * dirp) {\n\tstatic struct dirent ent;\n\n\tint ret = syscall_readdir(dirp->fd, ++dirp->cur_entry, &ent);\n\tif (ret < 0) {\n\t\terrno = -ret;\n\t\tmemset(&ent, 0, sizeof(struct dirent));\n\t\treturn NULL;\n\t}\n\n\tif (ret == 0) {\n\t\t/* end of directory */\n\t\tmemset(&ent, 0, sizeof(struct dirent));\n\t\treturn NULL;\n\t}\n\n\treturn &ent;\n}\n\nlong telldir(DIR * dirp) {\n\treturn (long)dirp->cur_entry;\n}\n\nvoid rewinddir(DIR * dirp) {\n\tdirp->cur_entry = -1;\n}\n\nvoid seekdir(DIR * dirp, long loc) {\n\tdirp->cur_entry = loc;\n}\n\nstruct dirent32 {\n\tunsigned int d_ino;\n\tchar d_name[256];\n};\n\nstruct dirent32 * readdir32 (DIR * dirp) {\n\tstatic struct dirent32 ent;\n\tstruct dirent* big = readdir(dirp);\n\tif (!big) return NULL;\n\n\tent.d_ino = big->d_ino;\n\tmemcpy(ent.d_name,big->d_name,sizeof(ent.d_name));\n\treturn &ent;\n}\n\nstruct dirent32 * readdir32 (DIR * dirp) __asm__(\"readdir\");\n"
  },
  {
    "path": "libc/dirent/mkdir.c",
    "content": "#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/stat.h>\n\nDEFN_SYSCALL2(mkdir, SYS_MKDIR, char *, unsigned int);\n\nint mkdir(const char *pathname, mode_t mode) {\n\t__sets_errno(syscall_mkdir((char *)pathname, mode));\n}\n"
  },
  {
    "path": "libc/dlfcn/dlfcn.c",
    "content": "/*\n * Stub library for providing dlopen, dlclose, dlsym.\n */\n\n#include <stdlib.h>\n\nstatic char * error = \"dlopen functions not available\";\n\nvoid * __attribute__((weak)) dlopen(const char * filename, int flags) {\n\treturn NULL;\n}\n\nint __attribute__((weak)) dlclose(void * handle) {\n\treturn -1;\n}\n\nvoid * dlsym(void * handle, const char * symbol) {\n\treturn NULL;\n}\n\nchar * dlerror(void) {\n\treturn error;\n}\n\nint __cxa_atexit(void (*fn)(void *), void * arg, void *d) {\n\treturn 0;\n}\n\nvoid __ld_symbol_table(void) { }\nvoid __ld_objects_table(void) { }\n"
  },
  {
    "path": "libc/errno/errorno.c",
    "content": "#include <errno.h>\n\nint errno = 0;\n"
  },
  {
    "path": "libc/iconv/iconv.c",
    "content": "#include <iconv.h>\n#include <errno.h>\n#include <stdlib.h>\n\nstruct _iconv_state {\n\tchar *tocode;\n\tchar *fromcode;\n};\n\niconv_t iconv_open(const char *tocode, const char *fromcode) {\n\terrno = EINVAL;\n\treturn (iconv_t)-1;\n\n#if 0\n\tstruct _iconv_state * state = malloc(sizeof(struct _iconv_state));\n\n\tstate->tocode = strdup(tocode);\n\tstate->fromcode = strdup(fromcode);\n\n\treturn (iconv_t)state;\n#endif\n}\n\nint iconv_close(iconv_t cd) {\n\tstruct _iconv_state * state = (struct _iconv_state*)cd;\n\n\tfree(state->tocode);\n\tfree(state->fromcode);\n\n\tfree(cd);\n\n\treturn 0;\n}\n\nsize_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) {\n\treturn -1;\n}\n"
  },
  {
    "path": "libc/ioctl/ioctl.c",
    "content": "#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/ioctl.h>\n#include <unistd.h>\n\nDEFN_SYSCALL3(ioctl, SYS_IOCTL, int, unsigned long, void *);\n\nint ioctl(int fd, unsigned long request, void * argp) {\n\t__sets_errno(syscall_ioctl(fd, request, argp));\n}\n\n/* termios */\nspeed_t cfgetispeed(const struct termios * tio) {\n\treturn 0;\n}\nspeed_t cfgetospeed(const struct termios * tio) {\n\treturn tio->c_cflag & CBAUD;\n}\n\nint cfsetispeed(struct termios * tio, speed_t speed) {\n\t/* hahahaha, yeah right */\n\treturn 0;\n}\n\nint cfsetospeed(struct termios * tio, speed_t speed) {\n\ttio->c_cflag = (tio->c_cflag & ~CBAUD) | (speed & CBAUD);\n\treturn 0;\n}\n\nint tcdrain(int i) {\n\t//DEBUG_STUB(\"tcdrain(%d)\\n\", i);\n\treturn 0;\n}\n\nint tcflow(int fd, int arg) {\n\treturn ioctl(fd, TCXONC, (void*)(uintptr_t)arg);\n}\n\nint tcflush(int fd, int arg) {\n\treturn ioctl(fd, TCFLSH, (void*)(uintptr_t)arg);\n}\n\npid_t tcgetsid(int fd) {\n\t//DEBUG_STUB(\"tcgetsid(%d)\\n\", fd);\n\treturn getpid();\n}\n\nint tcsendbreak(int fd, int arg) {\n\treturn ioctl(fd, TCSBRK, (void*)(uintptr_t)arg);\n}\n\nint tcgetattr(int fd, struct termios * tio) {\n\treturn ioctl(fd, TCGETS, tio);\n}\n\nint tcsetattr(int fd, int actions, struct termios * tio) {\n\tswitch (actions) {\n\t\tcase TCSANOW:\n\t\t\treturn ioctl(fd, TCSETS, tio);\n\t\tcase TCSADRAIN:\n\t\t\treturn ioctl(fd, TCSETSW, tio);\n\t\tcase TCSAFLUSH:\n\t\t\treturn ioctl(fd, TCSETSF, tio);\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n}\n\nint tcsetpgrp(int fd, pid_t pgrp) {\n\treturn ioctl(fd, TIOCSPGRP, &pgrp);\n}\n\npid_t tcgetpgrp(int fd) {\n\tpid_t pgrp;\n\tioctl(fd, TIOCGPGRP, &pgrp);\n\treturn pgrp;\n}\n\n"
  },
  {
    "path": "libc/libgen/basename.c",
    "content": "#include <libgen.h>\n#include <stdlib.h>\n#include <string.h>\n\nchar * basename(char * path) {\n\tchar * s = path;\n\tchar * c = NULL;\n\tdo {\n\t\twhile (*s == '/') {\n\t\t\t*s = '\\0'; s++;\n\t\t\tif (!*s) goto _done;\n\t\t}\n\t\tc = s;\n\t\ts = strchr(c,'/');\n\t} while (s);\n\n_done:\n\n\tif (!c) {\n\t\treturn \"/\";\n\t}\n\n\treturn c;\n}\n"
  },
  {
    "path": "libc/libgen/dirname.c",
    "content": "#include <libgen.h>\n#include <stdlib.h>\n#include <string.h>\n\nchar * dirname(char * path) {\n\tint has_slash = 0;\n\tchar * c = path;\n\twhile (*c) {\n\t\tif (*c == '/') {\n\t\t\thas_slash = 1;\n\t\t}\n\t\tc++;\n\t}\n\tif (!has_slash) {\n\t\treturn \".\";\n\t}\n\n\tc--;\n\twhile (*c == '/') {\n\t\t*c = '\\0';\n\t\tif (c == path) break;\n\t\tc--;\n\t}\n\n\tif (c == path) {\n\t\treturn \"/\";\n\t}\n\n\t/* All trailing slashes are cleared out */\n\twhile (*c != '/') {\n\t\t*c = '\\0';\n\t\tif (c == path) break;\n\t\tc--;\n\t}\n\n\tif (c == path) {\n\t\tif (*c == '/') return \"/\";\n\t\treturn \".\";\n\t}\n\n\twhile (*c == '/') {\n\t\tif (c == path) return \"/\";\n\t\t*c = '\\0';\n\t\tc--;\n\t}\n\n\treturn path;\n}\n"
  },
  {
    "path": "libc/libintl/libintl.c",
    "content": "/* Stub. */\n#include <libintl.h>\n\nchar * gettext (const char * msgid) {\n\treturn (char*)msgid;\n}\n\nchar * dgettext (const char * domainname, const char * msgid) {\n\treturn (char*)msgid;\n}\n\nchar * dcgettext (const char * domainname, const char * msgid, int category) {\n\treturn (char*)msgid;\n}\n\nchar * ngettext (const char * msgid, const char * msgid_plural, unsigned long int n) {\n\tif (n != 1) return (char*)msgid_plural;\n\treturn (char*)msgid;\n}\n\nchar * dngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n) {\n\tif (n != 1) return (char*)msgid_plural;\n\treturn (char*)msgid;\n}\n\nchar * dcngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n, int category) {\n\tif (n != 1) return (char*)msgid_plural;\n\treturn (char*)msgid;\n}\n"
  },
  {
    "path": "libc/locale/localeconv.c",
    "content": "#include <locale.h>\n\nstatic struct lconv _en_US = {\n    .decimal_point = \".\",\n    .thousands_sep = \",\",\n    .grouping = \"\\x03\\x03\",\n    .int_curr_symbol = \"USD \",\n    .currency_symbol = \"$\",\n    .mon_decimal_point = \".\",\n    .mon_thousands_sep = \",\",\n    .mon_grouping = \"\\x03\\x03\",\n    .positive_sign = \"+\",\n    .negative_sign = \"-\",\n    .int_frac_digits = 2,\n    .frac_digits = 2,\n    .p_cs_precedes = 1,\n    .p_sep_by_space = 0,\n    .n_cs_precedes = 1,\n    .n_sep_by_space = 0,\n    .p_sign_posn = 1,\n    .n_sign_posn = 1,\n};\n\nstruct lconv * localeconv(void) {\n    return &_en_US;\n}\n"
  },
  {
    "path": "libc/locale/setlocale.c",
    "content": "#include <stdlib.h>\n#include <locale.h>\n\nchar * setlocale(int category, const char *locale) {\n    return \"en_US\";\n}\n\n"
  },
  {
    "path": "libc/main.c",
    "content": "#include <stdint.h>\n#include <stddef.h>\n#include <stdlib.h>\n\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/sysfunc.h>\n\nDEFN_SYSCALL1(exit,  SYS_EXT, int);\nDEFN_SYSCALL2(sleepabs,  SYS_SLEEPABS, unsigned long, unsigned long);\n\nextern void _init();\nextern void _fini();\n\nchar ** environ = NULL;\nint _environ_size = 0;\nchar * _argv_0 = NULL;\nint __libc_debug = 0;\n\nchar ** __argv = NULL;\nextern char ** __get_argv(void) {\n\treturn __argv;\n}\n\nextern void __stdio_init_buffers(void);\nextern void __stdio_cleanup(void);\n\nvoid _exit(int val){\n\t_fini();\n\t__stdio_cleanup();\n\tsyscall_exit(val);\n\t__builtin_unreachable();\n}\n\nextern void __make_tls(void);\n\nint __libc_is_multicore = 0;\nstatic int __libc_init_called = 0;\n\n__attribute__((constructor))\nstatic void _libc_init(void) {\n\t__libc_init_called = 1;\n\t__make_tls();\n\t__stdio_init_buffers();\n\t__libc_is_multicore = sysfunc(TOARU_SYS_FUNC_NPROC, NULL) > 1;\n\n\tunsigned int x = 0;\n\tunsigned int nulls = 0;\n\tfor (x = 0; 1; ++x) {\n\t\tif (!__get_argv()[x]) {\n\t\t\t++nulls;\n\t\t\tif (nulls == 2) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (nulls == 1) {\n\t\t\tenviron = &__get_argv()[x];\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!environ) {\n\t\tenviron = malloc(sizeof(char *) * 4);\n\t\tenviron[0] = NULL;\n\t\tenviron[1] = NULL;\n\t\tenviron[2] = NULL;\n\t\tenviron[3] = NULL;\n\t\t_environ_size = 4;\n\t} else {\n\t\t/* Find actual size */\n\t\tint size = 0;\n\n\t\tchar ** tmp = environ;\n\t\twhile (*tmp) {\n\t\t\tsize++;\n\t\t\ttmp++;\n\t\t}\n\n\t\tif (size < 4) {\n\t\t\t_environ_size = 4;\n\t\t} else {\n\t\t\t/* Multiply by two */\n\t\t\t_environ_size = size * 2;\n\t\t}\n\n\t\tchar ** new_environ = malloc(sizeof(char*) * _environ_size);\n\t\tint i = 0;\n\t\twhile (i < _environ_size && environ[i]) {\n\t\t\tnew_environ[i] = environ[i];\n\t\t\ti++;\n\t\t}\n\n\t\twhile (i < _environ_size) {\n\t\t\tnew_environ[i] = NULL;\n\t\t\ti++;\n\t\t}\n\n\t\tenviron = new_environ;\n\t}\n\tif (getenv(\"__LIBC_DEBUG\")) __libc_debug = 1;\n\t_argv_0 = __get_argv()[0];\n}\n\nvoid pre_main(int argc, char * argv[], char ** envp, int (*main)(int,char**)) {\n\tif (!__get_argv()) {\n\t\t/* Statically loaded, must set __argv so __get_argv() works */\n\t\t__argv = argv;\n\t\t/* Run our initializers, because I'm pretty sure the kernel didn't... */\n\t\tif (!__libc_init_called) {\n\t\t\textern uintptr_t __init_array_start __attribute__((weak));\n\t\t\textern uintptr_t __init_array_end __attribute__((weak));\n\t\t\tfor (uintptr_t * constructor = &__init_array_start; constructor < &__init_array_end; ++constructor) {\n\t\t\t\tvoid (*constr)(void) = (void*)*constructor;\n\t\t\t\tconstr();\n\t\t\t}\n\t\t}\n\t}\n\t_init();\n\texit(main(argc, argv));\n}\n\n"
  },
  {
    "path": "libc/math/bad.c",
    "content": "/* STUB MATH LIBRARY */\n#include <stdio.h>\n#include <stdlib.h>\n#include <math.h>\n\nextern char * _argv_0;\n#define BAD do { if (getenv(\"LIBM_DEBUG\")) { fprintf(stderr, \"%s called bad math function %s\\n\", _argv_0, __func__); } } while (0)\n\ndouble acos(double x) {\n\tBAD;\n\treturn 0.0;\n}\n\ndouble asin(double x) {\n\tBAD;\n\treturn 0.0;\n}\n\ndouble ldexp(double a, int exp) {\n\tdouble out = a;\n\twhile (exp) {\n\t\tout *= 2.0;\n\t\texp--;\n\t}\n\treturn out;\n}\n\ndouble log(double x) {\n\tBAD;\n\treturn 0.0;\n}\n\ndouble log10(double x) {\n\tBAD;\n\treturn 0.0;\n}\n\ndouble log2(double x) {\n\tBAD;\n\treturn 0.0;\n}\n\ndouble log1p(double x) {\n\tBAD;\n\treturn log(x+1.0);\n}\n\ndouble expm1(double x) {\n\tBAD;\n\treturn exp(x) - 1.0;\n}\n\ndouble trunc(double x) {\n\tBAD;\n\treturn (double)(long)x;\n}\n\ndouble acosh(double x) { BAD; return 0.0; }\ndouble asinh(double x) { BAD; return 0.0; }\ndouble atanh(double x) { BAD; return 0.0; }\ndouble erf(double x)   { BAD; return 0.0; }\ndouble erfc(double x)  { BAD; return 0.0; }\ndouble gamma(double x) { BAD; return 0.0; }\ndouble tgamma(double x){ BAD; return 0.0; }\ndouble lgamma(double x){ BAD; return 0.0; }\ndouble remainder(double x, double y) { BAD; return 0.0; }\n\ndouble copysign(double x, double y) {\n\tif (y < 0) {\n\t\tif (x < 0) return x;\n\t\treturn -x;\n\t} else {\n\t\tif (x < 0) return -x;\n\t\treturn x;\n\t}\n}\n\n"
  },
  {
    "path": "libc/math/math.c",
    "content": "#include <math.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdio.h>\n\n#if 0\nextern char * _argv_0;\n#define MATH do { if (getenv(\"LIBM_DEBUG\")) { fprintf(stderr, \"%s called math function %s\\n\", _argv_0, __func__); } } while (0)\n#else\n#define MATH (void)0\n#endif\n\ndouble exp(double x) {\n\treturn pow(M_E, x);\n}\n\nint abs(int j) {\n\treturn (j < 0 ? -j : j);\n}\n\ndouble fabs(double x) {\n\tMATH;\n\treturn __builtin_fabs(x);\n}\n\nfloat fabsf(float x) {\n\treturn fabs(x);\n}\n\nfloat sqrtf(float x) {\n\treturn sqrt(x);\n}\n\nstatic double bad_sine_table[] = {\n\t0,\n\t\t0.01745240644,\n\t\t0.03489949671,\n\t\t0.05233595625,\n\t\t0.06975647375,\n\t\t0.08715574276,\n\t\t0.1045284633,\n\t\t0.1218693434,\n\t\t0.139173101,\n\t\t0.1564344651,\n\t\t0.1736481777,\n\t\t0.1908089954,\n\t\t0.2079116908,\n\t\t0.2249510544,\n\t\t0.2419218956,\n\t\t0.2588190451,\n\t\t0.2756373559,\n\t\t0.2923717048,\n\t\t0.3090169944,\n\t\t0.3255681545,\n\t\t0.3420201434,\n\t\t0.3583679496,\n\t\t0.3746065935,\n\t\t0.3907311285,\n\t\t0.4067366431,\n\t\t0.4226182618,\n\t\t0.4383711468,\n\t\t0.4539904998,\n\t\t0.4694715628,\n\t\t0.4848096203,\n\t\t0.5000000001,\n\t\t0.515038075,\n\t\t0.5299192643,\n\t\t0.5446390351,\n\t\t0.5591929035,\n\t\t0.5735764364,\n\t\t0.5877852524,\n\t\t0.6018150232,\n\t\t0.6156614754,\n\t\t0.6293203911,\n\t\t0.6427876098,\n\t\t0.6560590291,\n\t\t0.6691306064,\n\t\t0.6819983601,\n\t\t0.6946583705,\n\t\t0.7071067813,\n\t\t0.7193398004,\n\t\t0.7313537017,\n\t\t0.7431448256,\n\t\t0.7547095803,\n\t\t0.7660444432,\n\t\t0.7771459615,\n\t\t0.7880107537,\n\t\t0.7986355101,\n\t\t0.8090169944,\n\t\t0.8191520444,\n\t\t0.8290375726,\n\t\t0.838670568,\n\t\t0.8480480962,\n\t\t0.8571673008,\n\t\t0.8660254039,\n\t\t0.8746197072,\n\t\t0.8829475929,\n\t\t0.8910065243,\n\t\t0.8987940464,\n\t\t0.9063077871,\n\t\t0.9135454577,\n\t\t0.9205048535,\n\t\t0.9271838546,\n\t\t0.9335804266,\n\t\t0.9396926208,\n\t\t0.9455185757,\n\t\t0.9510565163,\n\t\t0.956304756,\n\t\t0.961261696,\n\t\t0.9659258263,\n\t\t0.9702957263,\n\t\t0.9743700648,\n\t\t0.9781476008,\n\t\t0.9816271835,\n\t\t0.984807753,\n\t\t0.9876883406,\n\t\t0.9902680688,\n\t\t0.9925461517,\n\t\t0.9945218954,\n\t\t0.9961946981,\n\t\t0.9975640503,\n\t\t0.9986295348,\n\t\t0.999390827,\n\t\t0.9998476952,\n\t\t1,\n\t\t0.9998476952,\n\t\t0.999390827,\n\t\t0.9986295347,\n\t\t0.9975640502,\n\t\t0.9961946981,\n\t\t0.9945218953,\n\t\t0.9925461516,\n\t\t0.9902680687,\n\t\t0.9876883406,\n\t\t0.984807753,\n\t\t0.9816271834,\n\t\t0.9781476007,\n\t\t0.9743700647,\n\t\t0.9702957262,\n\t\t0.9659258262,\n\t\t0.9612616959,\n\t\t0.9563047559,\n\t\t0.9510565162,\n\t\t0.9455185755,\n\t\t0.9396926207,\n\t\t0.9335804264,\n\t\t0.9271838545,\n\t\t0.9205048534,\n\t\t0.9135454575,\n\t\t0.9063077869,\n\t\t0.8987940462,\n\t\t0.8910065241,\n\t\t0.8829475927,\n\t\t0.874619707,\n\t\t0.8660254036,\n\t\t0.8571673006,\n\t\t0.848048096,\n\t\t0.8386705678,\n\t\t0.8290375724,\n\t\t0.8191520441,\n\t\t0.8090169942,\n\t\t0.7986355099,\n\t\t0.7880107534,\n\t\t0.7771459613,\n\t\t0.7660444429,\n\t\t0.75470958,\n\t\t0.7431448253,\n\t\t0.7313537014,\n\t\t0.7193398001,\n\t\t0.707106781,\n\t\t0.6946583702,\n\t\t0.6819983598,\n\t\t0.6691306061,\n\t\t0.6560590288,\n\t\t0.6427876094,\n\t\t0.6293203908,\n\t\t0.6156614751,\n\t\t0.6018150229,\n\t\t0.587785252,\n\t\t0.5735764361,\n\t\t0.5591929032,\n\t\t0.5446390347,\n\t\t0.5299192639,\n\t\t0.5150380746,\n\t\t0.4999999997,\n\t\t0.4848096199,\n\t\t0.4694715625,\n\t\t0.4539904994,\n\t\t0.4383711465,\n\t\t0.4226182614,\n\t\t0.4067366428,\n\t\t0.3907311282,\n\t\t0.3746065931,\n\t\t0.3583679492,\n\t\t0.342020143,\n\t\t0.3255681541,\n\t\t0.309016994,\n\t\t0.2923717044,\n\t\t0.2756373555,\n\t\t0.2588190447,\n\t\t0.2419218952,\n\t\t0.224951054,\n\t\t0.2079116904,\n\t\t0.190808995,\n\t\t0.1736481773,\n\t\t0.1564344647,\n\t\t0.1391731006,\n\t\t0.121869343,\n\t\t0.1045284629,\n\t\t0.08715574235,\n\t\t0.06975647334,\n\t\t0.05233595584,\n\t\t0.0348994963,\n\t\t0.01745240603,\n\t\t-0.000000000410206857,\n\t\t-0.01745240685,\n\t\t-0.03489949712,\n\t\t-0.05233595666,\n\t\t-0.06975647416,\n\t\t-0.08715574317,\n\t\t-0.1045284637,\n\t\t-0.1218693438,\n\t\t-0.1391731014,\n\t\t-0.1564344655,\n\t\t-0.1736481781,\n\t\t-0.1908089958,\n\t\t-0.2079116912,\n\t\t-0.2249510548,\n\t\t-0.241921896,\n\t\t-0.2588190455,\n\t\t-0.2756373562,\n\t\t-0.2923717052,\n\t\t-0.3090169948,\n\t\t-0.3255681549,\n\t\t-0.3420201438,\n\t\t-0.35836795,\n\t\t-0.3746065938,\n\t\t-0.3907311289,\n\t\t-0.4067366435,\n\t\t-0.4226182622,\n\t\t-0.4383711472,\n\t\t-0.4539905002,\n\t\t-0.4694715632,\n\t\t-0.4848096207,\n\t\t-0.5000000004,\n\t\t-0.5150380753,\n\t\t-0.5299192646,\n\t\t-0.5446390354,\n\t\t-0.5591929039,\n\t\t-0.5735764368,\n\t\t-0.5877852527,\n\t\t-0.6018150235,\n\t\t-0.6156614757,\n\t\t-0.6293203914,\n\t\t-0.6427876101,\n\t\t-0.6560590294,\n\t\t-0.6691306067,\n\t\t-0.6819983604,\n\t\t-0.6946583708,\n\t\t-0.7071067815,\n\t\t-0.7193398007,\n\t\t-0.731353702,\n\t\t-0.7431448258,\n\t\t-0.7547095806,\n\t\t-0.7660444435,\n\t\t-0.7771459618,\n\t\t-0.7880107539,\n\t\t-0.7986355104,\n\t\t-0.8090169947,\n\t\t-0.8191520446,\n\t\t-0.8290375729,\n\t\t-0.8386705682,\n\t\t-0.8480480964,\n\t\t-0.857167301,\n\t\t-0.8660254041,\n\t\t-0.8746197074,\n\t\t-0.8829475931,\n\t\t-0.8910065244,\n\t\t-0.8987940465,\n\t\t-0.9063077873,\n\t\t-0.9135454579,\n\t\t-0.9205048537,\n\t\t-0.9271838548,\n\t\t-0.9335804267,\n\t\t-0.939692621,\n\t\t-0.9455185758,\n\t\t-0.9510565165,\n\t\t-0.9563047561,\n\t\t-0.9612616961,\n\t\t-0.9659258264,\n\t\t-0.9702957264,\n\t\t-0.9743700649,\n\t\t-0.9781476009,\n\t\t-0.9816271836,\n\t\t-0.9848077531,\n\t\t-0.9876883407,\n\t\t-0.9902680688,\n\t\t-0.9925461517,\n\t\t-0.9945218954,\n\t\t-0.9961946981,\n\t\t-0.9975640503,\n\t\t-0.9986295348,\n\t\t-0.999390827,\n\t\t-0.9998476952,\n\t\t-1,\n\t\t-0.9998476951,\n\t\t-0.999390827,\n\t\t-0.9986295347,\n\t\t-0.9975640502,\n\t\t-0.996194698,\n\t\t-0.9945218953,\n\t\t-0.9925461516,\n\t\t-0.9902680687,\n\t\t-0.9876883405,\n\t\t-0.9848077529,\n\t\t-0.9816271833,\n\t\t-0.9781476006,\n\t\t-0.9743700646,\n\t\t-0.9702957261,\n\t\t-0.9659258261,\n\t\t-0.9612616958,\n\t\t-0.9563047558,\n\t\t-0.9510565161,\n\t\t-0.9455185754,\n\t\t-0.9396926206,\n\t\t-0.9335804263,\n\t\t-0.9271838543,\n\t\t-0.9205048532,\n\t\t-0.9135454574,\n\t\t-0.9063077868,\n\t\t-0.898794046,\n\t\t-0.8910065239,\n\t\t-0.8829475925,\n\t\t-0.8746197068,\n\t\t-0.8660254034,\n\t\t-0.8571673003,\n\t\t-0.8480480958,\n\t\t-0.8386705676,\n\t\t-0.8290375722,\n\t\t-0.8191520439,\n\t\t-0.809016994,\n\t\t-0.7986355096,\n\t\t-0.7880107532,\n\t\t-0.777145961,\n\t\t-0.7660444427,\n\t\t-0.7547095798,\n\t\t-0.743144825,\n\t\t-0.7313537011,\n\t\t-0.7193397998,\n\t\t-0.7071067807,\n\t\t-0.6946583699,\n\t\t-0.6819983595,\n\t\t-0.6691306058,\n\t\t-0.6560590284,\n\t\t-0.6427876091,\n\t\t-0.6293203905,\n\t\t-0.6156614747,\n\t\t-0.6018150226,\n\t\t-0.5877852517,\n\t\t-0.5735764357,\n\t\t-0.5591929029,\n\t\t-0.5446390344,\n\t\t-0.5299192636,\n\t\t-0.5150380743,\n\t\t-0.4999999993,\n\t\t-0.4848096196,\n\t\t-0.4694715621,\n\t\t-0.4539904991,\n\t\t-0.4383711461,\n\t\t-0.422618261,\n\t\t-0.4067366424,\n\t\t-0.3907311278,\n\t\t-0.3746065927,\n\t\t-0.3583679488,\n\t\t-0.3420201426,\n\t\t-0.3255681537,\n\t\t-0.3090169936,\n\t\t-0.292371704,\n\t\t-0.2756373551,\n\t\t-0.2588190443,\n\t\t-0.2419218948,\n\t\t-0.2249510536,\n\t\t-0.20791169,\n\t\t-0.1908089946,\n\t\t-0.1736481769,\n\t\t-0.1564344643,\n\t\t-0.1391731002,\n\t\t-0.1218693426,\n\t\t-0.1045284625,\n\t\t-0.08715574194,\n\t\t-0.06975647293,\n\t\t-0.05233595543,\n\t\t-0.03489949589,\n\t\t-0.01745240562,\n\t\t0.0\n};\n\ndouble sin(double x) {\n\tMATH;\n\tif (x < 0.0) {\n\t\tx += M_PI * 2.0 * 100.0;\n\t}\n\tint i = x * 360.0 / (M_PI * 2.0);\n\tdouble z = x * 360.0 / (M_PI * 2.0);\n\tz -= i;\n\n\ti = i % 360;\n\n\t//fprintf(stderr, \"z = %f\\n\", z);\n\n\tdouble a = bad_sine_table[i];\n\tdouble b = bad_sine_table[(i+1)%360];\n\n\treturn a * (1.0-z) + b * (z);\n}\n\ndouble cos(double x) {\n\treturn sin(x + M_PI / 2.0);\n}\n\ndouble atan(double x) {\n\treturn atan2(x,1.0);\n}\n\ndouble hypot(double x, double y) {\n\treturn sqrt(x * x + y * y);\n}\n\ndouble modf(double x, double *iptr) {\n\tMATH;\n\tint i = (int)x;\n\t*iptr = (double)i;\n\treturn x - i;\n}\n\ndouble frexp(double x, int *exp) {\n\tMATH;\n\tstruct {\n\t\tuint32_t lsw;\n\t\tuint32_t msw;\n\t} extract;\n\n\tmemcpy(&extract, &x, sizeof(double));\n\n\t*exp = ((extract.msw & 0x7ff00000) >> 20) - 0x3FE;\n\n\tstruct {\n\t\tuint32_t lsw;\n\t\tuint32_t msw;\n\t} out_double;\n\n\tout_double.msw = (extract.msw & 0x800fffff) | 0x3FE00000;\n\tout_double.lsw = extract.lsw;\n\n\tdouble out;\n\tmemcpy(&out, &out_double, sizeof(double));\n\treturn out;\n}\n\ndouble cosh(double x) {\n\treturn (exp(x) + exp(-x)) / 2.0;\n}\n\ndouble sinh(double x) {\n\treturn (exp(x) - exp(-x)) / 2.0;\n}\n\ndouble tanh(double x) {\n\treturn (exp(2.0*x) - 1.0) / (exp(2.0*x) + 1.0);\n}\n\nint fpclassify(double x) {\n\tunion {\n\t\tdouble asFloat;\n\t\tuint64_t asInt;\n\t} bits = {x};\n\tuint64_t exponent = (bits.asInt >> 52) & 0x7FF;\n\tuint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL);\n\n\tif (exponent == 0x7FF) {\n\t\treturn mantissa ? FP_NAN : FP_INFINITE;\n\t} else if (exponent == 0) {\n\t\treturn mantissa ? FP_SUBNORMAL : FP_ZERO;\n\t}\n\treturn FP_NORMAL;\n}\n\ndouble ceil(double x) {\n\tunion {\n\t\tdouble asFloat;\n\t\tuint64_t asInt;\n\t} bits = {x};\n\tint64_t exponent = ((bits.asInt >> 52) & 0x7FF) - 0x3ff;\n\tuint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL);\n\tif (exponent >= 52 || x == 0) return x;\n\tif (exponent <= -1) return (bits.asInt >> 63) ? -0.0 : 1.0;\n\tuint64_t mask = (0xfffffFFFFffffULL >> exponent);\n\tif (!(mask & mantissa)) return x;\n\tif (!(bits.asInt >> 63)) {\n\t\tbits.asInt += mask;\n\t}\n\tbits.asInt &= ~mask;\n\treturn bits.asFloat;\n}\n\ndouble round(double x) {\n\tunion {\n\t\tdouble asFloat;\n\t\tuint64_t asInt;\n\t} bits = {x};\n\tint64_t exponent = ((bits.asInt >> 52) & 0x7FF) - 0x3ff;\n\tuint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL);\n\tif (exponent >= 52 || x == 0) return x;\n\tif (exponent < -1) return (bits.asInt >> 63) ? -0.0 : 0.0;\n\tif (exponent == -1) return x >= 0.5 ? 1.0 : (x <= -0.5 ? -1.0 : ((bits.asInt >> 63) ? -0.0 : 0.0));\n\n\tuint64_t mask  = 0xfffffFFFFffffULL >> exponent;\n\tuint64_t a = mantissa & mask;\n\tuint64_t b = mantissa & (mask >> 1);\n\n\tif (a & ~b) {\n\t\tbits.asInt += mask;\n\t\tbits.asInt &= ~mask;\n\t\treturn bits.asFloat;\n\t}\n\n\tbits.asInt &= ~mask;\n\treturn bits.asFloat;\n}\n\nfloat ceilf(float x) {\n\treturn ceil(x);\n}\n\nfloat roundf(float x) {\n\treturn round(x);\n}\n\nlong lroundf(float x) {\n\treturn round(x);\n}\n\n"
  },
  {
    "path": "libc/poll/poll.c",
    "content": "#include <poll.h>\n#include <stdio.h>\n#include <errno.h>\n#include <sys/fswait.h>\n\nextern char * _argv_0;\n\nstatic char * poll_print_flags(int flags) {\n\tstatic char buf[100];\n\tchar * b = buf;\n\tint saw_something = 0;\n#define OPT(n) do { if (flags & n) { b += sprintf(b, \"%s%s\", saw_something ? \"|\" : \"\", #n); saw_something = 1; } } while (0)\n\tOPT(POLLIN);\n\tOPT(POLLOUT);\n\tOPT(POLLRDHUP);\n\tOPT(POLLERR);\n\tOPT(POLLHUP);\n\tOPT(POLLNVAL);\n\tOPT(POLLPRI);\n\treturn buf;\n}\n\nextern int __libc_debug;\n\nint poll(struct pollfd *fds, nfds_t nfds, int timeout) {\n\tint count_pollin = 0;\n\n\tfor (nfds_t i = 0; i < nfds; ++i) {\n\t\tif (fds[i].events & POLLIN) {\n\t\t\tcount_pollin++;\n\t\t}\n\t\tfds[i].revents = 0;\n\t}\n\n\tfor (nfds_t i = 0; i < nfds; ++i) {\n\t\tif (fds[i].events & (~POLLIN)) {\n\t\t\tif (fds[i].events == (POLLIN | POLLPRI)) continue;\n\t\t\tif (__libc_debug) {\n\t\t\t\tfprintf(stderr, \"%s: poll: unsupported bit set in fds: %s\\n\", _argv_0, poll_print_flags(fds[i].events));\n\t\t\t}\n\t\t\tif ((fds[i].events & POLLOUT) && nfds == 1) {\n\t\t\t\tfds[i].revents |= POLLOUT;\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\t//if (fds[i].events & POLLIN) continue;\n\t\t\t//fprintf(stderr, \"%s: poll: the above error is fatal\\n\", _argv_0);\n\t\t\t//return -EINVAL;\n\t\t}\n\t}\n\n\tint fswait_fds[count_pollin];\n\tint fswait_backref[count_pollin];\n\tint fswait_results[count_pollin];\n\tint j = 0;\n\tfor (nfds_t i = 0; i < nfds; ++i) {\n\t\tif (fds[i].events & POLLIN) {\n\t\t\tfswait_fds[j] = fds[i].fd;\n\t\t\tfswait_backref[j] = i;\n\t\t\tj++;\n\t\t}\n\t}\n\n\tint ret = fswait3(count_pollin, fswait_fds, timeout, fswait_results);\n\n\tif (ret >= 0 && ret < count_pollin) {\n\t\tint count = 0;\n\t\tfor (int i = 0; i < count_pollin; ++i) {\n\t\t\tif (fswait_results[i]) {\n\t\t\t\tfds[fswait_backref[i]].revents = POLLIN;\n\t\t\t\tcount++;\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t} else if (ret == count_pollin) {\n\t\treturn 0;\n\t} else {\n\t\treturn ret; /* Error */\n\t}\n}\n"
  },
  {
    "path": "libc/pthread/pthread.c",
    "content": "/* This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2012-2018 K. Lange\n */\n#include <stdlib.h>\n#include <stdint.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <signal.h>\n#include <pthread.h>\n#include <errno.h>\n#include <string.h>\n#include <stdio.h>\n\n#include <sys/wait.h>\n#include <sys/sysfunc.h>\n\nDEFN_SYSCALL3(clone, SYS_CLONE, uintptr_t, uintptr_t, void *);\nDEFN_SYSCALL0(gettid, SYS_GETTID);\n\nstruct __pthread {\n\tpid_t tid;\n\tvoid * (*entry)(void*);\n\tvoid * arg;\n};\n\nextern int __libc_is_multicore;\nstatic inline void _yield(void) {\n\tif (!__libc_is_multicore) syscall_yield();\n}\n\n#define PTHREAD_STACK_SIZE 0x100000\n\nint clone(uintptr_t a,uintptr_t b,void* c) {\n\t__sets_errno(syscall_clone(a,b,c));\n}\nint gettid() {\n\treturn syscall_gettid(); /* never fails */\n}\n\nvoid * __tls_get_addr(void* input) {\n#ifdef __x86_64__\n\tstruct tls_index {\n\t\tuintptr_t module;\n\t\tuintptr_t offset;\n\t};\n\tstruct tls_index * index = input;\n\t/* We only support initial-exec stuff, so this must be %fs:offset */\n\tuintptr_t threadbase;\n\tasm (\"mov %%fs:0, %0\" :\"=r\"(threadbase));\n\treturn (void*)(threadbase + index->offset);\n#else\n\treturn NULL;\n#endif\n}\n\nvoid __make_tls(void) {\n\tchar * tlsSpace = valloc(4096);\n\tmemset(tlsSpace, 0x0, 4096);\n\t/* self-pointer start? */\n\tchar ** tlsSelf = (char **)(tlsSpace);\n\t*tlsSelf = (char*)tlsSelf;\n\tsysfunc(TOARU_SYS_FUNC_SETGSBASE, (char*[]){(char*)tlsSelf});\n}\n\nvoid pthread_exit(void * value) {\n\tsyscall_exit(0);\n\t__builtin_unreachable();\n}\n\nvoid * __thread_start(void * pthreadbase) {\n\tstruct __pthread * this = pthreadbase;\n\tthis->tid = gettid();\n\tchar ** tlsbase = (char**)((char*)this + 4096);\n\t*tlsbase = (char*)tlsbase;\n\tsysfunc(TOARU_SYS_FUNC_SETGSBASE, (char*[]){(char*)tlsbase});\n\tpthread_exit(this->entry(this->arg));\n\treturn NULL;\n}\n\nint pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg) {\n\tchar * stack = valloc(PTHREAD_STACK_SIZE + 8192);\n\tmemset(stack, 0, PTHREAD_STACK_SIZE + 8192);\n\tstruct __pthread * this = (void*)(stack + PTHREAD_STACK_SIZE);\n\t*thread = this;\n\tthis->entry = start_routine;\n\tthis->arg = arg;\n\tclone((uintptr_t)this, (uintptr_t)__thread_start, this);\n\treturn 0;\n}\n\nint pthread_kill(pthread_t thread, int sig) {\n\t__sets_errno(kill(thread->tid, sig));\n}\n\nvoid pthread_cleanup_push(void (*routine)(void *), void *arg) {\n\t/* do nothing */\n}\n\nvoid pthread_cleanup_pop(int execute) {\n\t/* do nothing */\n}\n\nint pthread_mutex_lock(pthread_mutex_t *mutex) {\n\twhile (__sync_lock_test_and_set(mutex, 0x01)) {\n\t\t_yield();\n\t}\n\treturn 0;\n}\n\nint pthread_mutex_trylock(pthread_mutex_t *mutex) {\n\tif (__sync_lock_test_and_set(mutex, 0x01)) {\n\t\treturn EBUSY;\n\t}\n\treturn 0;\n}\n\nint pthread_mutex_unlock(pthread_mutex_t *mutex) {\n\t__sync_lock_release(mutex);\n\treturn 0;\n}\n\nint pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) {\n\t*mutex = 0;\n\treturn 0;\n}\n\nint pthread_mutex_destroy(pthread_mutex_t *mutex) {\n\treturn 0;\n}\n\nint pthread_attr_init(pthread_attr_t *attr) {\n\t*attr = 0;\n\treturn 0;\n}\n\nint pthread_attr_destroy(pthread_attr_t *attr) {\n\treturn 0;\n}\n\nint pthread_join(pthread_t thread, void **retval) {\n\tint status;\n\tint result = waitpid(thread->tid, &status, 0);\n\tif (retval) {\n\t\t*retval = (void*)(uintptr_t)status;\n\t}\n\treturn result;\n}\n"
  },
  {
    "path": "libc/pthread/pthread_rwlock.c",
    "content": "#include <stdlib.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <signal.h>\n#include <pthread.h>\n#include <errno.h>\n\n#include <sys/wait.h>\n\nextern int __libc_is_multicore;\nstatic inline void _yield(void) {\n\tif (!__libc_is_multicore) syscall_yield();\n}\n\n#define ACQUIRE_LOCK() do { while (__sync_lock_test_and_set(&lock->atomic_lock, 0x01)) { _yield(); } } while (0)\n#define RELEASE_LOCK() do { __sync_lock_release(&lock->atomic_lock); } while (0)\n\nint pthread_rwlock_init(pthread_rwlock_t * lock, void * args) {\n\tlock->readers = 0;\n\tlock->atomic_lock = 0;\n\tif (args != NULL) {\n\t\tfprintf(stderr, \"pthread: pthread_rwlock_init arg unsupported\\n\");\n\t\treturn 1;\n\t}\n\treturn 0;\n}\n\nint pthread_rwlock_wrlock(pthread_rwlock_t * lock) {\n\tACQUIRE_LOCK();\n\twhile (1) {\n\t\tif (lock->readers == 0) {\n\t\t\tlock->readers = -1;\n\t\t\tlock->writerPid = syscall_getpid();\n\t\t\tRELEASE_LOCK();\n\t\t\treturn 0;\n\t\t}\n\t\t_yield();\n\t}\n}\n\nint pthread_rwlock_rdlock(pthread_rwlock_t * lock) {\n\tACQUIRE_LOCK();\n\twhile (1) {\n\t\tif (lock->readers >= 0) {\n\t\t\tlock->readers++;\n\t\t\tRELEASE_LOCK();\n\t\t\treturn 0;\n\t\t}\n\t\t_yield();\n\t}\n}\n\nint pthread_rwlock_unlock(pthread_rwlock_t * lock) {\n\tACQUIRE_LOCK();\n\tif (lock->readers > 0) lock->readers--;\n\telse if (lock->readers < 0) lock->readers = 0;\n\telse fprintf(stderr, \"pthread: bad lock state detected\\n\");\n\tRELEASE_LOCK();\n\treturn 0;\n}\n\nint pthread_rwlock_destroy(pthread_rwlock_t * lock) {\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/pty/pty.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/ioctl.h>\n#include <pty.h>\n#include <errno.h>\n\nDEFN_SYSCALL5(openpty, SYS_OPENPTY, int *, int *, char *, void *, void *);\n\nint openpty(int * amaster, int * aslave, char * name, const struct termios *termp, const struct winsize * winp) {\n\t__sets_errno(syscall_openpty(amaster,aslave,name,(struct termios *)termp,(struct winsize *)winp));\n}\n"
  },
  {
    "path": "libc/pwd/pwd.c",
    "content": "/***\n * getpwent, setpwent, endpwent, fgetpwent\n * getpwuid, getpwnam\n *\n * These functions manage entries in the password files.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2013-2018 K. Lange\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <pwd.h>\n\n/*\nstruct passwd {\n\tchar * pw_name;    // username\n\tchar * pw_passwd;  // password (not meaningful)\n\tuid_t  pw_uid;     // user id\n\tgid_t  pw_gid;     // group id\n\tchar * pw_comment; // used for decoration settings in toaruos\n\tchar * pw_gecos;   // full name\n\tchar * pw_dir;     // home directory\n\tchar * pw_shell;   // shell\n}\n*/\n\nstatic FILE * pwdb = NULL;\n\nstatic void open_it(void) {\n\tpwdb = fopen(\"/etc/passwd\", \"r\");\n}\n\n#define LINE_LEN 2048\n\nstatic struct passwd * pw_ent;\nstatic char * pw_blob;\n\nstruct passwd * fgetpwent(FILE * stream) {\n\tif (!stream) {\n\t\treturn NULL;\n\t}\n\tif (!pw_ent) {\n\t\tpw_ent = malloc(sizeof(struct passwd));\n\t\tpw_blob = malloc(LINE_LEN);\n\t}\n\n\tmemset(pw_blob, 0x00, LINE_LEN);\n\tfgets(pw_blob, LINE_LEN, stream);\n\n\n\tif (pw_blob[strlen(pw_blob)-1] == '\\n') {\n\t\tpw_blob[strlen(pw_blob)-1] = '\\0'; /* erase newline */\n\t}\n\n\t/* Tokenize */\n\tchar *p, *tokens[8], *last;\n\tint i = 0;\n\tfor ((p = strtok_r(pw_blob, \":\", &last)); p;\n\t\t\t(p = strtok_r(NULL, \":\", &last)), i++) {\n\t\ttokens[i] = p;\n\t}\n\n\tif (i < 8) return NULL;\n\n\tpw_ent->pw_name    = tokens[0];\n\tpw_ent->pw_passwd  = tokens[1];\n\tpw_ent->pw_uid     = atoi(tokens[2]);\n\tpw_ent->pw_gid     = atoi(tokens[3]);\n\tpw_ent->pw_gecos   = tokens[4];\n\tpw_ent->pw_dir     = tokens[5];\n\tpw_ent->pw_shell   = tokens[6];\n\tpw_ent->pw_comment = tokens[7];\n\n\treturn pw_ent;\n}\n\nstruct passwd * getpwent(void) {\n\tif (!pwdb) {\n\t\topen_it();\n\t}\n\n\tif (!pwdb) {\n\t\treturn NULL;\n\t}\n\n\treturn fgetpwent(pwdb);\n}\n\nvoid setpwent(void) {\n\t/* Reset stream to beginning */\n\tif (pwdb) rewind(pwdb);\n}\n\nvoid endpwent(void) {\n\t/* Close stream */\n\tif (pwdb) {\n\t\tfclose(pwdb);\n\t\tpwdb = NULL;\n\t}\n\tif (pw_ent) {\n\t\tfree(pw_ent);\n\t\tfree(pw_blob);\n\t\tpw_ent = NULL;\n\t\tpw_blob = NULL;\n\t}\n}\n\nstruct passwd * getpwnam(const char * name) {\n\tstruct passwd * p;\n\n\tsetpwent();\n\n\twhile ((p = getpwent())) {\n\t\tif (!strcmp(p->pw_name, name)) {\n\t\t\treturn p;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\nstruct passwd * getpwuid(uid_t uid) {\n\tstruct passwd * p;\n\n\tsetpwent();\n\n\twhile ((p = getpwent())) {\n\t\tif (p->pw_uid == uid) {\n\t\t\treturn p;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n\n"
  },
  {
    "path": "libc/sched/sched_yield.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sched.h>\n#include <errno.h>\n\nDEFN_SYSCALL0(yield, SYS_YIELD);\n\nint sched_yield(void) {\n\t__sets_errno(syscall_yield());\n}\n"
  },
  {
    "path": "libc/signal/kill.c",
    "content": "#include <signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(kill, SYS_KILL, int, int);\n\nint kill(int pid, int sig) {\n\t__sets_errno(syscall_kill(pid, sig));\n}\n\n"
  },
  {
    "path": "libc/signal/raise.c",
    "content": "#include <signal.h>\n#include <unistd.h>\n\nint raise(int sig) {\n\treturn kill(getpid(), sig);\n}\n"
  },
  {
    "path": "libc/signal/sigaction.c",
    "content": "#include <sys/signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL3(sigaction, SYS_SIGACTION, int, struct sigaction*, struct sigaction*);\n\nint sigaction(int signum, struct sigaction *act, struct sigaction *oldact) {\n\t__sets_errno(syscall_sigaction(signum, act, oldact));\n}\n\n"
  },
  {
    "path": "libc/signal/signal.c",
    "content": "#include <signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(signal, SYS_SIGNAL, int, void *);\n\n/* XXX This syscall interface is screwy, doesn't allow for good errno handling */\nsighandler_t signal(int signum, sighandler_t handler) {\n\treturn (sighandler_t)syscall_signal(signum, (void *)handler);\n}\n"
  },
  {
    "path": "libc/signal/sigpending.c",
    "content": "#include <sys/signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(sigpending, SYS_SIGPENDING, sigset_t *);\n\nint sigpending(sigset_t * set) {\n\t__sets_errno(syscall_sigpending(set));\n}\n\n\n"
  },
  {
    "path": "libc/signal/sigprocmask.c",
    "content": "#include <signal.h>\n#include <sys/signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL3(sigprocmask, SYS_SIGPROCMASK, int, const sigset_t * restrict, sigset_t* restrict);\n\nint sigprocmask(int how, const sigset_t * restrict set, sigset_t * restrict oset) {\n\t__sets_errno(syscall_sigprocmask(how,set,oset));\n}\n"
  },
  {
    "path": "libc/signal/sigset.c",
    "content": "#include <sys/signal.h>\n#include <sys/signal_defs.h>\n#include <string.h>\n#include <errno.h>\n\nint sigemptyset(sigset_t * set) {\n\t*set = 0;\n\treturn 0;\n}\n\nint sigfillset(sigset_t * set) {\n\tmemset(set, 0xFF, sizeof(sigset_t));\n\treturn 0;\n}\n\nint sigaddset(sigset_t * set, int signum) {\n\tif (signum > NUMSIGNALS) return -EINVAL;\n\t*set |= (1UL << signum);\n\treturn 0;\n}\n\nint sigdelset(sigset_t * set, int signum) {\n\tif (signum > NUMSIGNALS) return -EINVAL;\n\t*set &= ~(1UL << signum);\n\treturn 0;\n}\n\nint sigismember(sigset_t * set, int signum) {\n\tif (signum > NUMSIGNALS) return -EINVAL;\n\treturn !!(*set & (1UL << signum));\n}\n"
  },
  {
    "path": "libc/signal/sigsuspend.c",
    "content": "#include <signal.h>\n#include <sys/signal.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(sigsuspend, SYS_SIGSUSPEND,const sigset_t *);\n\nint sigsuspend(const sigset_t * restrict set) {\n\t__sets_errno(syscall_sigsuspend(set));\n}\n\nDEFN_SYSCALL2(sigwait,SYS_SIGWAIT,const sigset_t *,int *);\n\nint sigwait(const sigset_t * set, int * sig) {\n\tint res;\n\tdo {\n\t\tres = syscall_sigwait(set,sig);\n\t} while (res == -EINTR);\n\n\tif (res < 0) {\n\t\tres = -res;\n\t\terrno = res;\n\t}\n\n\treturn res;\n}\n"
  },
  {
    "path": "libc/stdio/perror.c",
    "content": "#include <stdio.h>\n#include <errno.h>\n#include <string.h>\n\nvoid perror(const char *s) {\n\tfprintf(stderr, \"%s: %s\\n\", s, strerror(errno));\n}\n"
  },
  {
    "path": "libc/stdio/printf.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <va_list.h>\n\n#define OUT(c) do { callback(userData, (c)); written++; } while (0)\nstatic size_t print_dec(unsigned long long value, unsigned int width, int (*callback)(void*,char), void * userData, int fill_zero, int align_right, int precision) {\n\tsize_t written = 0;\n\tunsigned long long n_width = 1;\n\tunsigned long long i = 9;\n\tif (precision == -1) precision = 1;\n\n\tif (value == 0) {\n\t\tn_width = 0;\n\t} else {\n\t\tunsigned long long val = value;\n\t\twhile (val >= 10UL) {\n\t\t\tval /= 10UL;\n\t\t\tn_width++;\n\t\t}\n\t}\n\n\tif (n_width < (unsigned long long)precision) n_width = precision;\n\n\tint printed = 0;\n\tif (align_right) {\n\t\twhile (n_width + printed < width) {\n\t\t\tOUT(fill_zero ? '0' : ' ');\n\t\t\tprinted += 1;\n\t\t}\n\n\t\ti = n_width;\n\t\tchar tmp[100];\n\t\twhile (i > 0) {\n\t\t\tunsigned long long n = value / 10;\n\t\t\tlong long r = value % 10;\n\t\t\ttmp[i - 1] = r + '0';\n\t\t\ti--;\n\t\t\tvalue = n;\n\t\t}\n\t\twhile (i < n_width) {\n\t\t\tOUT(tmp[i]);\n\t\t\ti++;\n\t\t}\n\t} else {\n\t\ti = n_width;\n\t\tchar tmp[100];\n\t\twhile (i > 0) {\n\t\t\tunsigned long long n = value / 10;\n\t\t\tlong long r = value % 10;\n\t\t\ttmp[i - 1] = r + '0';\n\t\t\ti--;\n\t\t\tvalue = n;\n\t\t\tprinted++;\n\t\t}\n\t\twhile (i < n_width) {\n\t\t\tOUT(tmp[i]);\n\t\t\ti++;\n\t\t}\n\t\twhile (printed < (long long)width) {\n\t\t\tOUT(fill_zero ? '0' : ' ');\n\t\t\tprinted += 1;\n\t\t}\n\t}\n\n\treturn written;\n}\n\n/*\n * Hexadecimal to string\n */\nstatic size_t print_hex(unsigned long long value, unsigned int width, int (*callback)(void*,char), void* userData, int fill_zero, int alt, int caps, int align) {\n\tsize_t written = 0;\n\tint i = width;\n\n\tunsigned long long n_width = 1;\n\tunsigned long long j = 0x0F;\n\twhile (value > j && j < UINT64_MAX) {\n\t\tn_width += 1;\n\t\tj *= 0x10;\n\t\tj += 0x0F;\n\t}\n\n\tif (!fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\tif (alt) {\n\t\tOUT('0');\n\t\tOUT(caps ? 'X' : 'x');\n\t}\n\n\tif (fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT('0');\n\t\t\ti--;\n\t\t}\n\t}\n\n\ti = (long long)n_width;\n\twhile (i-- > 0) {\n\t\tchar c = (caps ? \"0123456789ABCDEF\" : \"0123456789abcdef\")[(value>>(i*4))&0xF];\n\t\tOUT(c);\n\t}\n\n\tif (align == 0) {\n\t\ti = width;\n\t\twhile (i > (long long)n_width + 2*!!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\treturn written;\n}\n\nstatic size_t print_oct(unsigned long long value, unsigned int width, int (*callback)(void*,char), void* userData, int fill_zero, int alt, int caps, int align) {\n\tsize_t written = 0;\n\tint i = width;\n\n\tunsigned long long n_width = 1;\n\tunsigned long long j = 7;\n\twhile (value > j && j < UINT64_MAX) {\n\t\tn_width += 1;\n\t\tj *= 8;\n\t\tj += 7;\n\t}\n\n\tif (!fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + !!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\tif (alt) {\n\t\tOUT('0');\n\t}\n\n\tif (fill_zero && align == 1) {\n\t\twhile (i > (long long)n_width + !!alt) {\n\t\t\tOUT('0');\n\t\t\ti--;\n\t\t}\n\t}\n\n\ti = (long long)n_width;\n\twhile (i-- > 0) {\n\t\tchar c = \"01234567\"[(value>>(i*3))&0x7];\n\t\tOUT(c);\n\t}\n\n\tif (align == 0) {\n\t\ti = width;\n\t\twhile (i > (long long)n_width + !!alt) {\n\t\t\tOUT(' ');\n\t\t\ti--;\n\t\t}\n\t}\n\n\treturn written;\n}\n/*\n * vasprintf()\n */\nsize_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args) {\n\tchar * s;\n\tsize_t written = 0;\n\tfor (const char *f = fmt; *f; f++) {\n\t\tif (*f != '%') {\n\t\t\tOUT(*f);\n\t\t\tcontinue;\n\t\t}\n\t\t++f;\n\t\tunsigned int arg_width = 0;\n\t\tint align = 1; /* right */\n\t\tint fill_zero = 0;\n\t\tint big = 0;\n\t\tint alt = 0;\n\t\tint always_sign = 0;\n\t\tint precision = -1;\n\t\twhile (1) {\n\t\t\tif (*f == '-') {\n\t\t\t\talign = 0;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '#') {\n\t\t\t\talt = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '*') {\n\t\t\t\targ_width = (int)va_arg(args, int);\n\t\t\t\t++f;\n\t\t\t} else if (*f == '0') {\n\t\t\t\tfill_zero = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == '+') {\n\t\t\t\talways_sign = 1;\n\t\t\t\t++f;\n\t\t\t} else if (*f == ' ') {\n\t\t\t\talways_sign = 2;\n\t\t\t\t++f;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\twhile (*f >= '0' && *f <= '9') {\n\t\t\targ_width *= 10;\n\t\t\targ_width += *f - '0';\n\t\t\t++f;\n\t\t}\n\t\tif (*f == '.') {\n\t\t\t++f;\n\t\t\tprecision = 0;\n\t\t\tif (*f == '*') {\n\t\t\t\tprecision = (int)va_arg(args, int);\n\t\t\t\t++f;\n\t\t\t} else  {\n\t\t\t\twhile (*f >= '0' && *f <= '9') {\n\t\t\t\t\tprecision *= 10;\n\t\t\t\t\tprecision += *f - '0';\n\t\t\t\t\t++f;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (*f == 'l') {\n\t\t\tbig = 1;\n\t\t\t++f;\n\t\t\tif (*f == 'l') {\n\t\t\t\tbig = 2;\n\t\t\t\t++f;\n\t\t\t}\n\t\t}\n\t\tif (*f == 'j') {\n\t\t\tbig = (sizeof(uintmax_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(uintmax_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\tif (*f == 'z') {\n\t\t\tbig = (sizeof(size_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(size_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\tif (*f == 't') {\n\t\t\tbig = (sizeof(ptrdiff_t) == sizeof(unsigned long long) ? 2 :\n\t\t\t\t   sizeof(ptrdiff_t) == sizeof(unsigned long) ? 1 : 0);\n\t\t\t++f;\n\t\t}\n\t\t/* fmt[i] == '%' */\n\t\tswitch (*f) {\n\t\t\tcase 's': /* String pointer -> String */\n\t\t\t\t{\n\t\t\t\t\tsize_t count = 0;\n\t\t\t\t\tif (big) {\n\t\t\t\t\t\treturn written;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts = (char *)va_arg(args, char *);\n\t\t\t\t\t\tif (s == NULL) {\n\t\t\t\t\t\t\ts = \"(null)\";\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (precision >= 0) {\n\t\t\t\t\t\t\twhile (*s && precision > 0) {\n\t\t\t\t\t\t\t\tOUT(*s++);\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tprecision--;\n\t\t\t\t\t\t\t\tif (arg_width && count == arg_width) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twhile (*s) {\n\t\t\t\t\t\t\t\tOUT(*s++);\n\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\tif (arg_width && count == arg_width) break;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\twhile (count < arg_width) {\n\t\t\t\t\t\tOUT(' ');\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'c': /* Single character */\n\t\t\t\tOUT((char)va_arg(args,int));\n\t\t\t\tbreak;\n\t\t\tcase 'p':\n\t\t\t\talt = 1;\n\t\t\t\tif (sizeof(void*) == sizeof(long long)) big = 2; /* fallthrough */\n\t\t\tcase 'X':\n\t\t\tcase 'x': /* Hexadecimal number */\n\t\t\t\t{\n\t\t\t\t\tunsigned long long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (unsigned long long)va_arg(args, unsigned long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (unsigned long)va_arg(args, unsigned long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (unsigned int)va_arg(args, unsigned int);\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_hex(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'O':\n\t\t\tcase 'o': /* Octal number */\n\t\t\t\t{\n\t\t\t\t\tunsigned long long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (unsigned long long)va_arg(args, unsigned long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (unsigned long)va_arg(args, unsigned long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (unsigned int)va_arg(args, unsigned int);\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_oct(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'i':\n\t\t\tcase 'd': /* Decimal number */\n\t\t\t\t{\n\t\t\t\t\tlong long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (long long)va_arg(args, long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (long)va_arg(args, long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (int)va_arg(args, int);\n\t\t\t\t\t}\n\t\t\t\t\tif (val < 0) {\n\t\t\t\t\t\tOUT('-');\n\t\t\t\t\t\tval = -val;\n\t\t\t\t\t} else if (always_sign) {\n\t\t\t\t\t\tOUT(always_sign == 2 ? ' ' : '+');\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_dec(val, arg_width, callback, userData, fill_zero, align, precision);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'u': /* Unsigned ecimal number */\n\t\t\t\t{\n\t\t\t\t\tunsigned long long val;\n\t\t\t\t\tif (big == 2) {\n\t\t\t\t\t\tval = (unsigned long long)va_arg(args, unsigned long long);\n\t\t\t\t\t} else if (big == 1) {\n\t\t\t\t\t\tval = (unsigned long)va_arg(args, unsigned long);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tval = (unsigned int)va_arg(args, unsigned int);\n\t\t\t\t\t}\n\t\t\t\t\twritten += print_dec(val, arg_width, callback, userData, fill_zero, align, precision);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'G':\n\t\t\tcase 'F':\n\t\t\tcase 'g': /* supposed to also support e */\n\t\t\tcase 'f':\n\t\t\t\t{\n\t\t\t\t\tif (precision == -1) precision = 8;\n\t\t\t\t\tdouble val = (double)va_arg(args, double);\n\t\t\t\t\tuint64_t asBits;\n\t\t\t\t\tmemcpy(&asBits,&val,sizeof(double));\n#define SIGNBIT(d) (d & 0x8000000000000000UL)\n\n\t\t\t\t\t/* Extract exponent */\n\t\t\t\t\tint64_t exponent = (asBits & 0x7ff0000000000000UL) >> 52;\n\n\t\t\t\t\t/* Fraction part */\n\t\t\t\t\tuint64_t fraction = (asBits & 0x000fffffffffffffUL);\n\n\t\t\t\t\tif (exponent == 0x7ff) {\n\t\t\t\t\t\tif (!fraction) {\n\t\t\t\t\t\t\tif (SIGNBIT(asBits)) {\n\t\t\t\t\t\t\t\tOUT('-');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tOUT('i');\n\t\t\t\t\t\t\tOUT('n');\n\t\t\t\t\t\t\tOUT('f');\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tOUT('n');\n\t\t\t\t\t\t\tOUT('a');\n\t\t\t\t\t\t\tOUT('n');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t} else if ((*f == 'g' || *f == 'G') && exponent == 0 && fraction == 0) {\n\t\t\t\t\t\tif (SIGNBIT(asBits)) {\n\t\t\t\t\t\t\tOUT('-');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tOUT('0');\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* Okay, now we can do some real work... */\n\n\t\t\t\t\tint isNegative = !!SIGNBIT(asBits);\n\t\t\t\t\tif (isNegative) {\n\t\t\t\t\t\tOUT('-');\n\t\t\t\t\t\tval = -val;\n\t\t\t\t\t}\n\n\t\t\t\t\twritten += print_dec((unsigned long long)val, arg_width, callback, userData, fill_zero, align, 1);\n\t\t\t\t\tOUT('.');\n\t\t\t\t\tfor (int j = 0; j < ((precision > -1 && precision < 16) ? precision : 16); ++j) {\n\t\t\t\t\t\tif ((unsigned long long)(val * 100000.0) % 100000 == 0 && j != 0) break;\n\t\t\t\t\t\tval = val - (unsigned long long)val;\n\t\t\t\t\t\tval *= 10.0;\n\t\t\t\t\t\tdouble roundy = ((double)(val - (unsigned long long)val) - 0.99999);\n\t\t\t\t\t\tif (roundy < 0.00001 && roundy > -0.00001 && ((unsigned long long)(val) % 10) != 9) {\n\t\t\t\t\t\t\twritten += print_dec((unsigned long long)(val) % 10 + 1, 0, callback, userData, 0, 0, 1);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twritten += print_dec((unsigned long long)(val) % 10, 0, callback, userData, 0, 0, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase '%': /* Escape */\n\t\t\t\tOUT('%');\n\t\t\t\tbreak;\n\t\t\tdefault: /* Nothing at all, just dump it */\n\t\t\t\tOUT(*f);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn written;\n}\n\n/* Strings */\nstruct CBData {\n\tchar * str;\n\tsize_t size;\n\tsize_t written;\n};\n\nstatic int cb_sprintf(void * user, char c) {\n\tstruct CBData * data = user;\n\tif (data->size > data->written + 1) {\n\t\tdata->str[data->written] = c;\n\t\tdata->written++;\n\t\tif (data->written < data->size) {\n\t\t\tdata->str[data->written] = '\\0';\n\t\t}\n\t}\n\treturn 0;\n}\n\nint vsnprintf(char *str, size_t size, const char *format, va_list ap) {\n\tstruct CBData data = {str,size,0};\n\tint out = xvasprintf(cb_sprintf, &data, format, ap);\n\tcb_sprintf(&data, '\\0');\n\treturn out;\n}\n\nint snprintf(char * str, size_t size, const char * format, ...) {\n\tstruct CBData data = {str,size,0};\n\tva_list args;\n\tva_start(args, format);\n\tint out = xvasprintf(cb_sprintf, &data, format, args);\n\tva_end(args);\n\tcb_sprintf(&data, '\\0');\n\treturn out;\n}\n\n/* Unlimited strings */\nstatic int cb_sxprintf(void * user, char c) {\n\tstruct CBData * data = user;\n\tdata->str[data->written] = c;\n\tdata->written++;\n\treturn 0;\n}\n\nint vsprintf(char *str, const char *format, va_list ap) {\n\tstruct CBData data = {str,0,0};\n\tint out = xvasprintf(cb_sxprintf, &data, format, ap);\n\tcb_sxprintf(&data, '\\0');\n\treturn out;\n}\n\nint sprintf(char * str, const char * format, ...) {\n\tstruct CBData data = {str,0,0};\n\tva_list args;\n\tva_start(args, format);\n\tint out = xvasprintf(cb_sxprintf, &data, format, args);\n\tva_end(args);\n\tcb_sxprintf(&data, '\\0');\n\treturn out;\n}\n\n/**\n * String that needs to reallocate as it goes\n */\nstatic int cb_asprintf(void * user, char c) {\n\tstruct CBData * data = user;\n\n\tif (data->written + 1 > data->size) {\n\t\tdata->size = data->size < 8 ? 8 : data->size * 2;\n\t\tdata->str = realloc(data->str, data->size);\n\t}\n\n\tdata->str[data->written] = c;\n\tdata->written++;\n\treturn 0;\n}\n\nint vasprintf(char ** buf, const char * fmt, va_list args) {\n\tstruct CBData data = {NULL,0,0};\n\txvasprintf(cb_asprintf, &data, fmt, args);\n\tcb_asprintf(&data, '\\0');\n\t*buf = data.str;\n\treturn 0;\n}\n\nint asprintf(char ** ret, const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = vasprintf(ret,fmt,args);\n\tva_end(args);\n\treturn out;\n}\n\n/* Streams */\n\nstatic int cb_fprintf(void * user, char c) {\n\tfputc(c,(FILE*)user);\n\treturn 0;\n}\n\nint fprintf(FILE *stream, const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = xvasprintf(cb_fprintf, stream, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n\nint printf(const char * fmt, ...) {\n\tva_list args;\n\tva_start(args, fmt);\n\tint out = xvasprintf(cb_fprintf, stdout, fmt, args);\n\tva_end(args);\n\treturn out;\n}\n\nint vfprintf(FILE * stream, const char *fmt, va_list args) {\n\treturn xvasprintf(cb_fprintf, stream, fmt, args);\n}\n\nint vprintf(const char *fmt, va_list args) {\n\treturn xvasprintf(cb_fprintf, stdout, fmt, args);\n}\n\n"
  },
  {
    "path": "libc/stdio/puts.c",
    "content": "#include <stdio.h>\n#include <string.h>\n\nint puts(const char *s) {\n\t/* eof? */\n\tfwrite(s, 1, strlen(s), stdout);\n\tfwrite(\"\\n\", 1, 1, stdout);\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/stdio/remove.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n\nint remove(const char * pathname) {\n\t/* TODO directories */\n\treturn unlink(pathname);\n}\n"
  },
  {
    "path": "libc/stdio/rename.c",
    "content": "#include <stdio.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(rename, SYS_RENAME, const char *, const char *);\n\nint rename(const char * oldpath, const char * newpath) {\n\t__sets_errno(syscall_rename(oldpath, newpath));\n}\n"
  },
  {
    "path": "libc/stdio/scanf.c",
    "content": "#include <stdio.h>\n#include <ctype.h>\n#include <string.h>\n\nextern char * _argv_0;\nextern int __libc_debug;\n\nint vsscanf(const char *str, const char *format, va_list ap) {\n\tif (__libc_debug) fprintf(stderr, \"%s: sscanf(\\\"%s\\\", format=\\\"%s\\\", ...);\\n\", _argv_0, str, format);\n\tint count = 0;\n\twhile (*format) {\n\t\tif (*format == ' ') {\n\t\t\t/* handle white space */\n\t\t\twhile (*str && isspace(*str)) {\n\t\t\t\tstr++;\n\t\t\t}\n\t\t} else if (*format == '%') {\n\t\t\t/* Parse */\n\t\t\tformat++;\n\t\t\tint _long = 0;\n\n\t\t\tif (*format == 'l') {\n\t\t\t\tformat++;\n\t\t\t\tif (*format == 'l') {\n\t\t\t\t\t_long = 1;\n\t\t\t\t\tif (__libc_debug) fprintf(stderr, \"%s: \\033[33;3mWarning\\033[0m: Need to scan a large pointer (%d)\\n\", _argv_0, _long);\n\t\t\t\t\tformat++;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (*format == 'd') {\n\t\t\t\tint i = 0;\n\t\t\t\tint sign = 1;\n\t\t\t\twhile (isspace(*str)) str++;\n\t\t\t\tif (*str == '-') {\n\t\t\t\t\tsign = -1;\n\t\t\t\t\tstr++;\n\t\t\t\t}\n\t\t\t\twhile (*str && *str >= '0' && *str <= '9') {\n\t\t\t\t\ti = i * 10 + *str - '0';\n\t\t\t\t\tstr++;\n\t\t\t\t}\n\t\t\t\tint * out = (int *)va_arg(ap, int*);\n\t\t\t\tif (__libc_debug) fprintf(stderr, \"%s: sscanf: out %d\\n\", _argv_0, i);\n\t\t\t\tcount++;\n\t\t\t\t*out = i * sign;\n\t\t\t} else if (*format == 'u') {\n\t\t\t\tunsigned int i = 0;\n\t\t\t\twhile (isspace(*str)) str++;\n\t\t\t\twhile (*str && *str >= '0' && *str <= '9') {\n\t\t\t\t\ti = i * 10 + *str - '0';\n\t\t\t\t\tstr++;\n\t\t\t\t}\n\t\t\t\tunsigned int * out = (unsigned int *)va_arg(ap, unsigned int*);\n\t\t\t\tif (__libc_debug) fprintf(stderr, \"%s: sscanf: out %d\\n\", _argv_0, i);\n\t\t\t\tcount++;\n\t\t\t\t*out = i;\n\t\t\t}\n\t\t} else {\n\t\t\t/* Expect exact character? */\n\t\t\tif (*str == *format) {\n\t\t\t\tstr++;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tformat++;\n\t}\n\treturn count;\n}\n\nint vfscanf(FILE * stream, const char *format, va_list ap) {\n\tif (__libc_debug) fprintf(stderr, \"%s: fscanf(%d, format=%s, ...);\\n\", _argv_0, fileno(stream), format);\n\twhile (*format) {\n\t\tif (*format == ' ') {\n\t\t\t/* Handle whitespace */\n\t\t} else if (*format == '%') {\n\t\t\t/* Parse */\n\t\t} else {\n\t\t\t/* Expect exact character? */\n\t\t}\n\t\tformat++;\n\t}\n\treturn 0;\n}\n\nint sscanf(const char *str, const char *format, ...) {\n\tva_list args;\n\tva_start(args, format);\n\tint out = vsscanf(str, format, args);\n\tva_end(args);\n\treturn out;\n}\n\nint fscanf(FILE *stream, const char *format, ...) {\n\tva_list args;\n\tva_start(args, format);\n\tint out = vfscanf(stream, format, args);\n\tva_end(args);\n\treturn out;\n}\n\nint scanf(const char *format, ...) {\n\tva_list args;\n\tva_start(args, format);\n\tint out = vfscanf(stdin, format, args);\n\tva_end(args);\n\treturn out;\n}\n"
  },
  {
    "path": "libc/stdio/stdio.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <syscall.h>\n#include <string.h>\n#include <sys/types.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <errno.h>\n\nstruct _FILE {\n\tint fd;\n\n\tchar * read_buf;\n\tint available;\n\tint offset;\n\tint read_from;\n\tint ungetc;\n\tint bufsiz;\n\tlong last_read_start;\n\tchar * _name;\n\n\tchar * write_buf;\n\tsize_t written;\n\tsize_t wbufsiz;\n\n\tstruct _FILE * prev;\n\tstruct _FILE * next;\n\n\tint flags;\n};\n\n/* Flag values */\n#define STDIO_EOF         0x0001\n#define STDIO_ERROR       0x0002\n#define STDIO_UNSEEKABLE  0x0004\n\nFILE _stdin = {\n\t.fd = 0,\n\t.read_buf = NULL,\n\t.available = 0,\n\t.offset = 0,\n\t.read_from = 0,\n\t.ungetc = -1,\n\t.last_read_start = 0,\n\t.bufsiz = BUFSIZ,\n\n\t.wbufsiz = BUFSIZ,\n\t.write_buf = NULL,\n\t.written = 0,\n\t.flags = 0,\n};\n\nFILE _stdout = {\n\t.fd = 1,\n\t.read_buf = NULL,\n\t.available = 0,\n\t.offset = 0,\n\t.read_from = 0,\n\t.ungetc = -1,\n\t.last_read_start = 0,\n\t.bufsiz = BUFSIZ,\n\n\t.wbufsiz = BUFSIZ,\n\t.write_buf = NULL,\n\t.written = 0,\n\t.flags = 0,\n};\n\nFILE _stderr = {\n\t.fd = 2,\n\t.read_buf = NULL,\n\t.available = 0,\n\t.offset = 0,\n\t.read_from = 0,\n\t.ungetc = -1,\n\t.last_read_start = 0,\n\t.bufsiz = BUFSIZ,\n\n\t.wbufsiz = BUFSIZ,\n\t.write_buf = NULL,\n\t.written = 0,\n\t.flags = 0,\n};\n\nFILE * stdin = &_stdin;\nFILE * stdout = &_stdout;\nFILE * stderr = &_stderr;\n\nstatic FILE * _head = NULL;\n\nvoid __stdio_init_buffers(void) {\n\t_stdin.read_buf = malloc(BUFSIZ);\n\t_stdout.write_buf = malloc(BUFSIZ);\n\t_stderr.write_buf = malloc(BUFSIZ);\n\t_stdin._name = strdup(\"stdin\");\n\t_stdout._name = strdup(\"stdout\");\n\t_stderr._name = strdup(\"stderr\");\n}\n\nvoid __stdio_cleanup(void) {\n\tif (stdout) fflush(stdout);\n\tif (stderr) fflush(stderr);\n\twhile (_head) {\n\t\tfclose(_head);\n\t}\n}\n\n#if 0\nstatic char * stream_id(FILE * stream) {\n\tstatic char out[] = \"stream\\0\\0\\0\\0\\0\\0\";\n\tif (stream == &_stdin) return \"stdin\";\n\tif (stream == &_stdout) return \"stdout\";\n\tif (stream == &_stderr) return \"stderr\";\n\tsprintf(out, \"stream %d\", fileno(stream));\n\treturn out;\n}\n#endif\n\nextern int __libc_debug;\nextern char * _argv_0;\n\nint setvbuf(FILE * stream, char * buf, int mode, size_t size) {\n\tif (mode != _IOLBF) {\n\t\treturn -1; /* Unsupported */\n\t}\n\tif (buf) {\n\t\tif (stream->read_buf) {\n\t\t\tfree(stream->read_buf);\n\t\t}\n\t\tstream->read_buf = buf;\n\t\tstream->bufsiz = size;\n\t}\n\treturn 0;\n}\n\nint fflush(FILE * stream) {\n\tif (!stream->write_buf) return EOF;\n\tif (stream->written) {\n\t\tssize_t written = syscall_write(stream->fd, stream->write_buf, stream->written);\n\t\tif (written < 0) {\n\t\t\tstream->flags |= STDIO_ERROR;\n\t\t\treturn EOF;\n\t\t}\n\t\tstream->written = 0;\n\t}\n\treturn 0;\n}\n\nstatic size_t write_bytes(FILE * f, char * buf, size_t len) {\n\tif (!f->write_buf) return 0;\n\n\tsize_t newBytes = 0;\n\twhile (len > 0) {\n\t\tf->write_buf[f->written++] = *buf;\n\t\tif (f->written == (size_t)f->wbufsiz || *buf == '\\n') {\n\t\t\tif (fflush(f) == EOF) return newBytes;\n\t\t}\n\t\tnewBytes++;\n\t\tbuf++;\n\t\tlen--;\n\t}\n\n\treturn newBytes;\n}\n\nstatic size_t read_bytes(FILE * f, char * out, size_t len) {\n\tsize_t r_out = 0;\n\n\t//fprintf(stderr, \"%s: Read %d bytes from %s\\n\", _argv_0, len, stream_id(f));\n\t//fprintf(stderr, \"%s: off[%d] avail[%d] read[%d]\\n\", _argv_0, f->offset, f->available, f->read_from);\n\n\twhile (len > 0) {\n\t\tif (f->ungetc >= 0) {\n\t\t\t*out = f->ungetc;\n\t\t\tlen--;\n\t\t\tout++;\n\t\t\tr_out++;\n\t\t\tf->ungetc = -1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (f->available == 0) {\n\t\t\tif (f->offset == f->bufsiz) {\n\t\t\t\tf->offset = 0;\n\t\t\t}\n\t\t\tif (!(f->flags & STDIO_UNSEEKABLE)) {\n\t\t\t\tf->last_read_start = syscall_seek(f->fd, 0, SEEK_CUR);\n\t\t\t\tif (f->last_read_start == -ESPIPE) {\n\t\t\t\t\tf->last_read_start = -1;\n\t\t\t\t\tf->flags |= STDIO_UNSEEKABLE;\n\t\t\t\t}\n\t\t\t}\n\t\t\tssize_t r = read(fileno(f), &f->read_buf[f->offset], f->bufsiz - f->offset);\n\t\t\tif (r < 0) {\n\t\t\t\t//fprintf(stderr, \"error condition\\n\");\n\t\t\t\tf->flags |= STDIO_ERROR;\n\t\t\t\treturn r_out;\n\t\t\t} else {\n\t\t\t\tf->read_from = f->offset;\n\t\t\t\tf->available = r;\n\t\t\t\tf->offset += f->available;\n\t\t\t}\n\t\t}\n\n\t\tif (f->available == 0) {\n\t\t\t/* EOF condition */\n\t\t\t//fprintf(stderr, \"%s: no bytes available, returning read value of %d\\n\", _argv_0, r_out);\n\t\t\tf->flags |= STDIO_EOF;\n\t\t\treturn r_out;\n\t\t}\n\n\t\t//fprintf(stderr, \"%s: reading until %d reaches %d or %d reaches 0\\n\", _argv_0, f->read_from, f->offset, len);\n\t\twhile (f->read_from < f->offset && len > 0 && f->available > 0) {\n\t\t\t*out = f->read_buf[f->read_from];\n\t\t\tlen--;\n\t\t\tf->read_from++;\n\t\t\tf->available--;\n\t\t\tout++;\n\t\t\tr_out += 1;\n\t\t}\n\t}\n\n\t//fprintf(stderr, \"%s: read completed, returning read value of %d\\n\", _argv_0, r_out);\n\treturn r_out;\n}\n\nstatic void parse_mode(const char * mode, int * flags_, int * mask_) {\n\tconst char * x = mode;\n\n\tint flags = 0;\n\tint mask = 0644;\n\n\twhile (*x) {\n\t\tif (*x == 'a') {\n\t\t\tflags |= O_WRONLY;\n\t\t\tflags |= O_APPEND;\n\t\t\tflags |= O_CREAT;\n\t\t}\n\t\tif (*x == 'w') {\n\t\t\tflags |= O_WRONLY;\n\t\t\tflags |= O_CREAT;\n\t\t\tflags |= O_TRUNC;\n\t\t\tmask = 0666;\n\t\t}\n\t\tif (*x == '+') {\n\t\t\tflags |= O_RDWR;\n\t\t\tflags &= ~(O_APPEND); /* uh... */\n\t\t}\n\t\tif (*x == 'x') {\n\t\t\tflags |= O_EXCL;\n\t\t}\n\t\t++x;\n\t}\n\n\t*flags_  = flags;\n\t*mask_ = mask;\n}\n\n\nFILE * fopen(const char *path, const char *mode) {\n\n\tint flags, mask;\n\tparse_mode(mode, &flags, &mask);\n\tint fd = syscall_open(path, flags, mask);\n\n\tif (fd < 0) {\n\t\terrno = -fd;\n\t\treturn NULL;\n\t}\n\n\tFILE * out = malloc(sizeof(FILE));\n\tmemset(out, 0, sizeof(struct _FILE));\n\tout->fd = fd;\n\tout->read_buf = malloc(BUFSIZ);\n\tout->bufsiz = BUFSIZ;\n\tout->available = 0;\n\tout->read_from = 0;\n\tout->offset = 0;\n\tout->ungetc = -1;\n\tout->flags = 0;\n\tout->_name = strdup(path);\n\n\tout->write_buf = malloc(BUFSIZ);\n\tout->written = 0;\n\tout->wbufsiz = BUFSIZ;\n\n\tout->next = _head;\n\tif (_head) _head->prev = out;\n\t_head = out;\n\n\treturn out;\n}\n\n/* This is very wrong */\nFILE * freopen(const char *path, const char *mode, FILE * stream) {\n\n\tif (path) {\n\t\tfflush(stream);\n\t\tfree(stream->_name);\n\t\tsyscall_close(stream->fd);\n\t\tint flags, mask;\n\t\tparse_mode(mode, &flags, &mask);\n\t\tint fd = syscall_open(path, flags, mask);\n\t\tstream->fd = fd;\n\t\tstream->available = 0;\n\t\tstream->read_from = 0;\n\t\tstream->offset = 0;\n\t\tstream->ungetc = -1;\n\t\tstream->flags = 0;\n\t\tstream->_name = strdup(path);\n\t\tstream->written = 0;\n\t\tif (fd < 0) {\n\t\t\terrno = -fd;\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn stream;\n}\n\nint ungetc(int c, FILE * stream) {\n\tif (stream->ungetc > 0)\n\t\treturn EOF;\n\n\treturn (stream->ungetc = c);\n}\n\nFILE * fdopen(int fd, const char *mode){\n\tFILE * out = malloc(sizeof(FILE));\n\tmemset(out, 0, sizeof(struct _FILE));\n\tout->fd = fd;\n\tout->read_buf = malloc(BUFSIZ);\n\tout->bufsiz = BUFSIZ;\n\tout->available = 0;\n\tout->read_from = 0;\n\tout->offset = 0;\n\tout->ungetc = -1;\n\tout->flags = 0;\n\n\tchar tmp[30];\n\tsprintf(tmp, \"fd[%d]\", fd);\n\tout->_name = strdup(tmp);\n\n\tout->write_buf = malloc(BUFSIZ);\n\tout->written = 0;\n\tout->wbufsiz = BUFSIZ;\n\n\tout->next = _head;\n\tif (_head) _head->prev = out;\n\t_head = out;\n\n\treturn out;\n}\n\nint _fwouldblock(FILE * stream) {\n\treturn !stream->available;\n}\n\nint fclose(FILE * stream) {\n\tfflush(stream);\n\tint out = syscall_close(stream->fd);\n\tfree(stream->_name);\n\tfree(stream->read_buf);\n\tif (stream->write_buf) free(stream->write_buf);\n\tstream->write_buf = NULL;\n\tif (stream == &_stdin || stream == &_stdout || stream == &_stderr) {\n\t\treturn out;\n\t} else {\n\n\t\tif (stream->prev) stream->prev->next = stream->next;\n\t\tif (stream->next) stream->next->prev = stream->prev;\n\t\tif (stream == _head) _head = stream->next;\n\n\t\tfree(stream);\n\t\treturn out;\n\t}\n}\n\nstatic char * _whence_str(int whence) {\n\tif (whence == SEEK_SET) return \"SEEK_SET\";\n\tif (whence == SEEK_CUR) return \"SEEK_CUR\";\n\tif (whence == SEEK_END) return \"SEEK_END\";\n\treturn \"?\";\n}\n\nint fseek(FILE * stream, long offset, int whence) {\n\tif (stream->read_from && whence == SEEK_CUR) {\n\t\tif (_argv_0 && strcmp(_argv_0, \"ld.so\")) {\n\t\t\tif (__libc_debug) {\n\t\t\t\tfprintf(stderr, \"%s: fseek(%s, %ld, %s)\\n\", _argv_0, stream->_name, offset, _whence_str(whence));\n\t\t\t\tfprintf(stderr, \"\\033[33;3mWARNING\\033[0m: seeking when offset is currently %d\\n\", stream->read_from);\n\t\t\t\tfprintf(stderr, \"\\033[33;3mWARNING\\033[0m: this may not be reflected in kernel\\n\");\n\t\t\t}\n\t\t}\n\t\toffset = offset + stream->read_from + stream->last_read_start;\n\t\twhence = SEEK_SET;\n\t}\n\tif (stream->written) {\n\t\tfflush(stream);\n\t}\n\tstream->offset = 0;\n\tstream->read_from = 0;\n\tstream->available = 0;\n\tstream->ungetc = -1;\n\tstream->flags = 0;\n\n\tint resp = syscall_seek(stream->fd,offset,whence);\n\tif (resp < 0) {\n\t\terrno = -resp;\n\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nlong ftell(FILE * stream) {\n\tif (_argv_0 && strcmp(_argv_0, \"ld.so\") && __libc_debug) {\n\t\tfprintf(stderr, \"%s: ftell(%s)\\n\", _argv_0, stream->_name);\n\t}\n\tif (stream->written) {\n\t\tfflush(stream);\n\t}\n\tif (stream->read_from || stream->last_read_start) {\n\t\treturn stream->last_read_start + stream->read_from;\n\t}\n\tstream->offset = 0;\n\tstream->read_from = 0;\n\tstream->available = 0;\n\tstream->ungetc = -1;\n\tstream->flags = 0;\n\tlong resp = syscall_seek(stream->fd, 0, SEEK_CUR);\n\tif (resp < 0) {\n\t\terrno = -resp;\n\t\treturn -1;\n\t}\n\treturn resp;\n}\n\nint fgetpos(FILE *stream, fpos_t *pos) {\n\tlong ret = ftell(stream);\n\tif (ret == -1) return -1;\n\n\t*pos = ret;\n\treturn 0;\n}\n\nint fsetpos(FILE *stream, const fpos_t *pos) {\n\treturn fseek(stream, *pos, SEEK_SET);\n}\n\nsize_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream) {\n\tchar * tracking = (char*)ptr;\n\tfor (size_t i = 0; i < nmemb; ++i) {\n\t\tsize_t r = read_bytes(stream, tracking, size);\n\t\ttracking += r;\n\t\tif (r < size) {\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn nmemb;\n}\n\nsize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream) {\n\tchar * tracking = (char*)ptr;\n\tfor (size_t i = 0; i < nmemb; ++i) {\n\t\tsize_t r = write_bytes(stream, tracking, size);\n\t\ttracking += r;\n\t\tif (r < size) {\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn nmemb;\n}\n\nint fileno(FILE * stream) {\n\treturn stream->fd;\n}\n\nint fputs(const char *s, FILE *stream) {\n\twhile (*s) {\n\t\tfputc(*s++, stream);\n\t}\n\treturn 0;\n}\n\nint fputc(int c, FILE *stream) {\n\tchar data[] = {c};\n\twrite_bytes(stream, data, 1);\n\treturn c;\n}\n\nint putc(int c, FILE *stream) __attribute__((weak, alias(\"fputc\")));\n\nint fgetc(FILE * stream) {\n\tchar buf[1];\n\tint r;\n\tr = fread(buf, 1, 1, stream);\n\tif (r < 0) {\n\t\tstream->flags |= STDIO_EOF;\n\t\treturn EOF;\n\t} else if (r == 0) {\n\t\tstream->flags |= STDIO_EOF;\n\t\treturn EOF;\n\t}\n\treturn (unsigned char)buf[0];\n}\n\nint getc(FILE * stream) __attribute__((weak, alias(\"fgetc\")));\n\nint getchar(void) {\n\treturn fgetc(stdin);\n}\n\nchar *fgets(char *s, int size, FILE *stream) {\n\tif (size == 0) return NULL;\n\tif (size == 1) return *s = '\\0', s;\n\tint c;\n\tchar * out = s;\n\twhile ((c = fgetc(stream)) >= 0) {\n\t\t*s++ = c;\n\t\tsize--;\n\t\t*s = '\\0';\n\t\tif (size == 1 || c == '\\n') {\n\t\t\treturn out;\n\t\t}\n\t}\n\tif (c == EOF) {\n\t\tstream->flags |= STDIO_EOF;\n\t\tif (out == s) {\n\t\t\treturn NULL;\n\t\t} else {\n\t\t\t*s = '\\0';\n\t\t\treturn out;\n\t\t}\n\t}\n\treturn NULL;\n}\n\nint putchar(int c) {\n\treturn fputc(c, stdout);\n}\n\nvoid rewind(FILE *stream) {\n\tfseek(stream, 0, SEEK_SET);\n}\n\nvoid setbuf(FILE * stream, char * buf) {\n\t// ...\n}\n\nint feof(FILE * stream) {\n\treturn !!(stream->flags & STDIO_EOF);\n}\n\nvoid clearerr(FILE * stream) {\n\tstream->flags = ~(STDIO_ERROR | STDIO_EOF);\n}\n\nint ferror(FILE * stream) {\n\treturn !!(stream->flags & STDIO_ERROR);\n}\n\nssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream) {\n\tif (!lineptr || !n) {\n\t\terrno = EINVAL;\n\t\t/* XXX Are we supposed to set the stream to failed for this case? Seems dubious. */\n\t\treturn -1;\n\t}\n\n\tsize_t c = 0;\n\twhile (1) {\n\t\tint i = fgetc(stream);\n\n\t\tif (*n <= c + 1) {\n\t\t\tsize_t nn = *n < 120 ? 120 : (*n * 2);\n\t\t\t/* TODO: We don't define SSIZE_MAX? Anyway, ssize_t is typedefed to long on all both of our supported\n\t\t\t * platforms, so use LONG_MAX here as a replacement. size_t should be able to hold SSIZE_MAX * 2, so\n\t\t\t * the above calculation should not overflow and we can check here if *n has gotten too big. */\n\t\t\tif (nn > LONG_MAX) nn = LONG_MAX;\n\t\t\tif (nn <= c + 1) {\n\t\t\t\t/* ... and this check should only trip if we've maxed up *n to SSIZE_MAX\n\t\t\t\t *     and it's still not big enought to fit our data. */\n\t\t\t\terrno = EOVERFLOW;\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tchar * nlineptr = realloc(*lineptr, nn);\n\t\t\tif (!nlineptr) return -1; /* malloc failure */\n\t\t\t*n = nn;\n\t\t\t*lineptr = nlineptr;\n\t\t}\n\n\t\tif (i == EOF) {\n\t\t\t(*lineptr)[c] = '\\0';\n\t\t\tif (c) return c;\n\t\t\treturn -1;\n\t\t}\n\n\t\t(*lineptr)[c++] = i;\n\t\tif (i == delimiter) break;\n\t}\n\n\t(*lineptr)[c] = '\\0';\n\treturn c;\n}\n\nssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream) {\n\treturn getdelim(lineptr, n, '\\n', stream);\n}\n"
  },
  {
    "path": "libc/stdio/tmpfile.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n\nFILE * tmpfile(void) {\n\tstatic int tmpfile_num = 1;\n\n\tchar tmp[100];\n\tsprintf(tmp, \"/tmp/tmp%d.%d\", getpid(), tmpfile_num++);\n\n\tFILE * out = fopen(tmp, \"w+b\");\n\n\tunlink(tmp);\n\n\treturn out;\n}\n"
  },
  {
    "path": "libc/stdio/tmpnam.c",
    "content": "#include <stdio.h>\n#include <unistd.h>\n\nstatic char _internal[L_tmpnam];\n\nchar * tmpnam(char * s) {\n\tstatic int tmp_id = 1;\n\n\tif (!s) {\n\t\ts = _internal;\n\t}\n\n\tsprintf(s, \"/tmp/tmp%d.%d\", getpid(), tmp_id++);\n\n\treturn s;\n}\n"
  },
  {
    "path": "libc/stdlib/abort.c",
    "content": "#include <stdlib.h>\n#include <syscall.h>\n\nvoid abort(void) {\n\tsyscall_exit(-1);\n\t__builtin_unreachable();\n}\n"
  },
  {
    "path": "libc/stdlib/atexit.c",
    "content": "#include <stdlib.h>\n\nstatic void (*_atexit_handlers[32])(void) = {NULL};\nstatic int _atexit_count = 0;\n\nvoid _handle_atexit(void) {\n\tif (!_atexit_count) return;\n\tdo {\n\t\t_atexit_count--;\n\t\t_atexit_handlers[_atexit_count]();\n\t} while (_atexit_count);\n}\n\nint atexit(void (*h)(void)) {\n\tif (_atexit_count == ATEXIT_MAX) return 1;\n\t_atexit_handlers[_atexit_count++] = h;\n\treturn 0;\n}\n\n"
  },
  {
    "path": "libc/stdlib/atof.c",
    "content": "/* Really bad atof */\n\n#include <stdlib.h>\n\ndouble atof(const char * nptr) {\n\treturn strtod(nptr, NULL);\n}\n"
  },
  {
    "path": "libc/stdlib/bsearch.c",
    "content": "#include <stdlib.h>\n\nvoid *bsearch(const void *key, const void *base, size_t nmemb, size_t size,\n\tint (*compar)(const void *, const void *)) {\n\t/* Stupid naive implementation */\n\tconst char * b = base;\n\tsize_t i = 0;\n\twhile (i < nmemb) {\n\t\tconst void * a = b;\n\t\tif (!compar(key,a)) return (void *)a;\n\t\ti++;\n\t\tb += size;\n\t}\n\treturn NULL;\n}\n"
  },
  {
    "path": "libc/stdlib/div.c",
    "content": "#include <stdlib.h>\n\ndiv_t div(int numerator, int denominator) {\n\tdiv_t out;\n\tout.quot = numerator / denominator;\n\tout.rem  = numerator % denominator;\n\n\tif (numerator >= 0 && out.rem < 0) {\n\t\tout.quot++;\n\t\tout.rem -= denominator;\n\t} else if (numerator < 0 && out.rem > 0) {\n\t\tout.quot--;\n\t\tout.rem += denominator;\n\t}\n\n\treturn out;\n}\n\nldiv_t ldiv(long numerator, long denominator) {\n\tldiv_t out;\n\tout.quot = numerator / denominator;\n\tout.rem  = numerator % denominator;\n\n\tif (numerator >= 0 && out.rem < 0) {\n\t\tout.quot++;\n\t\tout.rem -= denominator;\n\t} else if (numerator < 0 && out.rem > 0) {\n\t\tout.quot--;\n\t\tout.rem += denominator;\n\t}\n\n\treturn out;\n}\n"
  },
  {
    "path": "libc/stdlib/getenv.c",
    "content": "#include <string.h>\n#include <stdlib.h>\n\nextern char ** environ;\n\nchar * getenv(const char *name) {\n\tchar ** e = environ;\n\tsize_t len = strlen(name);\n\twhile (*e) {\n\t\tchar * t = *e;\n\t\tif (strstr(t, name) == *e) {\n\t\t\tif (t[len] == '=') {\n\t\t\t\treturn &t[len+1];\n\t\t\t}\n\t\t}\n\t\te++;\n\t}\n\treturn NULL;\n}\n"
  },
  {
    "path": "libc/stdlib/labs.c",
    "content": "long int labs(long int j) {\n\treturn (j < 0) ? -j : j;\n}\n"
  },
  {
    "path": "libc/stdlib/malloc.c",
    "content": "/* vim: tabstop=4 shiftwidth=4 noexpandtab\r\n *\r\n * klange's Slab Allocator\r\n *\r\n * Implemented for CS241, Fall 2010, machine problem 7\r\n * at the University of Illinois, Urbana-Champaign.\r\n *\r\n * Overall competition winner for speed.\r\n * Well ranked in memory usage.\r\n *\r\n * Copyright (c) 2010-2018 K. Lange.  All rights reserved.\r\n *\r\n * Developed by: K. Lange <klange@toaruos.org>\r\n *               Dave Majnemer <dmajnem2@acm.uiuc.edu>\r\n *               Assocation for Computing Machinery\r\n *               University of Illinois, Urbana-Champaign\r\n *               http://acm.uiuc.edu\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy\r\n * of this software and associated documentation files (the \"Software\"), to\r\n * deal with the Software without restriction, including without limitation the\r\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\r\n * sell copies of the Software, and to permit persons to whom the Software is\r\n * furnished to do so, subject to the following conditions:\r\n *   1. Redistributions of source code must retain the above copyright notice,\r\n *      this list of conditions and the following disclaimers.\r\n *   2. Redistributions in binary form must reproduce the above copyright\r\n *      notice, this list of conditions and the following disclaimers in the\r\n *      documentation and/or other materials provided with the distribution.\r\n *   3. Neither the names of the Association for Computing Machinery, the\r\n *      University of Illinois, nor the names of its contributors may be used\r\n *      to endorse or promote products derived from this Software without\r\n *      specific prior written permission.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\r\n * CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\r\n * WITH THE SOFTWARE.\r\n *\r\n * ##########\r\n * # README #\r\n * ##########\r\n *\r\n * About the slab allocator\r\n * \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\r\n *\r\n * This is a simple implementation of a \"slab\" allocator. It works by operating\r\n * on \"bins\" of items of predefined sizes and a set of pseudo-bins of any size.\r\n * When a new allocation request is made, the allocator determines if it will\r\n * fit in an existing bin. If there are no bins of the correct size for a given\r\n * allocation request, the allocator will make a bin and add it to a(n empty)\r\n * list of available bins of that size. In this implementation, we use sizes\r\n * from 4 bytes (32 bit) or 8 bytes (64-bit) to 2KB for bins, fitting a 4K page\r\n * size. The implementation allows the number of pages in a single bin to be\r\n * increased, as well as allowing for changing the size of page (though this\r\n * should, for the most part, remain 4KB under any modern system).\r\n *\r\n * Special thanks\r\n * \"\"\"\"\"\"\"\"\"\"\"\"\"\"\r\n *\r\n * I would like to thank Dave Majnemer, who I have credited above as a\r\n * contributor, for his assistance. Without Dave, klmalloc would be a mash\r\n * up of bits of forward movement in no discernible pattern. Dave helped\r\n * me ensure that I could build a proper slab allocator and has consantly\r\n * derided me for not fixing the bugs and to-do items listed in the last\r\n * section of this readme.\r\n *\r\n * GCC Function Attributes\r\n * \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\r\n *\r\n * A couple of GCC function attributes, designated by the __attribute__\r\n * directive, are used in this code to streamline optimization.\r\n * I've chosen to include a brief overview of the particular attributes\r\n * I am making use of:\r\n *\r\n * - malloc:\r\n *   Tells gcc that a given function is a memory allocator\r\n *   and that non-NULL values it returns should never be\r\n *   associated with other chunks of memory. We use this for\r\n *   alloc, realloc and calloc, as is requested in the gcc\r\n *   documentation for the attribute.\r\n *\r\n * - always_inline:\r\n *   Tells gcc to always inline the given code, regardless of the\r\n *   optmization level. Small functions that would be noticeably\r\n *   slower with the overhead of paramter handling are given\r\n *   this attribute.\r\n *\r\n * - pure:\r\n *   Tells gcc that a function only uses inputs and its output.\r\n *\r\n * Things to work on\r\n * \"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\r\n *\r\n * TODO: Try to be more consistent on comment widths...\r\n * FIXME: Make thread safe! Not necessary for competition, but would be nice.\r\n * FIXME: Splitting/coalescing is broken. Fix this ASAP!\r\n *\r\n**/\r\n\r\n/* Includes {{{ */\r\n#include <syscall.h>\r\n#include <assert.h>\r\n#include <stdint.h>\r\n#include <limits.h>\r\n#include <string.h>\r\n#include <stdlib.h>\r\n/* }}} */\r\n/* Definitions {{{ */\r\n\r\n#define sbrk syscall_sbrk\r\n\r\n/*\r\n * Defines for often-used integral values\r\n * related to our binning and paging strategy.\r\n */\r\n#if defined(__x86_64__) || defined(__aarch64__)\r\n#define NUM_BINS 10U\t\t\t\t\t\t\t\t/* Number of bins, total, under 64-bit. */\r\n#define SMALLEST_BIN_LOG 3U\t\t\t\t\t\t\t/* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */\r\n#else\r\n#define NUM_BINS 11U\t\t\t\t\t\t\t\t/* Number of bins, total, under 32-bit. */\r\n#define SMALLEST_BIN_LOG 2U\t\t\t\t\t\t\t/* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */\r\n#endif\r\n#define BIG_BIN (NUM_BINS - 1)\t\t\t\t\t\t/* Index for the big bin, (NUM_BINS - 1) */\r\n#define SMALLEST_BIN (1UL << SMALLEST_BIN_LOG)\t\t/* Size of the smallest bin. */\r\n\r\n#define PAGE_SIZE 0x1000\t\t\t\t\t\t\t/* Size of a page (in bytes), should be 4KB */\r\n#define PAGE_MASK (PAGE_SIZE - 1)\t\t\t\t\t/* Block mask, size of a page * number of pages - 1. */\r\n#define SKIP_P INT32_MAX\t\t\t\t\t\t\t/* INT32_MAX is half of UINT32_MAX; this gives us a 50% marker for skip lists. */\r\n#define SKIP_MAX_LEVEL 6\t\t\t\t\t\t\t/* We have a maximum of 6 levels in our skip lists. */\r\n\r\n#define BIN_MAGIC 0xDEFAD00D\r\n\r\n/* }}} */\r\n\r\n/*\r\n * Internal functions.\r\n */\r\nstatic void * __attribute__ ((malloc)) klmalloc(uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klrealloc(void * ptr, uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size);\r\nstatic void * __attribute__ ((malloc)) klvalloc(uintptr_t size);\r\nstatic void klfree(void * ptr);\r\n\r\nstatic int volatile mem_lock = 0;\r\nstatic const char * _lock_holder;\r\n\r\n#ifdef assert\r\n#undef assert\r\n#define assert(statement) ((statement) ? (void)0 : _malloc_assert(__FILE__, __LINE__, __FUNCTION__, #statement))\r\n#endif\r\n\r\n#define WRITE(x) syscall_write(2, (char*)x, sizeof(x))\r\n#define WRITEV(x) syscall_write(2, (char*)x, strlen(x))\r\nstatic void _malloc_assert(const char * file, int line, const char * func, const char *x) {\r\n\tWRITEV(func);\r\n\tWRITE(\" in \");\r\n\tWRITEV(file);\r\n\tWRITE(\" failed assertion: \");\r\n\tWRITEV(x);\r\n\tWRITE(\"\\n\");\r\n\texit(1);\r\n}\r\n\r\nextern int __libc_is_multicore;\r\n\r\nstatic inline void _yield(void) {\r\n\tif (!__libc_is_multicore) syscall_yield();\r\n}\r\n\r\nstatic void spin_lock(int volatile * lock, const char * caller) {\r\n\twhile(__sync_lock_test_and_set(lock, 0x01)) {\r\n\t\t_yield();\r\n\t}\r\n\t_lock_holder = caller;\r\n}\r\n\r\nstatic void spin_unlock(int volatile * lock) {\r\n\t__sync_lock_release(lock);\r\n}\r\n\r\n\r\nvoid * __attribute__ ((malloc)) malloc(uintptr_t size) {\r\n\tspin_lock(&mem_lock, __FUNCTION__);\r\n\tvoid * ret = klmalloc(size);\r\n\tspin_unlock(&mem_lock);\r\n\treturn ret;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size) {\r\n\tspin_lock(&mem_lock, __FUNCTION__);\r\n\tvoid * ret = klrealloc(ptr, size);\r\n\tspin_unlock(&mem_lock);\r\n\treturn ret;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size) {\r\n\tspin_lock(&mem_lock, __FUNCTION__);\r\n\tvoid * ret = klcalloc(nmemb, size);\r\n\tspin_unlock(&mem_lock);\r\n\treturn ret;\r\n}\r\n\r\nvoid * __attribute__ ((malloc)) valloc(uintptr_t size) {\r\n\tspin_lock(&mem_lock, __FUNCTION__);\r\n\tvoid * ret = klvalloc(size);\r\n\tspin_unlock(&mem_lock);\r\n\treturn ret;\r\n}\r\n\r\nvoid free(void * ptr) {\r\n\tspin_lock(&mem_lock, __FUNCTION__);\r\n\tklfree(ptr);\r\n\tspin_unlock(&mem_lock);\r\n}\r\n\r\n\r\n/* Bin management {{{ */\r\n\r\n/*\r\n * Adjust bin size in bin_size call to proper bounds.\r\n */\r\nstatic inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_adjust_bin(uintptr_t bin)\r\n{\r\n\tif (bin <= (uintptr_t)SMALLEST_BIN_LOG)\r\n\t{\r\n\t\treturn 0;\r\n\t}\r\n\tbin -= SMALLEST_BIN_LOG + 1;\r\n\tif (bin > (uintptr_t)BIG_BIN) {\r\n\t\treturn BIG_BIN;\r\n\t}\r\n\treturn bin;\r\n}\r\n\r\n/*\r\n * Given a size value, find the correct bin\r\n * to place the requested allocation in.\r\n */\r\nstatic inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_bin_size(uintptr_t size) {\r\n\tuintptr_t bin = sizeof(size) * CHAR_BIT - __builtin_clzl(size);\r\n\tbin += !!(size & (size - 1));\r\n\treturn klmalloc_adjust_bin(bin);\r\n}\r\n\r\n/*\r\n * Bin header - One page of memory.\r\n * Appears at the front of a bin to point to the\r\n * previous bin (or NULL if the first), the next bin\r\n * (or NULL if the last) and the head of the bin, which\r\n * is a stack of cells of data.\r\n */\r\ntypedef struct _klmalloc_bin_header {\r\n\tstruct _klmalloc_bin_header *  next;\t/* Pointer to the next node. */\r\n\tvoid * head;\t\t\t\t\t\t\t/* Head of this bin. */\r\n\tuintptr_t size;\t\t\t\t\t\t\t/* Size of this bin, if big; otherwise bin index. */\r\n\tuint32_t bin_magic;\r\n} klmalloc_bin_header;\r\n\r\n/*\r\n * A big bin header is basically the same as a regular bin header\r\n * only with a pointer to the previous (physically) instead of\r\n * a \"next\" and with a list of forward headers.\r\n */\r\ntypedef struct _klmalloc_big_bin_header {\r\n\tstruct _klmalloc_big_bin_header * next;\r\n\tvoid * head;\r\n\tuintptr_t size;\r\n\tuint32_t bin_magic;\r\n} klmalloc_big_bin_header;\r\n\r\n\r\n/*\r\n * List of pages in a bin.\r\n */\r\ntypedef struct _klmalloc_bin_header_head {\r\n\tklmalloc_bin_header * first;\r\n} klmalloc_bin_header_head;\r\n\r\n/*\r\n * Array of available bins.\r\n */\r\nstatic klmalloc_bin_header_head klmalloc_bin_head[NUM_BINS - 1];\t/* Small bins */\r\n\r\n/* }}} Bin management */\r\n/* Doubly-Linked List {{{ */\r\n\r\n/*\r\n * Remove an entry from a page list.\r\n * Decouples the element from its\r\n * position in the list by linking\r\n * its neighbors to eachother.\r\n */\r\nstatic inline void __attribute__ ((always_inline)) klmalloc_list_decouple(klmalloc_bin_header_head *head, klmalloc_bin_header *node) {\r\n\tklmalloc_bin_header *next\t= node->next;\r\n\thead->first = next;\r\n\tnode->next = NULL;\r\n}\r\n\r\n/*\r\n * Insert an entry into a page list.\r\n * The new entry is placed at the front\r\n * of the list and the existing border\r\n * elements are updated to point back\r\n * to it (our list is doubly linked).\r\n */\r\nstatic inline void __attribute__ ((always_inline)) klmalloc_list_insert(klmalloc_bin_header_head *head, klmalloc_bin_header *node) {\r\n\tnode->next = head->first;\r\n\thead->first = node;\r\n}\r\n\r\n/*\r\n * Get the head of a page list.\r\n * Because redundant function calls\r\n * are really great, and just in case\r\n * we change the list implementation.\r\n */\r\nstatic inline klmalloc_bin_header * __attribute__ ((always_inline)) klmalloc_list_head(klmalloc_bin_header_head *head) {\r\n\treturn head->first;\r\n}\r\n\r\n/* }}} Lists */\r\n/* Stack {{{ */\r\n/*\r\n * Pop an item from a block.\r\n * Free space is stored as a stack,\r\n * so we get a free space for a bin\r\n * by popping a free node from the\r\n * top of the stack.\r\n */\r\nstatic void * klmalloc_stack_pop(klmalloc_bin_header *header) {\r\n\tassert(header);\r\n\tassert(header->head != NULL);\r\n\tassert((uintptr_t)header->head > (uintptr_t)header);\r\n\tif (header->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)header->head < (uintptr_t)header + header->size);\r\n\t} else {\r\n\t\tassert((uintptr_t)header->head < (uintptr_t)header + PAGE_SIZE);\r\n\t\tassert((uintptr_t)header->head > (uintptr_t)header + sizeof(klmalloc_bin_header) - 1);\r\n\t}\r\n\t\r\n\t/*\r\n\t * Remove the current head and point\r\n\t * the head to where the old head pointed.\r\n\t */\r\n\tvoid *item = header->head;\r\n\tuintptr_t **head = header->head;\r\n\tuintptr_t *next = *head;\r\n\theader->head = next;\r\n\treturn item;\r\n}\r\n\r\n/*\r\n * Push an item into a block.\r\n * When we free memory, we need\r\n * to add the freed cell back\r\n * into the stack of free spaces\r\n * for the block.\r\n */\r\nstatic void klmalloc_stack_push(klmalloc_bin_header *header, void *ptr) {\r\n\tassert(ptr != NULL);\r\n\tassert((uintptr_t)ptr > (uintptr_t)header);\r\n\tif (header->size > NUM_BINS) {\r\n\t\tassert((uintptr_t)ptr < (uintptr_t)header + header->size);\r\n\t} else {\r\n\t\tassert((uintptr_t)ptr < (uintptr_t)header + PAGE_SIZE);\r\n\t}\r\n\tuintptr_t **item = (uintptr_t **)ptr;\r\n\t*item = (uintptr_t *)header->head;\r\n\theader->head = item;\r\n}\r\n\r\n/*\r\n * Is this cell stack empty?\r\n * If the head of the stack points\r\n * to NULL, we have exhausted the\r\n * stack, so there is no more free\r\n * space available in the block.\r\n */\r\nstatic inline int __attribute__ ((always_inline)) klmalloc_stack_empty(klmalloc_bin_header *header) {\r\n\treturn header->head == NULL;\r\n}\r\n\r\n/* }}} Stack */\r\n\r\n/* malloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klmalloc(uintptr_t size) {\r\n\t/*\r\n\t * C standard implementation:\r\n\t * If size is zero, we can choose do a number of things.\r\n\t * This implementation will return a NULL pointer.\r\n\t */\r\n\tif (__builtin_expect(size == 0, 0))\r\n\t\treturn NULL;\r\n\r\n\t/*\r\n\t * Find the appropriate bin for the requested\r\n\t * allocation and start looking through that list.\r\n\t */\r\n\tunsigned int bucket_id = klmalloc_bin_size(size);\r\n\r\n\tif (bucket_id < BIG_BIN) {\r\n\t\t/*\r\n\t\t * Small bins.\r\n\t\t */\r\n\t\tklmalloc_bin_header * bin_header = klmalloc_list_head(&klmalloc_bin_head[bucket_id]);\r\n\t\tif (!bin_header) {\r\n\t\t\t/*\r\n\t\t\t * Grow the heap for the new bin.\r\n\t\t\t */\r\n\t\t\tbin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE);\r\n\t\t\tbin_header->bin_magic = BIN_MAGIC;\r\n\t\t\tassert((uintptr_t)bin_header % PAGE_SIZE == 0);\r\n\r\n\t\t\t/*\r\n\t\t\t * Set the head of the stack.\r\n\t\t\t */\r\n\t\t\tbin_header->head = (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header));\r\n\t\t\t/*\r\n\t\t\t * Insert the new bin at the front of\r\n\t\t\t * the list of bins for this size.\r\n\t\t\t */\r\n\t\t\tklmalloc_list_insert(&klmalloc_bin_head[bucket_id], bin_header);\r\n\t\t\t/*\r\n\t\t\t * Initialize the stack inside the bin.\r\n\t\t\t * The stack is initially full, with each\r\n\t\t\t * entry pointing to the next until the end\r\n\t\t\t * which points to NULL.\r\n\t\t\t */\r\n\t\t\tuintptr_t adj = SMALLEST_BIN_LOG + bucket_id;\r\n\t\t\tuintptr_t i, available = ((PAGE_SIZE - sizeof(klmalloc_bin_header)) >> adj) - 1;\r\n\r\n\t\t\tuintptr_t **base = bin_header->head;\r\n\t\t\tfor (i = 0; i < available; ++i) {\r\n\t\t\t\t/*\r\n\t\t\t\t * Our available memory is made into a stack, with each\r\n\t\t\t\t * piece of memory turned into a pointer to the next\r\n\t\t\t\t * available piece. When we want to get a new piece\r\n\t\t\t\t * of memory from this block, we just pop off a free\r\n\t\t\t\t * spot and give its address.\r\n\t\t\t\t */\r\n\t\t\t\tbase[i << bucket_id] = (uintptr_t *)&base[(i + 1) << bucket_id];\r\n\t\t\t}\r\n\t\t\tbase[available << bucket_id] = NULL;\r\n\t\t\tbin_header->size = bucket_id;\r\n\t\t}\r\n\t\tuintptr_t ** item = klmalloc_stack_pop(bin_header);\r\n\t\tif (klmalloc_stack_empty(bin_header)) {\r\n\t\t\tklmalloc_list_decouple(&(klmalloc_bin_head[bucket_id]),bin_header);\r\n\t\t}\r\n\t\treturn item;\r\n\t} else {\r\n\t\t/*\r\n\t\t * Round requested size to a set of pages, plus the header size.\r\n\t\t */\r\n\t\tuintptr_t pages = (size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1;\r\n\t\tklmalloc_big_bin_header * bin_header = (klmalloc_big_bin_header*)sbrk(PAGE_SIZE * pages);\r\n\t\tbin_header->bin_magic = BIN_MAGIC;\r\n\t\tassert((uintptr_t)bin_header % PAGE_SIZE == 0);\r\n\t\t/*\r\n\t\t * Give the header the remaining space.\r\n\t\t */\r\n\t\tbin_header->size = pages * PAGE_SIZE - sizeof(klmalloc_big_bin_header);\r\n\t\tassert((bin_header->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\t\t/*\r\n\t\t * Return the head of the block.\r\n\t\t */\r\n\t\tbin_header->head = NULL;\r\n\t\treturn (void*)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header));\r\n\t}\r\n}\r\n/* }}} */\r\n/* free() {{{ */\r\nstatic void klfree(void *ptr) {\r\n\t/*\r\n\t * C standard implementation: Do nothing when NULL is passed to free.\r\n\t */\r\n\tif (__builtin_expect(ptr == NULL, 0)) {\r\n\t\treturn;\r\n\t}\r\n\r\n\t/*\r\n\t * Woah, woah, hold on, was this a page-aligned block?\r\n\t */\r\n\tif ((uintptr_t)ptr % PAGE_SIZE == 0) {\r\n\t\t/*\r\n\t\t * Well howdy-do, it was.\r\n\t\t */\r\n\t\tptr = (void *)((uintptr_t)ptr - 1);\r\n\t}\r\n\r\n\t/*\r\n\t * Get our pointer to the head of this block by\r\n\t * page aligning it.\r\n\t */\r\n\tklmalloc_bin_header * header = (klmalloc_bin_header *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK);\r\n\tassert((uintptr_t)header % PAGE_SIZE == 0);\r\n\r\n\tif (header->bin_magic != BIN_MAGIC)\r\n\t\treturn;\r\n\r\n\t/*\r\n\t * For small bins, the bin number is stored in the size\r\n\t * field of the header. For large bins, the actual size\r\n\t * available in the bin is stored in this field. It's\r\n\t * easy to tell which is which, though.\r\n\t */\r\n\tuintptr_t bucket_id = header->size;\r\n\tif (bucket_id > (uintptr_t)NUM_BINS) {\r\n\t\tbucket_id = BIG_BIN;\r\n\t\tklmalloc_big_bin_header *bheader = (klmalloc_big_bin_header*)header;\r\n\t\t\r\n\t\tassert(bheader);\r\n\t\tassert(bheader->head == NULL);\r\n\t\tassert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0);\r\n\r\n\t\tchar * args[] = {(char*)header, (char*)(bheader->size + sizeof(klmalloc_big_bin_header))};\r\n\t\tsyscall_sysfunc(43, args);\r\n\t} else {\r\n\t\t/*\r\n\t\t * If the stack is empty, we are freeing\r\n\t\t * a block from a previously full bin.\r\n\t\t * Return it to the busy bins list.\r\n\t\t */\r\n\t\tif (klmalloc_stack_empty(header)) {\r\n\t\t\tklmalloc_list_insert(&klmalloc_bin_head[bucket_id], header);\r\n\t\t}\r\n\t\t/*\r\n\t\t * Push new space back into the stack.\r\n\t\t */\r\n\t\tklmalloc_stack_push(header, ptr);\r\n\t}\r\n}\r\n/* }}} */\r\n/* valloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klvalloc(uintptr_t size) {\r\n\t/*\r\n\t * Allocate a page-aligned block.\r\n\t * XXX: THIS IS HORRIBLY, HORRIBLY WASTEFUL!! ONLY USE THIS\r\n\t *      IF YOU KNOW WHAT YOU ARE DOING!\r\n\t */\r\n\tuintptr_t true_size = size + PAGE_SIZE - sizeof(klmalloc_big_bin_header); /* Here we go... */\r\n\tvoid * result = klmalloc(true_size);\r\n\tvoid * out = (void *)((uintptr_t)result + (PAGE_SIZE - sizeof(klmalloc_big_bin_header)));\r\n\tassert((uintptr_t)out % PAGE_SIZE == 0);\r\n\treturn out;\r\n}\r\n/* }}} */\r\n/* realloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klrealloc(void *ptr, uintptr_t size) {\r\n\t/*\r\n\t * C standard implementation: When NULL is passed to realloc,\r\n\t * simply malloc the requested size and return a pointer to that.\r\n\t */\r\n\tif (__builtin_expect(ptr == NULL, 0))\r\n\t\treturn klmalloc(size);\r\n\r\n\t/*\r\n\t * C standard implementation: For a size of zero, free the\r\n\t * pointer and return NULL, allocating no new memory.\r\n\t */\r\n\tif (__builtin_expect(size == 0, 0))\r\n\t{\r\n\t\tfree(ptr);\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\t/*\r\n\t * Find the bin for the given pointer\r\n\t * by aligning it to a page.\r\n\t */\r\n\tklmalloc_bin_header * header_old = (void *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK);\r\n\tif (header_old->bin_magic != BIN_MAGIC) {\r\n\t\tassert(0 && \"Bad magic on realloc.\");\r\n\t\treturn NULL;\r\n\t}\r\n\r\n\tuintptr_t old_size = header_old->size;\r\n\tif (old_size < (uintptr_t)BIG_BIN) {\r\n\t\t/*\r\n\t\t * If we are copying from a small bin,\r\n\t\t * we need to get the size of the bin\r\n\t\t * from its id.\r\n\t\t */\r\n\t\told_size = (1UL << (SMALLEST_BIN_LOG + old_size));\r\n\t}\r\n\r\n\tif (old_size == size) return ptr;\r\n\r\n\t/*\r\n\t * Reallocate more memory.\r\n\t */\r\n\tvoid * newptr = klmalloc(size);\r\n\tif (__builtin_expect(newptr != NULL, 1)) {\r\n\r\n\t\t/*\r\n\t\t * Copy the old value into the new value.\r\n\t\t * Be sure to only copy as much as was in\r\n\t\t * the old block.\r\n\t\t */\r\n\t\tmemcpy(newptr, ptr, (old_size < size) ? old_size : size);\r\n\t\tklfree(ptr);\r\n\t\treturn newptr;\r\n\t}\r\n\r\n\t/*\r\n\t * We failed to allocate more memory,\r\n\t * which means we're probably out.\r\n\t *\r\n\t * Bail and return NULL.\r\n\t */\r\n\treturn NULL;\r\n}\r\n/* }}} */\r\n/* calloc() {{{ */\r\nstatic void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size) {\r\n\t/*\r\n\t * Allocate memory and zero it before returning\r\n\t * a pointer to the newly allocated memory.\r\n\t * \r\n\t * Implemented by way of a simple malloc followed\r\n\t * by a memset to 0x00 across the length of the\r\n\t * requested memory chunk.\r\n\t */\r\n\r\n\tvoid *ptr = klmalloc(nmemb * size);\r\n\tif (ptr) memset(ptr,0x00,nmemb * size);\r\n\treturn ptr;\r\n}\r\n/* }}} */\r\n\r\n\r\n"
  },
  {
    "path": "libc/stdlib/mbstowcs.c",
    "content": "#include <stdlib.h>\n#include <wchar.h>\n#include <string.h>\n#include <stdio.h>\n\n#include <toaru/decodeutf8.h>\n\nstatic int to_eight(uint32_t codepoint, char * out) {\n\tmemset(out, 0x00, 7);\n\n\tif (codepoint < 0x0080) {\n\t\tout[0] = (char)codepoint;\n\t} else if (codepoint < 0x0800) {\n\t\tout[0] = 0xC0 | (codepoint >> 6);\n\t\tout[1] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x10000) {\n\t\tout[0] = 0xE0 | (codepoint >> 12);\n\t\tout[1] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[2] = 0x80 | (codepoint & 0x3F);\n\t} else if (codepoint < 0x200000) {\n\t\tout[0] = 0xF0 | (codepoint >> 18);\n\t\tout[1] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint) & 0x3F);\n\t} else if (codepoint < 0x4000000) {\n\t\tout[0] = 0xF8 | (codepoint >> 24);\n\t\tout[1] = 0x80 | (codepoint >> 18);\n\t\tout[2] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint) & 0x3F);\n\t} else {\n\t\tout[0] = 0xF8 | (codepoint >> 30);\n\t\tout[1] = 0x80 | ((codepoint >> 24) & 0x3F);\n\t\tout[2] = 0x80 | ((codepoint >> 18) & 0x3F);\n\t\tout[3] = 0x80 | ((codepoint >> 12) & 0x3F);\n\t\tout[4] = 0x80 | ((codepoint >> 6) & 0x3F);\n\t\tout[5] = 0x80 | ((codepoint) & 0x3F);\n\t}\n\n\treturn strlen(out);\n}\n\nsize_t mbstowcs(wchar_t *dest, const char *src, size_t n) {\n\tsize_t count = 0;\n\tuint32_t state = 0;\n\tuint32_t codepoint = 0;\n\n\twhile ((!dest || count < n) && *src) {\n\t\tif (!decode(&state, &codepoint, *(unsigned char *)src)) {\n\t\t\tif (dest) {\n\t\t\t\tdest[count] = codepoint;\n\t\t\t}\n\t\t\tcount++;\n\t\t\tcodepoint = 0;\n\t\t} else if (state == UTF8_REJECT) {\n\t\t\treturn (size_t)-1;\n\t\t}\n\t\tsrc++;\n\t}\n\n\tif (dest && !*src && count < n) {\n\t\tdest[count] = L'\\0';\n\t}\n\n\treturn count;\n}\n\nsize_t wcstombs(char * dest, const wchar_t *src, size_t n) {\n\tsize_t count = 0;\n\n\twhile ((!dest || count < n) && *src) {\n\t\tchar tmp[7];\n\t\tint size = to_eight(*src, tmp);\n\t\tif (count + size > n) return n;\n\t\tmemcpy(&dest[count], tmp, size);\n\t\tcount += size;\n\t\tsrc++;\n\t}\n\n\tif (dest && !*src && count < n) {\n\t\tdest[count] = '\\0';\n\t}\n\n\treturn count;\n}\n\n"
  },
  {
    "path": "libc/stdlib/mktemp.c",
    "content": "#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <fcntl.h>\n\nchar * mktemp(char * template) {\n\tif (strstr(template + strlen(template)-6, \"XXXXXX\") != template + strlen(template) - 6) {\n\t\terrno = EINVAL;\n\t\treturn NULL;\n\t}\n\tstatic int _i = 0;\n\tchar tmp[7] = {0};\n\tsprintf(tmp,\"%04d%02d\", getpid(), _i++);\n\tmemcpy(template + strlen(template) - 6, tmp, 6);\n\treturn template;\n}\n\nint mkstemp(char * template) {\n\tmktemp(template);\n\treturn open(template, O_RDWR | O_CREAT, 0600);\n}\n"
  },
  {
    "path": "libc/stdlib/putenv.c",
    "content": "#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <errno.h>\n\nextern char ** environ;\nextern int _environ_size;\n\nstatic int why_no_strnstr(char * a, char * b, int n) {\n\tfor (int i = 0; (i < n) && (a[i]) && (b[i]); ++i) {\n\t\tif (a[i] != b[i]) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\nint unsetenv(const char * str) {\n\tint last_index = -1;\n\tint found_index = -1;\n\tint len = strlen(str);\n\n\tif (!len) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tchar *c = strchrnul(str,'=');\n\tif (*c) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tfor (int i = 0; environ[i]; ++i) {\n\t\tif (found_index == -1 && (strstr(environ[i], str) == environ[i] && environ[i][len] == '=')) {\n\t\t\tfound_index = i;\n\t\t}\n\t\tlast_index = i;\n\t}\n\n\tif (found_index == -1) {\n\t\t/* not found = success */\n\t\treturn 0;\n\t}\n\n\tif (last_index == found_index) {\n\t\t/* Was last element */\n\t\tenviron[last_index] = NULL;\n\t\treturn 0;\n\t}\n\n\t/* Was not last element, swap ordering */\n\tenviron[found_index] = environ[last_index];\n\tenviron[last_index] = NULL;\n\treturn 0;\n}\n\nint putenv(char * string) {\n\tchar * c = strchrnul(string, '=');\n\n\tif (c == string) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tif (!*c) {\n\t\treturn unsetenv(string);\n\t}\n\n\tint s = c - string;\n\n\tint i;\n\tfor (i = 0; i < (_environ_size - 1) && environ[i]; ++i) {\n\t\tif (!why_no_strnstr(string, environ[i], s) && environ[i][s] == '=') {\n\t\t\tenviron[i] = string;\n\t\t\treturn 0;\n\t\t}\n\t}\n\t/* Not found */\n\tif (i == _environ_size - 1) {\n\t\tint _new_environ_size = _environ_size * 2;\n\t\tchar ** new_environ = malloc(sizeof(char*) * _new_environ_size);\n\t\tint j = 0;\n\t\twhile (j < _new_environ_size && environ[j]) {\n\t\t\tnew_environ[j] = environ[j];\n\t\t\tj++;\n\t\t}\n\t\twhile (j < _new_environ_size) {\n\t\t\tnew_environ[j] = NULL;\n\t\t\tj++;\n\t\t}\n\t\t_environ_size = _new_environ_size;\n\t\tenviron = new_environ;\n\t}\n\n\tenviron[i] = string;\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/stdlib/qsort.c",
    "content": "#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stddef.h>\n#include <string.h>\n#include <sys/types.h>\n\nextern char * _argv_0;\nextern int __libc_debug;\n\nstruct SortableArray {\n\tvoid * data;\n\tsize_t size;\n\tvoid * arg;\n\tint (*func)(const void *, const void *, void *);\n};\n\nstatic ssize_t partition(struct SortableArray * array, ssize_t lo, ssize_t hi) {\n\tchar pivot[array->size];\n\tmemcpy(pivot, (char *)array->data + array->size * hi, array->size);\n\tssize_t i = lo - 1;\n\tfor (ssize_t j = lo; j <= hi; ++j) {\n\t\tuint8_t * obj_j = (uint8_t *)array->data + array->size * j;\n\t\tif (array->func(obj_j, pivot, array->arg) <= 0) {\n\t\t\ti++;\n\t\t\tif (j != i) {\n\t\t\t\tuint8_t * obj_i = (uint8_t *)array->data + array->size * i;\n\t\t\t\tfor (size_t x = 0; x < array->size; ++x) {\n\t\t\t\t\tuint8_t tmp = obj_i[x];\n\t\t\t\t\tobj_i[x] = obj_j[x];\n\t\t\t\t\tobj_j[x] = tmp;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn i;\n}\n\nstatic void quicksort(struct SortableArray * array, ssize_t lo, ssize_t hi) {\n\tif (lo >= 0 && hi >= 0) {\n\t\tif (lo < hi) {\n\t\t\tssize_t pivot = partition(array, lo, hi);\n\t\t\tquicksort(array, lo, pivot - 1);\n\t\t\tquicksort(array, pivot + 1, hi);\n\t\t}\n\t}\n}\n\nstatic int sort_caller(const void * a, const void * b, void * arg) {\n\tint (*func)(const void *, const void *) = arg;\n\treturn func(a,b);\n}\n\nvoid qsort_r(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void * arg) {\n\tif (nmemb < 2) return;\n\tif (!size) return;\n\tstruct SortableArray array = {base,size,arg,compar};\n\tquicksort(&array, 0, nmemb - 1);\n}\n\nvoid qsort(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) {\n\tqsort_r(base,nmemb,size,sort_caller,compar);\n}\n"
  },
  {
    "path": "libc/stdlib/rand.c",
    "content": "#include <stdint.h>\n#include <stdlib.h>\n#include <math.h>\n\nstatic uint32_t x = 123456789;\nstatic uint32_t y = 362436069;\nstatic uint32_t z = 521288629;\nstatic uint32_t w = 88675123;\n\nint rand(void) {\n\tuint32_t t;\n\n\tt = x ^ (x << 11);\n\tx = y; y = z; z = w;\n\tw = w ^ (w >> 19) ^ t ^ (t >> 8);\n\n\treturn (w & RAND_MAX);\n}\n\nvoid srand(unsigned int seed) {\n\tx = 123456789  ^ (seed << 16) ^ (seed >> 16);\n\ty = 362436069;\n\tz = 521288629;\n\tw = 88675123;\n}\n"
  },
  {
    "path": "libc/stdlib/realpath.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <limits.h>\n#include <unistd.h>\n#include <errno.h>\n\n#ifndef __toaru__\n#undef realpath\n#define realpath _realpath_toaru\n#endif\n\n#define SYMLINK_MAX 5\n\nstatic void _append_dir(char *out, char *element) {\n\tstrcat(out,\"/\");\n\tstrcat(out,element);\n}\n\nstatic void _remove_last(char * out) {\n\tchar * last = strrchr(out,'/');\n\tif (last) {\n\t\t*last = '\\0';\n\t}\n}\n\n/**\n * This is accurate to how we handle paths in ToaruOS.\n * It's not correct for real symbolic link handling,\n * so it needs some work for that.\n */\nchar *realpath(const char *path, char *resolved_path) {\n\t/*\n\t * Basically the same as what we do in the kernel for canonicalize_path\n\t * but slightly more complicated because of the requirement to check\n\t * symlinks... this is going to get interesting.\n\t */\n\tif (!path) {\n\t\terrno = -EINVAL;\n\t\treturn NULL;\n\t}\n\n\tif (!resolved_path) {\n\t\tresolved_path = malloc(PATH_MAX+1);\n\t}\n\n\t/* If we're lucky, we can do this with no allocations, so let's start here... */\n\tchar working_path[PATH_MAX+1];\n\tmemcpy(working_path, path, strlen(path)+1);\n\n\t*resolved_path = 0;\n\n\tif (working_path[0] != '/') {\n\t\t/* Begin by retreiving the current working directory */\n\t\tchar cwd[PATH_MAX+1];\n\t\tif (!getcwd(cwd, PATH_MAX)) {\n\t\t\t/* Not actually sure if this is the right choice for this, but whatever. */\n\t\t\terrno = -ENOTDIR;\n\t\t\treturn NULL;\n\t\t}\n\n\t\tchar *save;\n\t\tchar *tok = strtok_r(cwd,\"/\",&save);\n\t\tdo {\n\t\t\t_append_dir(resolved_path, tok);\n\t\t} while ((tok = strtok_r(NULL,\"/\",&save)));\n\t}\n\n\tchar *save;\n\tchar *tok = strtok_r(working_path,\"/\",&save);\n\tif (tok) {\n\t\tdo {\n\t\t\tif (!strcmp(tok,\".\")) continue;\n\t\t\tif (!strcmp(tok,\"..\")) {\n\t\t\t\t_remove_last(resolved_path);\n\t\t\t\tcontinue;\n\t\t\t} else {\n\t\t\t\t_append_dir(resolved_path, tok);\n\t\t\t}\n\t\t} while ((tok = strtok_r(NULL,\"/\",&save)));\n\t}\n\n\tif (resolved_path[0] == '\\0') {\n\t\tstrcat(resolved_path,\"/\");\n\t}\n\n\treturn resolved_path;\n}\n\n#ifndef __toaru__\nint main(int argc, char * argv[]) {\n\tchar tmp[PATH_MAX+1];\n\n\tif (!realpath(argv[1], tmp)) {\n\t\tfprintf(stderr, \"invalid path, errno=%d\\n\", errno);\n\t\treturn 1;\n\t}\n\n\tfprintf(stderr, \"%s=%s\\n\", argv[1], tmp);\n\treturn 0;\n}\n#endif\n"
  },
  {
    "path": "libc/stdlib/setenv.c",
    "content": "#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n\nint setenv(const char *name, const char *value, int overwrite) {\n\tif (!overwrite && getenv(name)) return 0;\n\n\tif (!name || !*name) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tchar * c = strchrnul(name, '=');\n\n\tif (*c) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tchar * tmp = malloc(strlen(name) + strlen(value) + 2);\n\t*tmp = '\\0';\n\tstrcat(tmp, name);\n\tstrcat(tmp, \"=\");\n\tstrcat(tmp, value);\n\treturn putenv(tmp);\n}\n"
  },
  {
    "path": "libc/stdlib/strtod.c",
    "content": "#include <stdlib.h>\n#include <math.h>\n#include <stdio.h>\n\ndouble strtod(const char *nptr, char **endptr) {\n\tint sign = 1;\n\tif (*nptr == '-') {\n\t\tsign = -1;\n\t\tnptr++;\n\t}\n\n\tlong long decimal_part = 0;\n\n\twhile (*nptr && *nptr != '.') {\n\t\tif (*nptr < '0' || *nptr > '9') {\n\t\t\tbreak;\n\t\t}\n\t\tdecimal_part *= 10LL;\n\t\tdecimal_part += (long long)(*nptr - '0');\n\t\tnptr++;\n\t}\n\n\tdouble sub_part = 0;\n\tdouble multiplier = 0.1;\n\n\tif (*nptr == '.') {\n\t\tnptr++;\n\n\t\twhile (*nptr) {\n\t\t\tif (*nptr < '0' || *nptr > '9') {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tsub_part += multiplier * (*nptr - '0');\n\t\t\tmultiplier *= 0.1;\n\t\t\tnptr++;\n\t\t}\n\t}\n\n\tdouble expn = (double)sign;\n\n\tif (*nptr == 'e' || *nptr == 'E') {\n\t\tnptr++;\n\n\t\tint exponent_sign = 1;\n\n\t\tif (*nptr == '+') {\n\t\t\tnptr++;\n\t\t} else if (*nptr == '-') {\n\t\t\texponent_sign = -1;\n\t\t\tnptr++;\n\t\t}\n\n\t\tint exponent = 0;\n\n\t\twhile (*nptr) {\n\t\t\tif (*nptr < '0' || *nptr > '9') {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\texponent *= 10;\n\t\t\texponent += (*nptr - '0');\n\t\t\tnptr++;\n\t\t}\n\n\t\texpn = pow(10.0,(double)(exponent * exponent_sign));\n\t}\n\n\tif (endptr) {\n\t\t*endptr = (char *)nptr;\n\t}\n\tdouble result = ((double)decimal_part + sub_part) * expn;\n\treturn result;\n}\n\nfloat strtof(const char *nptr, char **endptr) {\n\treturn strtod(nptr,endptr);\n}\n"
  },
  {
    "path": "libc/stdlib/strtoul.c",
    "content": "#include <stdlib.h>\n#include <ctype.h>\n#include <limits.h>\n#include <errno.h>\n\nstatic int is_valid(int base, char c) {\n\tif (c < '0') return 0;\n\tif (base <= 10) {\n\t\treturn c < ('0' + base);\n\t}\n\n\tif (c >= 'a' && c < 'a' + (base - 10)) return 1;\n\tif (c >= 'A' && c < 'A' + (base - 10)) return 1;\n\tif (c >= '0' && c <= '9') return 1;\n\treturn 0;\n}\n\nstatic int convert_digit(char c) {\n\tif (c >= '0' && c <= '9') {\n\t\treturn c - '0';\n\t}\n\tif (c >= 'a' && c <= 'z') {\n\t\treturn c - 'a' + 0xa;\n\t}\n\tif (c >= 'A' && c <= 'Z') {\n\t\treturn c - 'A' + 0xa;\n\t}\n\treturn 0;\n}\n\n#define strtox(max, type) \\\n\tif (base < 0 || base == 1 || base > 36) { \\\n\t\terrno = EINVAL; \\\n\t\treturn max; \\\n\t} \\\n\twhile (*nptr && isspace(*nptr)) nptr++; \\\n\tint sign = 1; \\\n\tif (*nptr == '-') { \\\n\t\tsign = -1; \\\n\t\tnptr++; \\\n\t} else if (*nptr == '+') { \\\n\t\tnptr++; \\\n\t} \\\n\tif (base == 16) { \\\n\t\tif (*nptr == '0') { \\\n\t\t\tnptr++; \\\n\t\t\tif (*nptr == 'x') { \\\n\t\t\t\tnptr++; \\\n\t\t\t} \\\n\t\t} \\\n\t} \\\n\tif (base == 0) { \\\n\t\tif (*nptr == '0') { \\\n\t\t\tbase = 8; \\\n\t\t\tnptr++; \\\n\t\t\tif (*nptr == 'x') { \\\n\t\t\t\tbase = 16; \\\n\t\t\t\tnptr++; \\\n\t\t\t} \\\n\t\t} else { \\\n\t\t\tbase = 10; \\\n\t\t} \\\n\t} \\\n\ttype val = 0; \\\n\twhile (is_valid(base, *nptr)) { \\\n\t\tval *= base; \\\n\t\tval += convert_digit(*nptr); \\\n\t\tnptr++; \\\n\t} \\\n\tif (endptr) { \\\n\t\t*endptr = (char *)nptr; \\\n\t} \\\n\tif (sign == -1) { \\\n\t\treturn -val; \\\n\t} else { \\\n\t\treturn val; \\\n\t}\n\nunsigned long int strtoul(const char *nptr, char **endptr, int base) {\n\tstrtox(ULONG_MAX, unsigned long int);\n}\n\nunsigned long long int strtoull(const char *nptr, char **endptr, int base) {\n\tstrtox(ULLONG_MAX, unsigned long int);\n}\n\nlong int strtol(const char *nptr, char **endptr, int base) {\n\tstrtox(LONG_MAX, unsigned long int);\n}\n\nlong long int strtoll(const char *nptr, char **endptr, int base) {\n\tstrtox(LLONG_MAX, unsigned long long int);\n}\n\n"
  },
  {
    "path": "libc/stdlib/system.c",
    "content": "#include <stdlib.h>\n#include <unistd.h>\n#include <wait.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n\nint system(const char * command) {\n\tchar * args[] = {\n\t\t\"/bin/sh\",\n\t\t\"-c\",\n\t\t(char *)command,\n\t\tNULL,\n\t};\n\tpid_t pid = fork();\n\tif (!pid) {\n\t\texecvp(args[0], args);\n\t\texit(1);\n\t\t__builtin_unreachable(); /* With -ffreestanding, gcc doesn't realize exit() doesn't return. */\n\t} else {\n\t\tint status;\n\t\twaitpid(pid, &status, 0);\n\t\treturn WEXITSTATUS(status);\n\t}\n}\n"
  },
  {
    "path": "libc/string/memmove.c",
    "content": "#include <stddef.h>\n#include <stdint.h>\n#include <string.h>\n\nvoid * memmove(void * dest, const void * src, size_t n) {\n\tchar * d = dest;\n\tconst char * s = src;\n\n\tif (d==s) {\n\t\treturn d;\n\t}\n\n\tif (s+n <= d || d+n <= s) {\n\t\treturn memcpy(d, s, n);\n\t}\n\n\tif (d<s) {\n\t\tif ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) {\n\t\t\twhile ((uintptr_t)d % sizeof(size_t)) {\n\t\t\t\tif (!n--) {\n\t\t\t\t\treturn dest;\n\t\t\t\t}\n\t\t\t\t*d++ = *s++;\n\t\t\t}\n\t\t\tfor (; n >= sizeof(size_t); n -= sizeof(size_t), d += sizeof(size_t), s += sizeof(size_t)) {\n\t\t\t\t*(size_t *)d = *(size_t *)s;\n\t\t\t}\n\t\t}\n\t\tfor (; n; n--) {\n\t\t\t*d++ = *s++;\n\t\t}\n\t} else {\n\t\tif ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) {\n\t\t\twhile ((uintptr_t)(d+n) % sizeof(size_t)) {\n\t\t\t\tif (!n--) {\n\t\t\t\t\treturn dest;\n\t\t\t\t}\n\t\t\t\td[n] = s[n];\n\t\t\t}\n\t\t\twhile (n >= sizeof(size_t)) {\n\t\t\t\tn -= sizeof(size_t);\n\t\t\t\t*(size_t *)(d+n) = *(size_t *)(s+n);\n\t\t\t}\n\t\t}\n\t\twhile (n) {\n\t\t\tn--;\n\t\t\td[n] = s[n];\n\t\t}\n\t}\n\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/string/str.c",
    "content": "#include <string.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <limits.h>\n#include <ctype.h>\n\n#define MIN(A, B) ((A) < (B) ? (A) : (B))\n#define MAX(A, B) ((A) > (B) ? (A) : (B))\n\n#define ALIGN (sizeof(size_t))\n#define ONES ((size_t)-1/UCHAR_MAX)\n#define HIGHS (ONES * (UCHAR_MAX/2+1))\n#define HASZERO(X) (((X)-ONES) & ~(X) & HIGHS)\n\n#define BITOP(A, B, OP) \\\n ((A)[(size_t)(B)/(8*sizeof *(A))] OP (size_t)1<<((size_t)(B)%(8*sizeof *(A))))\n\nint memcmp(const void * vl, const void * vr, size_t n) {\n\tconst unsigned char *l = vl;\n\tconst unsigned char *r = vr;\n\tfor (; n && *l == *r; n--, l++, r++);\n\treturn n ? *l-*r : 0;\n}\n\nvoid * memchr(const void * src, int c, size_t n) {\n\tconst unsigned char * s = src;\n\tc = (unsigned char)c;\n\tfor (; ((uintptr_t)s & (ALIGN - 1)) && n && *s != c; s++, n--);\n\tif (n && *s != c) {\n\t\tconst size_t * w;\n\t\tsize_t k = ONES * c;\n\t\tfor (w = (const void *)s; n >= sizeof(size_t) && !HASZERO(*w^k); w++, n -= sizeof(size_t));\n\t\tfor (s = (const void *)w; n && *s != c; s++, n--);\n\t}\n\treturn n ? (void *)s : 0;\n}\n\nvoid * memrchr(const void * m, int c, size_t n) {\n\tconst unsigned char * s = m;\n\tc = (unsigned char)c;\n\twhile (n--) {\n\t\tif (s[n] == c) {\n\t\t\treturn (void*)(s+n);\n\t\t}\n\t}\n\treturn 0;\n}\n\nint strcmp(const char * l, const char * r) {\n\tfor (; *l == *r && *l; l++, r++);\n\treturn *(unsigned char *)l - *(unsigned char *)r;\n}\n\nint strcoll(const char * s1, const char * s2) {\n\treturn strcmp(s1,s2); /* TODO locales */\n}\n\nsize_t strlen(const char * s) {\n\tconst char * a = s;\n\tconst size_t * w;\n\tfor (; (uintptr_t)s % ALIGN; s++) {\n\t\tif (!*s) {\n\t\t\treturn s-a;\n\t\t}\n\t}\n\tfor (w = (const void *)s; !HASZERO(*w); w++);\n\tfor (s = (const void *)w; *s; s++);\n\treturn s-a;\n}\n\nchar * strdup(const char * s) {\n\tsize_t l = strlen(s);\n\treturn memcpy(malloc(l+1), s, l+1);\n}\n\nchar * stpcpy(char * restrict d, const char * restrict s) {\n\tsize_t * wd;\n\tconst size_t * ws;\n\n\tif ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) {\n\t\tfor (; (uintptr_t)s % ALIGN; s++, d++) {\n\t\t\tif (!(*d = *s)) {\n\t\t\t\treturn d;\n\t\t\t}\n\t\t}\n\t\twd = (void *)d;\n\t\tws = (const void *)s;\n\t\tfor (; !HASZERO(*ws); *wd++ = *ws++);\n\t\td = (void *)wd;\n\t\ts = (const void *)ws;\n\t}\n\n\tfor (; (*d=*s); s++, d++);\n\n\treturn d;\n}\n\nchar * strcpy(char * restrict dest, const char * restrict src) {\n\tchar * out = dest;\n\tfor (; (*dest=*src); src++, dest++);\n\treturn out;\n}\n\nsize_t strspn(const char * s, const char * c) {\n\tconst char * a = s;\n\tsize_t byteset[32/sizeof(size_t)] = { 0 };\n\n\tif (!c[0]) {\n\t\treturn 0;\n\t}\n\tif (!c[1]) {\n\t\tfor (; *s == *c; s++);\n\t\treturn s-a;\n\t}\n\n\tfor (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++);\n\tfor (; *s && BITOP(byteset, *(unsigned char *)s, &); s++);\n\n\treturn s-a;\n}\n\nchar * strchrnul(const char * s, int c) {\n\tsize_t * w;\n\tsize_t k;\n\n\tc = (unsigned char)c;\n\tif (!c) {\n\t\treturn (char *)s + strlen(s);\n\t}\n\n\tfor (; (uintptr_t)s % ALIGN; s++) {\n\t\tif (!*s || *(unsigned char *)s == c) {\n\t\t\treturn (char *)s;\n\t\t}\n\t}\n\n\tk = ONES * c;\n\tfor (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++);\n\tfor (s = (void *)w; *s && *(unsigned char *)s != c; s++);\n\treturn (char *)s;\n}\n\nchar * strchr(const char * s, int c) {\n\tchar *r = strchrnul(s, c);\n\treturn *(unsigned char *)r == (unsigned char)c ? r : 0;\n}\n\nchar * strrchr(const char * s, int c) {\n\treturn memrchr(s, c, strlen(s) + 1);\n}\n\nsize_t strcspn(const char * s, const char * c) {\n\tconst char *a = s;\n\tif (c[0] && c[1]) {\n\t\tsize_t byteset[32/sizeof(size_t)] = { 0 };\n\t\tfor (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++);\n\t\tfor (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++);\n\t\treturn s-a;\n\t}\n\treturn strchrnul(s, *c)-a;\n}\n\nchar * strpbrk(const char * s, const char * b) {\n\ts += strcspn(s, b);\n\treturn *s ? (char *)s : 0;\n}\n\nstatic char *strstr_2b(const unsigned char * h, const unsigned char * n) {\n\tuint16_t nw = n[0] << 8 | n[1];\n\tuint16_t hw = h[0] << 8 | h[1];\n\tfor (h++; *h && hw != nw; hw = hw << 8 | *++h);\n\treturn *h ? (char *)h-1 : 0;\n}\n\nstatic char *strstr_3b(const unsigned char * h, const unsigned char * n) {\n\tuint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8;\n\tuint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8;\n\tfor (h += 2; *h && hw != nw; hw = (hw|*++h) << 8);\n\treturn *h ? (char *)h-2 : 0;\n}\n\nstatic char *strstr_4b(const unsigned char * h, const unsigned char * n) {\n\tuint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8 | n[3];\n\tuint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3];\n\tfor (h += 3; *h && hw != nw; hw = hw << 8 | *++h);\n\treturn *h ? (char *)h-3 : 0;\n}\n\nstatic char *strstr_twoway(const unsigned char * h, const unsigned char * n) {\n\tsize_t mem;\n\tsize_t mem0;\n\tsize_t byteset[32 / sizeof(size_t)] = { 0 };\n\tsize_t shift[256];\n\tsize_t l;\n\n\t/* Computing length of needle and fill shift table */\n\tfor (l = 0; n[l] && h[l]; l++) {\n\t\tBITOP(byteset, n[l], |=);\n\t\tshift[n[l]] = l+1;\n\t}\n\n\tif (n[l]) {\n\t\treturn 0; /* hit the end of h */\n\t}\n\n\t/* Compute maximal suffix */\n\tsize_t ip = -1;\n\tsize_t jp = 0;\n\tsize_t k = 1;\n\tsize_t p = 1;\n\twhile (jp+k<l) {\n\t\tif (n[ip+k] == n[jp+k]) {\n\t\t\tif (k == p) {\n\t\t\t\tjp += p;\n\t\t\t\tk = 1;\n\t\t\t} else {\n\t\t\t\tk++;\n\t\t\t}\n\t\t} else if (n[ip+k] > n[jp+k]) {\n\t\t\tjp += k;\n\t\t\tk = 1;\n\t\t\tp = jp - ip;\n\t\t} else {\n\t\t\tip = jp++;\n\t\t\tk = p = 1;\n\t\t}\n\t}\n\tsize_t ms = ip;\n\tsize_t p0 = p;\n\n\t/* And with the opposite comparison */\n\tip = -1;\n\tjp = 0;\n\tk = p = 1;\n\twhile (jp+k<l) {\n\t\tif (n[ip+k] == n[jp+k]) {\n\t\t\tif (k == p) {\n\t\t\t\tjp += p;\n\t\t\t\tk = 1;\n\t\t\t} else {\n\t\t\t\tk++;\n\t\t\t}\n\t\t} else if (n[ip+k] < n[jp+k]) {\n\t\t\tjp += k;\n\t\t\tk = 1;\n\t\t\tp = jp - ip;\n\t\t} else {\n\t\t\tip = jp++;\n\t\t\tk = p = 1;\n\t\t}\n\t}\n\tif (ip+1 > ms+1) {\n\t\tms = ip;\n\t} else {\n\t\tp = p0;\n\t}\n\n\t/* Periodic needle? */\n\tif (memcmp(n, n+p, ms+1)) {\n\t\tmem0 = 0;\n\t\tp = MAX(ms, l-ms-1) + 1;\n\t} else {\n\t\tmem0 = l-p;\n\t}\n\tmem = 0;\n\n\t/* Initialize incremental end-of-haystack pointer */\n\tconst unsigned char * z = h;\n\n\t/* Search loop */\n\tfor (;;) {\n\t\t/* Update incremental end-of-haystack pointer */\n\t\tif ((size_t)(z-h) < l) {\n\t\t\t/* Fast estimate for MIN(l,63) */\n\t\t\tsize_t grow = l | 63;\n\t\t\tconst unsigned char *z2 = memchr(z, 0, grow);\n\t\t\tif (z2) {\n\t\t\t\tz = z2;\n\t\t\t\tif ((size_t)(z-h) < l) {\n\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tz += grow;\n\t\t\t}\n\t\t}\n\n\t\t/* Check last byte first; advance by shift on mismatch */\n\t\tif (BITOP(byteset, h[l-1], &)) {\n\t\t\tk = l-shift[h[l-1]];\n\t\t\tif (k) {\n\t\t\t\tif (mem0 && mem && k < p) k = l-p;\n\t\t\t\th += k;\n\t\t\t\tmem = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else {\n\t\t\th += l;\n\t\t\tmem = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Compare right half */\n\t\tfor (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++);\n\t\tif (n[k]) {\n\t\t\th += k-ms;\n\t\t\tmem = 0;\n\t\t\tcontinue;\n\t\t}\n\t\t/* Compare left half */\n\t\tfor (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);\n\t\tif (k <= mem) {\n\t\t\treturn (char *)h;\n\t\t}\n\t\th += p;\n\t\tmem = mem0;\n\t}\n}\n\nchar *strstr(const char * h, const char * n) {\n\t/* Return immediately on empty needle */\n\tif (!n[0]) {\n\t\treturn (char *)h;\n\t}\n\n\t/* Use faster algorithms for short needles */\n\th = strchr(h, *n);\n\tif (!h || !n[1]) {\n\t\treturn (char *)h;\n\t}\n\n\tif (!h[1]) return 0;\n\tif (!n[2]) return strstr_2b((void *)h, (void *)n);\n\tif (!h[2]) return 0;\n\tif (!n[3]) return strstr_3b((void *)h, (void *)n);\n\tif (!h[3]) return 0;\n\tif (!n[4]) return strstr_4b((void *)h, (void *)n);\n\n\t/* Two-way on large needles */\n\treturn strstr_twoway((void *)h, (void *)n);\n}\n\nlong atol(const char * s) {\n\tint n = 0;\n\tint neg = 0;\n\twhile (isspace(*s)) {\n\t\ts++;\n\t}\n\tswitch (*s) {\n\t\tcase '-':\n\t\t\tneg = 1; /* fallthrough */\n\t\tcase '+':\n\t\t\ts++;\n\t}\n\twhile (isdigit(*s)) {\n\t\tn = 10*n - (*s++ - '0');\n\t}\n\t/* The sign order may look incorrect here but this is correct as n is calculated\n\t * as a negative number to avoid overflow on INT_MAX.\n\t */\n\treturn neg ? n : -n;\n}\n\nint atoi(const char * s) {\n\tint n = 0;\n\tint neg = 0;\n\twhile (isspace(*s)) {\n\t\ts++;\n\t}\n\tswitch (*s) {\n\t\tcase '-':\n\t\t\tneg = 1; /* fallthrough */\n\t\tcase '+':\n\t\t\ts++;\n\t}\n\twhile (isdigit(*s)) {\n\t\tn = 10*n - (*s++ - '0');\n\t}\n\t/* The sign order may look incorrect here but this is correct as n is calculated\n\t * as a negative number to avoid overflow on INT_MAX.\n\t */\n\treturn neg ? n : -n;\n}\n\nsize_t lfind(const char * str, const char accept) {\n\treturn (size_t)strchr(str, accept);\n}\n\nsize_t rfind(const char * str, const char accept) {\n\treturn (size_t)strrchr(str, accept);\n}\n\nchar * strtok_r(char * str, const char * delim, char ** saveptr) {\n\tchar * token;\n\tif (str == NULL) {\n\t\tstr = *saveptr;\n\t}\n\tstr += strspn(str, delim);\n\tif (*str == '\\0') {\n\t\t*saveptr = str;\n\t\treturn NULL;\n\t}\n\ttoken = str;\n\tstr = strpbrk(token, delim);\n\tif (str == NULL) {\n\t\t*saveptr = (char *)lfind(token, '\\0');\n\t} else {\n\t\t*str = '\\0';\n\t\t*saveptr = str + 1;\n\t}\n\treturn token;\n}\n\nchar * strtok(char * str, const char * delim) {\n\tstatic char * saveptr = NULL;\n\tif (str) {\n\t\t\tsaveptr = NULL;\n\t}\n\treturn strtok_r(str, delim, &saveptr);\n}\n\nchar * strcat(char *dest, const char *src) {\n\tchar * end = dest;\n\twhile (*end != '\\0') {\n\t\t++end;\n\t}\n\twhile (*src) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t}\n\t*end = '\\0';\n\treturn dest;\n}\n\nchar * strncat(char *dest, const char *src, size_t n) {\n\tchar * end = dest;\n\twhile (*end != '\\0') {\n\t\t++end;\n\t}\n\tsize_t i = 0;\n\twhile (*src && i < n) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t\ti++;\n\t}\n\t*end = '\\0';\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/string/strerror.c",
    "content": "#include <string.h>\n#include <errno.h>\n#include <stdio.h>\n\nstatic char * _error_strings[] = {\n\t[EPERM] = \"Operation not permitted\",\n\t[ENOENT] = \"No such file or directory\",\n\t[ESRCH] = \"No such process\",\n\t[EINTR] = \"Interrupted system call\",\n\t[EIO] = \"Input/Output error\",\n\t[ENXIO] = \"No such device or address\",\n\t[E2BIG] = \"Argument list too long\",\n\t[ENOEXEC] = \"Exec format error\",\n\t[EBADF] = \"Bad file descriptor\",\n\t[ECHILD] = \"No child processes\",\n\t[EAGAIN] = \"Resource temporarily unavailable\",\n\t[ENOMEM] = \"Cannot allocate memory\",\n\t[EACCES] = \"Permission denied\",\n\t[EFAULT] = \"Bad address\",\n\t[ENOTBLK] = \"Block device required\",\n\t[EBUSY] = \"Device or resource busy\",\n\t[EEXIST] = \"File exists\",\n\t[EXDEV] = \"Invalid cross-device link\",\n\t[ENODEV] = \"No such device\",\n\t[ENOTDIR] = \"Not a directory\",\n\t[EISDIR] = \"Is a directory\",\n\t[EINVAL] = \"Invalid argument\",\n\t[ENFILE] = \"Too many open files in system\",\n\t[EMFILE] = \"Too many open files\",\n\t[ENOTTY] = \"Inappropriate ioctl for device\",\n\t[ETXTBSY] = \"Text file busy\",\n\t[EFBIG] = \"File too large\",\n\t[ENOSPC] = \"No space left on device\",\n\t[ESPIPE] = \"Illegal seek\",\n\t[EROFS] = \"Read only file system\",\n\t[EMLINK] = \"Too many links\",\n\t[EPIPE] = \"Broken pipe\",\n\t[EDOM] = \"Math arg out of domain of func\",\n\t[ERANGE] = \"Math result not representable\",\n\t[ENOMSG] = \"No message of desired type\",\n\t[EIDRM] = \"Identifier removed\",\n\t[ECHRNG] = \"Channel number out of range\",\n\t[EL2NSYNC] = \"Level 2 not synchronized\",\n\t[EL3HLT] = \"Level 3 halted\",\n\t[EL3RST] = \"Level 3 reset\",\n\t[ELNRNG] = \"Link number out of range\",\n\t[EUNATCH] = \"Protocol driver not attached\",\n\t[ENOCSI] = \"No CSI structure available\",\n\t[EL2HLT] = \"Level 2 halted\",\n\t[EDEADLK] = \"Deadlock condition\",\n\t[ENOLCK] = \"No record locks available\",\n\t[EBADE] = \"Invalid exchange\",\n\t[EBADR] = \"Invalid request descriptor\",\n\t[EXFULL] = \"Exchange full\",\n\t[ENOANO] = \"No anode\",\n\t[EBADRQC] = \"Invalid request code\",\n\t[EBADSLT] = \"Invalid slot\",\n\t[EDEADLOCK] = \"File locking deadlock error\",\n\t[EBFONT] = \"Bad font file format\",\n\t[ENOSTR] = \"Device not a stream\",\n\t[ENODATA] = \"No data available\",\n\t[ETIME] = \"Timer expired\",\n\t[ENOSR] = \"Out of streams resources\",\n\t[ENONET] = \"Machine is not on the network\",\n\t[ENOPKG] = \"Package not installed\",\n\t[EREMOTE] = \"The object is remote\",\n\t[ENOLINK] = \"The link has been severed\",\n\t[EADV] = \"Advertise error\",\n\t[ESRMNT] = \"Srmount error\",\n\t[ECOMM] = \"Communication error on send\",\n\t[EPROTO] = \"Protocol error\",\n\t[EMULTIHOP] = \"Multihop attempted\",\n\t[ELBIN] = \"Inode is remote\",\n\t[EDOTDOT] = \"Cross mount point\",\n\t[EBADMSG] = \"Bad message\",\n\t[EFTYPE] = \"Inappropriate file type or format\",\n\t[ENOTUNIQ] = \"Name not unique\",\n\t[EBADFD] = \"File descriptor in bad state\",\n\t[EREMCHG] = \"Remote address changed\",\n\t[ELIBACC] = \"Can not access a needed shared library\",\n\t[ELIBBAD] = \"Accessing a corrupted shared library\",\n\t[ELIBSCN] = \".lib section in a.out corrupted\",\n\t[ELIBMAX] = \"Attempting to link in too many libraries\",\n\t[ELIBEXEC] = \"Attempting to exec a shared library\",\n\t[ENOSYS] = \"Function not implemented\",\n\t[ENOTEMPTY] = \"Directory not empty\",\n\t[ENAMETOOLONG] = \"File or path name too long\",\n\t[ELOOP] = \"Too many symbolic links\",\n\t[EOPNOTSUPP] = \"Operation not supported on transport endpoint\",\n\t[EPFNOSUPPORT] = \"Protocol family not supported\",\n\t[ECONNRESET] = \"Connection reset by peer\",\n\t[ENOBUFS] = \"No buffer space available\",\n\t[EAFNOSUPPORT] = \"Address family not supported by protocol family\",\n\t[EPROTOTYPE] = \"Protocol wrong type for socket\",\n\t[ENOTSOCK] = \"Socket operation on non-socket\",\n\t[ENOPROTOOPT] = \"Protocol not available\",\n\t[ESHUTDOWN] = \"Can't send after socket shutdown\",\n\t[ECONNREFUSED] = \"Connection refused\",\n\t[EADDRINUSE] = \"Address already in use\",\n\t[ECONNABORTED] = \"Connection aborted\",\n\t[ENETUNREACH] = \"Network is unreachable\",\n\t[ENETDOWN] = \"Network interface is not configured\",\n\t[ETIMEDOUT] = \"Connection timed out\",\n\t[EHOSTDOWN] = \"Host is down\",\n\t[EHOSTUNREACH] = \"No route to host\",\n\t[EINPROGRESS] = \"Connection already in progress\",\n\t[EALREADY] = \"Socket already connected\",\n\t[EDESTADDRREQ] = \"Destination address required\",\n\t[EMSGSIZE] = \"Message too long\",\n\t[EPROTONOSUPPORT] = \"Unknown protocol\",\n\t[ESOCKTNOSUPPORT] = \"Socket type not supported\",\n\t[EADDRNOTAVAIL] = \"Address not available\",\n\t[EISCONN] = \"Socket is already connected\",\n\t[ENOTCONN] = \"Socket is not connected\",\n\t[ENOTSUP] = \"Operation not supported\",\n\t[EOVERFLOW] = \"Value too large for defined data type\",\n\t[ECANCELED] = \"Operation canceled\",\n\t[ENOTRECOVERABLE] = \"State not recoverable\",\n\t[EOWNERDEAD] = \"Previous owner died\",\n\t[ESTRPIPE] = \"Streams pipe error\",\n\t[ERESTARTSYS] = \"Restartable system call was interrupted\",\n};\n\nstatic char _error_string[100];\nchar * strerror(int errnum) {\n\tchar * str = (errnum >= 0 && (size_t)errnum < sizeof(_error_strings) / sizeof(*_error_strings)) ? _error_strings[errnum] : NULL;\n\tif (!str) {\n\t\tsnprintf(_error_string, 100, \"%d\", errnum);\n\t\treturn _error_string;\n\t}\n\treturn str;\n}\n"
  },
  {
    "path": "libc/string/strncmp.c",
    "content": "#include <string.h>\n\nint strncmp(const char *s1, const char *s2, size_t n) {\n\tif (n == 0) return 0;\n\n\twhile (n-- && *s1 == *s2) {\n\t\tif (!n || !*s1) break;\n\t\ts1++;\n\t\ts2++;\n\t}\n\treturn (*(unsigned char *)s1) - (*(unsigned char *)s2);\n}\n"
  },
  {
    "path": "libc/string/strncpy.c",
    "content": "#include <string.h>\n\nchar * strncpy(char * dest, const char * src, size_t n) {\n\tchar * out = dest;\n\twhile (n > 0) {\n\t\tif (!*src) break;\n\t\t*out = *src;\n\t\t++out;\n\t\t++src;\n\t\t--n;\n\t}\n\tfor (int i = 0; i < (int)n; ++i) {\n\t\t*out = '\\0';\n\t\t++out;\n\t}\n\treturn out;\n}\n"
  },
  {
    "path": "libc/string/strsignal.c",
    "content": "#include <string.h>\n#include <signal.h>\n#include <stdio.h>\n\nconst char * const sys_siglist[] = {\n\t[SIGHUP]      = \"Hangup\",\n\t[SIGINT]      = \"Interrupt\",\n\t[SIGQUIT]     = \"Quit\",\n\t[SIGILL]      = \"Illegal instruction\",\n\t[SIGTRAP]     = \"Trace/breakpoint trap\",\n\t[SIGABRT]     = \"Aborted\",\n\t[SIGEMT]      = \"Emulation trap\",\n\t[SIGFPE]      = \"Arithmetic exception\",\n\t[SIGKILL]     = \"Killed\",\n\t[SIGBUS]      = \"Bus error\",\n\t[SIGSEGV]     = \"Segmentation fault\",\n\t[SIGSYS]      = \"Bad system call\",\n\t[SIGPIPE]     = \"Broken pipe\",\n\t[SIGALRM]     = \"Alarm clock\",\n\t[SIGTERM]     = \"Terminated\",\n\t[SIGUSR1]     = \"User defined signal 1\",\n\t[SIGUSR2]     = \"User defined signal 2\",\n\t[SIGCHLD]     = \"Child process status\",\n\t[SIGPWR]      = \"Power failure\",\n\t[SIGWINCH]    = \"Window changed\",\n\t[SIGURG]      = \"Urgent I/O condition\",\n\t[SIGPOLL]     = \"Pollable event\",\n\t[SIGSTOP]     = \"Stopped\",\n\t[SIGTSTP]     = \"Stopped\",\n\t[SIGCONT]     = \"Continued\",\n\t[SIGTTIN]     = \"Stopped (tty input)\",\n\t[SIGTTOU]     = \"Stopped (tty output)\",\n\t[SIGTTOUT]    = \"Stopped (tty output)\",\n\t[SIGVTALRM]   = \"Virtual timer expired\",\n\t[SIGPROF]     = \"Profiling timer expired\",\n\t[SIGXCPU]     = \"CPU time limit exceeded\",\n\t[SIGXFSZ]     = \"File size limit exceeded\",\n\n\t/* silly stuff */\n\t[SIGWAITING]  = \"Waiting\",\n\t[SIGDIAF]     = \"Died in a fire\",\n\t[SIGHATE]     = \"Hated\",\n\t[SIGWINEVENT] = \"Window event\",\n\t[SIGCAT]      = \"Meow\",\n};\n\nchar * strsignal(int sig) {\n\tstatic char _signal_description[256];\n\tif (sig > 0 && sig < NUMSIGNALS) {\n\t\tsnprintf(_signal_description, 256, \"%s\", sys_siglist[sig]);\n\t} else {\n\t\tsnprintf(_signal_description, 256, \"Killed by signal %d\", sig);\n\t}\n\treturn _signal_description;\n}\n"
  },
  {
    "path": "libc/string/strxfrm.c",
    "content": "#include <string.h>\n\n/**\n * We only really support the \"C\" locale, so this is always\n * just a dumb memcpy.\n */\nsize_t strxfrm(char *dest, const char *src, size_t n) {\n\tsize_t i = 0;\n\twhile (*src && i < n) {\n\t\t*dest = *src;\n\t\tdest++;\n\t\tsrc++;\n\t\ti++;\n\t}\n\tif (i < n) {\n\t\t*dest = '\\0';\n\t}\n\treturn i;\n}\n"
  },
  {
    "path": "libc/strings/strcasecmp.c",
    "content": "#include <strings.h>\n#include <ctype.h>\n\nint strcasecmp(const char * s1, const char * s2) {\n\tfor (; tolower(*s1) == tolower(*s2) && *s1; s1++, s2++);\n\treturn *(unsigned char *)s1 - *(unsigned char *)s2;\n}\n\nint strncasecmp(const char *s1, const char *s2, size_t n) {\n\tif (n == 0) return 0;\n\n\twhile (n-- && tolower(*s1) == tolower(*s2)) {\n\t\tif (!n || !*s1) break;\n\t\ts1++;\n\t\ts2++;\n\t}\n\treturn (unsigned int)tolower(*s1) - (unsigned int)tolower(*s2);\n}\n"
  },
  {
    "path": "libc/sys/fswait.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/fswait.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(fswait, SYS_FSWAIT, int, int *);\nDEFN_SYSCALL3(fswait2, SYS_FSWAIT2, int, int *,int);\nDEFN_SYSCALL4(fswait3, SYS_FSWAIT3, int, int *, int, int *);\n\nint fswait(int count, int * fds) {\n\t__sets_errno(syscall_fswait(count, fds));\n}\n\nint fswait2(int count, int * fds, int timeout) {\n\t__sets_errno(syscall_fswait2(count, fds, timeout));\n}\n\nint fswait3(int count, int * fds, int timeout, int * out) {\n\t__sets_errno(syscall_fswait3(count, fds, timeout, out));\n}\n"
  },
  {
    "path": "libc/sys/mount.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL5(mount, SYS_MOUNT, char *, char *, char *, unsigned long, void *);\n\nint mount(char * source, char * target, char * type, unsigned long flags, void * data) {\n\t__sets_errno(syscall_mount(source, target, type, flags, data));\n}\n\n"
  },
  {
    "path": "libc/sys/network.c",
    "content": "/*\n * socket methods (mostly unimplemented)\n */\n#include <errno.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <poll.h>\n#include <sys/ioctl.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netdb.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(socket, SYS_SOCKET, int, int, int);\nDEFN_SYSCALL5(setsockopt, SYS_SETSOCKOPT, int,int,int,const void*,size_t);\nDEFN_SYSCALL3(bind, SYS_BIND, int,const void*,size_t);\nDEFN_SYSCALL4(accept, SYS_ACCEPT, int,void*,size_t*,int);\nDEFN_SYSCALL2(listen, SYS_LISTEN, int,int);\nDEFN_SYSCALL3(connect, SYS_CONNECT, int,const void*,size_t);\nDEFN_SYSCALL5(getsockopt, SYS_GETSOCKOPT, int,int,int,void*,size_t*);\nDEFN_SYSCALL3(recv, SYS_RECV, int,void*,int);\nDEFN_SYSCALL3(send, SYS_SEND, int,const void*,int);\nDEFN_SYSCALL2(shutdown, SYS_SHUTDOWN, int, int);\nDEFN_SYSCALL3(getsockname, SYS_GETSOCKNAME, int,void*,size_t*);\nDEFN_SYSCALL3(getpeername, SYS_GETPEERNAME, int,void*,size_t*);\n\nint connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n\t__sets_errno(syscall_connect(sockfd,addr,addrlen));\n}\n\n/* All of these should just be reads. */\nssize_t recv(int sockfd, void *buf, size_t len, int flags) {\n\tstruct iovec _iovec = {\n\t\tbuf, len\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = NULL,\n\t\t.msg_namelen = 0,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\treturn recvmsg(sockfd, &_header, flags);\n}\nssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) {\n\tstruct iovec _iovec = {\n\t\tbuf, len\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = src_addr,\n\t\t.msg_namelen = addrlen ? *addrlen : 0,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\tssize_t result = recvmsg(sockfd, &_header, flags);\n\tif (addrlen) *addrlen = _header.msg_namelen;\n\treturn result;\n}\nssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) {\n\t__sets_errno(syscall_recv(sockfd,msg,flags));\n}\n\nssize_t send(int sockfd, const void *buf, size_t len, int flags) {\n\tstruct iovec _iovec = {\n\t\t(void*)buf, len\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = NULL,\n\t\t.msg_namelen = 0,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\treturn sendmsg(sockfd, &_header, flags);\n}\nssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) {\n\tstruct iovec _iovec = {\n\t\t(void*)buf, len\n\t};\n\tstruct msghdr _header = {\n\t\t.msg_name = (void*)dest_addr,\n\t\t.msg_namelen = addrlen,\n\t\t.msg_iov = &_iovec,\n\t\t.msg_iovlen = 1,\n\t\t.msg_control = NULL,\n\t\t.msg_controllen = 0,\n\t\t.msg_flags = 0,\n\t};\n\treturn sendmsg(sockfd, &_header, flags);\n}\nssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) {\n\t__sets_errno(syscall_send(sockfd,msg,flags));\n}\n\nint socket(int domain, int type, int protocol) {\n\t/* Thin wrapper around a new system call, I guess. */\n\t__sets_errno(syscall_socket(domain,type,protocol));\n}\n\nuint32_t htonl(uint32_t hostlong) {\n\treturn ( (((hostlong) & 0xFF) << 24) | (((hostlong) & 0xFF00) << 8) | (((hostlong) & 0xFF0000) >> 8) | (((hostlong) & 0xFF000000) >> 24));\n}\n\nuint16_t htons(uint16_t hostshort) {\n\treturn ( (((hostshort) & 0xFF) << 8) | (((hostshort) & 0xFF00) >> 8) );\n}\n\nuint32_t ntohl(uint32_t netlong) {\n\treturn htonl(netlong);\n}\n\nuint16_t ntohs(uint16_t netshort) {\n\treturn htons(netshort);\n}\n\nint bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {\n\t__sets_errno(syscall_bind(sockfd,addr,addrlen));\n}\n\nint accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen) {\n\t__sets_errno(syscall_accept(sockfd,addr,addrlen,0));\n}\n\nint accept4(int sockfd, struct sockaddr * addr, socklen_t * addrlen, int flags) {\n\t__sets_errno(syscall_accept(sockfd,addr,addrlen,flags));\n}\n\nint listen(int sockfd, int backlog) {\n\t__sets_errno(syscall_listen(sockfd,backlog));\n}\n\nint getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {\n\t__sets_errno(syscall_getsockopt(sockfd,level,optname,optval,optlen));\n}\n\nint setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {\n\t__sets_errno(syscall_setsockopt(sockfd,level,optname,optval,optlen));\n}\n\nint shutdown(int sockfd, int how) {\n\t__sets_errno(syscall_shutdown(sockfd,how));\n}\n\n#define UNIMPLEMENTED fprintf(stderr, \"[libnetwork] Unimplemented: %s\\n\", __FUNCTION__)\n\nint getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {\n\t__sets_errno(syscall_getsockname(sockfd, addr, addrlen));\n}\n\nint getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {\n\t__sets_errno(syscall_getpeername(sockfd, addr, addrlen));\n}\n\nin_addr_t inet_addr(const char * in) {\n\tchar ip[16];\n\tchar * c = ip;\n\tuint32_t out[4];\n\tchar * i;\n\tmemcpy(ip, in, strlen(in) < 15 ? strlen(in) + 1 : 15);\n\tip[15] = '\\0';\n\n\ti = (char *)strchr(c, '.');\n\t*i = '\\0';\n\tout[0] = atoi(c);\n\tc += strlen(c) + 1;\n\n\ti = (char *)strchr(c, '.');\n\t*i = '\\0';\n\tout[1] = atoi(c);\n\tc += strlen(c) + 1;\n\n\ti = (char *)strchr(c, '.');\n\t*i = '\\0';\n\tout[2] = atoi(c);\n\tc += strlen(c) + 1;\n\n\tout[3] = atoi(c);\n\n\treturn htonl((out[0] << 24) | (out[1] << 16) | (out[2] << 8) | (out[3]));\n}\n\nchar * inet_ntoa(struct in_addr in) {\n\tstatic char buf[17];\n\n\tuint32_t hostOrder = ntohl(in.s_addr);\n\n\tsnprintf(buf,17,\"%d.%d.%d.%d\",\n\t\t(hostOrder >> 24) & 0xFF,\n\t\t(hostOrder >> 16) & 0xFF,\n\t\t(hostOrder >>  8) & 0xFF,\n\t\t(hostOrder >>  0) & 0xFF);\n\n\treturn buf;\n}\n\nstatic struct hostent _hostent = {0};\nstatic uint32_t _hostent_addr = 0;\nstatic char * _host_entry_list[2] = {0};\n\nstruct dns_packet {\n\tuint16_t qid;\n\tuint16_t flags;\n\tuint16_t questions;\n\tuint16_t answers;\n\tuint16_t authorities;\n\tuint16_t additional;\n\tuint8_t data[];\n} __attribute__((packed)) __attribute__((aligned(2)));\n\nstruct hostent * gethostbyname(const char * name) {\n\n\t/* Is name just an IP address? */\n\tint maybe_ip = 1;\n\tint dots = 0;\n\tfor (const char * c = name; *c; ++c) {\n\t\tif ((*c < '0' || *c > '9') && *c != '.') {\n\t\t\tmaybe_ip = 0;\n\t\t\tbreak;\n\t\t}\n\t\tif (*c == '.') {\n\t\t\tdots++;\n\t\t\tif (dots > 3) {\n\t\t\t\tmaybe_ip = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (maybe_ip && dots == 3) {\n\t\t_hostent.h_name = (char*)name;\n\t\t_hostent.h_aliases = NULL;\n\t\t_hostent.h_addrtype = AF_INET;\n\t\t_hostent.h_length = sizeof(uint32_t);\n\t\t_hostent.h_addr_list = _host_entry_list;\n\t\t_host_entry_list[0] = (char*)&_hostent_addr;\n\t\t_hostent_addr = inet_addr(name);\n\t\treturn &_hostent;\n\t}\n\n\tif (!strcmp(name,\"localhost\")) {\n\t\t_hostent.h_name = (char*)name;\n\t\t_hostent.h_aliases = NULL;\n\t\t_hostent.h_addrtype = AF_INET;\n\t\t_hostent.h_length = sizeof(uint32_t);\n\t\t_hostent.h_addr_list = _host_entry_list;\n\t\t_host_entry_list[0] = (char*)&_hostent_addr;\n\t\t_hostent_addr = inet_addr(\"127.0.0.1\");\n\t\treturn &_hostent;\n\n\t}\n\n\t/* Try to open /etc/resolv.conf */\n\tFILE * resolv = fopen(\"/etc/resolv.conf\",\"r\");\n\tif (!resolv) resolv = fopen(\"/var/resolv.conf\",\"r\");\n\tif (!resolv) {\n\t\tfprintf(stderr, \"gethostbyname: no resolver\\n\");\n\t\treturn NULL;\n\t}\n\n\t/* Try to get a udp socket */\n\tint sock = socket(AF_INET, SOCK_DGRAM, 0);\n\tif (sock < 0) {\n\t\tfprintf(stderr, \"gethostbyname: could not get a socket\\n\");\n\t\treturn NULL;\n\t}\n\n\t/* Try to send something to the name server */\n\tchar tmp[256];\n\tfread(tmp, 256, 1, resolv);\n\tif (strncmp(tmp,\"nameserver \",strlen(\"nameserver \"))) {\n\t\tfprintf(stderr, \"gethostbyname: resolv doesn't look right?\\n\");\n\t}\n\n\t/* Try to convert so we can connect... */\n\tuint32_t ns_addr = inet_addr(tmp + strlen(\"nameserver \"));\n\n\t/* Form a DNS request */\n\tchar dat[256];\n\tstruct dns_packet * req = (struct dns_packet*)&dat;\n\tuint16_t qid = rand() & 0xFFFF;\n\treq->qid = htons(qid);\n\treq->flags = htons(0x0100);\n\treq->questions = htons(1);\n\treq->answers = htons(0);\n\treq->authorities = htons(0);\n\treq->additional = htons(0);\n\n\t/* Turn requested name into DNS request */\n\tssize_t i = 0;\n\tconst char * c = name;\n\twhile (*c) {\n\t\tconst char * n = strchr(c,'.');\n\t\tif (!n) n = c + strlen(c);\n\t\tssize_t len = n - c;\n\t\treq->data[i++] = len;\n\t\tfor (; c < n; ++c, ++i) {\n\t\t\treq->data[i] = *c;\n\t\t}\n\t\tif (!*c) break;\n\t\tc++;\n\t}\n\treq->data[i++] = 0x00;\n\treq->data[i++] = 0x00;\n\treq->data[i++] = 0x01; /* A */\n\treq->data[i++] = 0x00;\n\treq->data[i++] = 0x01; /* IN */\n\n\tstruct sockaddr_in dest;\n\tdest.sin_family = AF_INET;\n\tdest.sin_port   = htons(53);\n\tmemcpy(&dest.sin_addr.s_addr, &ns_addr, sizeof(ns_addr));\n\n\tif (sendto(sock, &dat, sizeof(struct dns_packet) + i, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) {\n\t\tfprintf(stderr, \"gethostbyname: failed to send\\n\");\n\t\treturn NULL;\n\t}\n\n\t/* Wait for a response, but don't wait too long. */\n\tstruct pollfd fds[1];\n\tfds[0].fd = sock;\n\tfds[0].events = POLLIN;\n\tint ret = poll(fds,1,2000); /* Two seconds? Is that okay? */\n\tif (ret <= 0) {\n\t\tfprintf(stderr, \"gethostbyname: timed out\\n\");\n\t\treturn NULL;\n\t}\n\n\tchar buf[1550];\n\tssize_t len = recv(sock, buf, 1550, 0);\n\tclose(sock);\n\n\tif (len < 0) {\n\t\tfprintf(stderr, \"gethostbyname: failed to recv\\n\");\n\t\treturn NULL;\n\t}\n\n\n\t/* Now examine the response */\n\tstruct dns_packet * response = (struct dns_packet *)&buf;\n\n\tif (ntohs(response->answers) == 0) {\n\t\tfprintf(stderr, \"gethostbyname: no answer\\n\");\n\t\treturn NULL;\n\t}\n\n\tuint16_t answers = ntohs(response->answers);\n\tuint16_t queries = ntohs(response->questions);\n\tconst unsigned char * d = response->data;\n\n\tfor (uint16_t i = 0; i < queries; ++i) {\n\t\twhile (1) {\n\t\t\tif (d - response->data >= len) goto _nope;\n\t\t\tint l = *d++;\n\t\t\tif ((l & 0xc0) == 0xc0) { d++; break; }\n\t\t\tif (!l) break;\n\t\t\td += l;\n\t\t}\n\t\td += 4;\n\t}\n\tfor (uint16_t i = 0; i < answers; ++i) {\n\t\twhile (1) {\n\t\t\tif (d - response->data >= len) goto _nope;\n\t\t\tint l = *d++;\n\t\t\tif ((l & 0xc0) == 0xc0) { d++; break; }\n\t\t\tif (!l) break;\n\t\t\td += l;\n\t\t}\n\n\t\tif (d - response->data > len + 10) goto _nope;\n\t\td += 2; /* skip type */\n\t\tuint16_t cls  = d[0] * 256 + d[1]; d += 2;\n\t\td += 4; /* skip ttl */\n\t\tuint16_t dlen = d[0] * 256 + d[1]; d += 2;\n\t\tif (dlen == 4 && cls == 1) {\n\t\t\tif (d - response->data > len + dlen) goto _nope;\n\t\t\t/* Get a return value */\n\t\t\t_hostent.h_name = (char*)name;\n\t\t\t_hostent.h_aliases = NULL;\n\t\t\t_hostent.h_addrtype = AF_INET;\n\t\t\t_hostent.h_length = sizeof(uint32_t);\n\t\t\t_hostent.h_addr_list = _host_entry_list;\n\t\t\t_host_entry_list[0] = (char*)&_hostent_addr;\n\t\t\t_hostent_addr = *(uint32_t*)(d);\n\t\t\treturn &_hostent;\n\t\t}\n\t\td += dlen;\n\t}\n\n_nope:\n\tfprintf(stderr, \"gethostbyname: no viable answer\\n\");\n\treturn NULL;\n}\n\nint getnameinfo(const struct sockaddr *addr, socklen_t addrlen,\n                char *host, socklen_t hostlen,\n                char *serv, socklen_t servlen, int flags) {\n\treturn -ENOSYS;\n}\n\nint getaddrinfo(const char *node, const char *service,\n                const struct addrinfo *hints,\n                struct addrinfo **res) {\n\n\tstruct hostent * ent = gethostbyname(node);\n\tif (!ent) return -EINVAL; /* EAI_FAIL */\n\n\t*res = malloc(sizeof(struct addrinfo));\n\t(*res)->ai_flags = 0;\n\t(*res)->ai_family = AF_INET;\n\t(*res)->ai_socktype = 0;\n\t(*res)->ai_protocol = 0;\n\t(*res)->ai_addrlen = sizeof(struct sockaddr_in);\n\tstruct sockaddr_in * addr = malloc(sizeof(struct sockaddr_in));\n\taddr->sin_family = AF_INET;\n\tmemcpy(&addr->sin_addr.s_addr, ent->h_addr, ent->h_length);\n\t(*res)->ai_addr = (struct sockaddr *)addr;\n\t(*res)->ai_canonname = NULL;\n\t(*res)->ai_next = NULL;\n\treturn 0;\n}\n\nvoid freeaddrinfo(struct addrinfo *res) {\n\tif (res->ai_addr) free(res->ai_addr);\n\tfree(res);\n}\n"
  },
  {
    "path": "libc/sys/ptrace.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/ptrace.h>\n#include <errno.h>\n\nDEFN_SYSCALL4(ptrace, SYS_PTRACE, int, int, void *, void *);\n\nlong ptrace(enum __ptrace_request request, pid_t pid, void * addr, void * data) {\n\t__sets_errno(syscall_ptrace(request,pid,addr,data));\n}\n"
  },
  {
    "path": "libc/sys/reboot.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(reboot, SYS_REBOOT);\n\n/* TODO: define reboot() */\n"
  },
  {
    "path": "libc/sys/shm.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(shm_obtain,  SYS_SHM_OBTAIN, const char *, size_t *);\nDEFN_SYSCALL1(shm_release, SYS_SHM_RELEASE, const char *);\n\nvoid * shm_obtain(const char * path, size_t * size) {\n\treturn (void *)syscall_shm_obtain(path, size);\n}\n\nint shm_release(const char * path) {\n\treturn syscall_shm_release(path);\n}\n"
  },
  {
    "path": "libc/sys/sysfunc.c",
    "content": "#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/sysfunc.h>\n\nDEFN_SYSCALL2(sysfunc, SYS_SYSFUNC, int, char **);\n\nextern int sysfunc(int command, char ** args) {\n\t__sets_errno(syscall_sysfunc(command, args));\n}\n"
  },
  {
    "path": "libc/sys/uname.c",
    "content": "#include <sys/utsname.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(uname, SYS_UNAME, void *);\n\nint uname(struct utsname *__name) {\n\treturn syscall_uname((void *)__name);\n}\n"
  },
  {
    "path": "libc/sys/wait.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL3(waitpid, SYS_WAITPID, int, int *, int);\n\nint waitpid(int pid, int *status, int options) {\n\t/* XXX: status, options? */\n\t__sets_errno(syscall_waitpid(pid, status, options));\n}\n\nint wait(int *status) {\n\treturn waitpid(-1, status, 0);\n}\n"
  },
  {
    "path": "libc/time/clock.c",
    "content": "#include <time.h>\n#include <sys/times.h>\n\nclock_t clock(void) {\n\tstruct tms timeValues;\n\ttimes(&timeValues);\n\treturn timeValues.tms_utime;\n}\n"
  },
  {
    "path": "libc/time/clock_gettime.c",
    "content": "#include <time.h>\n#include <errno.h>\n#include <sys/time.h>\n\nint clock_getres(clockid_t clk_id, struct timespec *res) {\n\tif (clk_id < 0 || clk_id > 1) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\n\tres->tv_sec = 0;\n\tres->tv_nsec = 1000;\n\treturn 0;\n}\n\nint clock_gettime(clockid_t clk_id, struct timespec *tp) {\n\tif (clk_id < 0 || clk_id > 1) {\n\t\terrno = EINVAL;\n\t\treturn -1;\n\t}\n\tstruct timeval t;\n\tgettimeofday(&t, NULL);\n\n\ttp->tv_sec  = t.tv_sec;\n\ttp->tv_nsec = t.tv_usec * 1000;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/time/ctime.c",
    "content": "#include <time.h>\n#include <sys/time.h>\n\n/*\n * TODO: Also supposed to set tz values...\n */\nchar * ctime(const time_t * timep) {\n    return asctime(localtime(timep));\n}\n"
  },
  {
    "path": "libc/time/gettimeofday.c",
    "content": "#include <sys/time.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(gettimeofday, SYS_GETTIMEOFDAY, void *, void *);\n\nint gettimeofday(struct timeval *p, void *z){\n\treturn syscall_gettimeofday(p,z);\n}\n\n"
  },
  {
    "path": "libc/time/localtime.c",
    "content": "#include <time.h>\n#include <sys/time.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n\n#define SEC_DAY 86400\n\n#define fprintf(...)\n\nstatic struct tm _timevalue;\n\nstatic int year_is_leap(int year) {\n\treturn ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)));\n}\n\n// 0 was a Thursday\n\nstatic int day_of_week(long seconds) {\n\tlong day = seconds / SEC_DAY;\n\tday += 4;\n\treturn day % 7;\n}\n\nstatic long days_in_month(int month, int year) {\n\tswitch(month) {\n\t\tcase 12:\n\t\t\treturn 31;\n\t\tcase 11:\n\t\t\treturn 30;\n\t\tcase 10:\n\t\t\treturn 31;\n\t\tcase 9:\n\t\t\treturn 30;\n\t\tcase 8:\n\t\t\treturn 31;\n\t\tcase 7:\n\t\t\treturn 31;\n\t\tcase 6:\n\t\t\treturn 30;\n\t\tcase 5:\n\t\t\treturn 31;\n\t\tcase 4:\n\t\t\treturn 30;\n\t\tcase 3:\n\t\t\treturn 31;\n\t\tcase 2:\n\t\t\treturn year_is_leap(year) ? 29 : 28;\n\t\tcase 1:\n\t\t\treturn 31;\n\t}\n\treturn 0;\n}\n\nstatic struct tm * fill_time(const time_t * timep, struct tm * _timevalue, const char * tzName, int tzOffset) {\n\n\ttime_t timeVal = *timep + tzOffset;\n\t_timevalue->_tm_zone_name = tzName;\n\t_timevalue->_tm_zone_offset = tzOffset;\n\n\tlong seconds = timeVal < 0 ? -2208988800L : 0;\n\tlong year_sec = 0;\n\n\tint startYear = timeVal < 0 ? 1900 : 1970;\n\n\tfor (int year = startYear; year < 2100; ++year) {\n\t\tlong added = year_is_leap(year) ? 366 : 365;\n\t\tlong secs = added * 86400;\n\n\t\tif (seconds + secs > timeVal) {\n\t\t\t_timevalue->tm_year = year - 1900;\n\t\t\tyear_sec = seconds;\n\t\t\tfor (int month = 1; month <= 12; ++month) {\n\t\t\t\tsecs = days_in_month(month, year) * SEC_DAY;\n\t\t\t\tif (seconds + secs > timeVal) {\n\t\t\t\t\t_timevalue->tm_mon = month - 1;\n\t\t\t\t\tfor (int day = 1; day <= days_in_month(month, year); ++day) {\n\t\t\t\t\t\tsecs = 60 * 60 * 24;\n\t\t\t\t\t\tif (seconds + secs > timeVal) {\n\t\t\t\t\t\t\t_timevalue->tm_mday = day;\n\t\t\t\t\t\t\tfor (int hour = 1; hour <= 24; ++hour) {\n\t\t\t\t\t\t\t\tsecs = 60 * 60;\n\t\t\t\t\t\t\t\tif (seconds + secs > timeVal) {\n\t\t\t\t\t\t\t\t\tlong remaining = timeVal - seconds;\n\t\t\t\t\t\t\t\t\t_timevalue->tm_hour = hour - 1;\n\t\t\t\t\t\t\t\t\t_timevalue->tm_min = remaining / 60;\n\t\t\t\t\t\t\t\t\t_timevalue->tm_sec = remaining % 60;\n\t\t\t\t\t\t\t\t\t_timevalue->tm_wday = day_of_week(timeVal);\n\t\t\t\t\t\t\t\t\t_timevalue->tm_yday = (timeVal - year_sec) / SEC_DAY;\n\t\t\t\t\t\t\t\t\t_timevalue->tm_isdst = 0;\n\t\t\t\t\t\t\t\t\treturn _timevalue;\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tseconds += secs;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn NULL;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tseconds += secs;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn NULL;\n\t\t\t\t} else {\n\t\t\t\t\tseconds += secs;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn NULL;\n\t\t} else {\n\t\t\tseconds += secs;\n\t\t}\n\t}\n\treturn (void *)0;\n}\n\n#define HOURS    3600\n#define MINUTES  60\n\nstatic int get_timezone_offset(void) {\n\tchar * tzOff = getenv(\"TZ_OFFSET\");\n\tif (!tzOff) return 0;\n\tchar * endptr;\n\tint out = strtol(tzOff,&endptr,10);\n\tif (*endptr) return 0;\n\treturn out;\n}\n\nstruct timezone_offset_db {\n\tint offset;\n\tconst char * abbrev;\n};\n\nstatic struct timezone_offset_db common_offsets[] = {\n\t{0, \"UTC\"},\n\t{1 * HOURS, \"CEST\"}, /* Central Europe Standard Time */\n\t{8 * HOURS, \"SST\"}, /* Singapore Standard Time */\n\t{9 * HOURS, \"JST\"}, /* Japan Standard Time */\n\t{-5 * HOURS, \"EST\"}, /* US Eastern Standard */\n\t{-6 * HOURS, \"CST\"}, /* US Central Standard */\n\t{-7 * HOURS, \"MST\"}, /* US Mountain Standard */\n\t{-8 * HOURS, \"PST\"}, /* US Pacific Standard */\n\t{0, NULL},\n};\n\nstatic char * get_timezone(void) {\n\tstatic char buf[20];\n\tchar * tzEnv = getenv(\"TZ\");\n\tif (!tzEnv) {\n\t\t/* Is there an offset? */\n\t\tint offset = get_timezone_offset();\n\t\tfor (struct timezone_offset_db * db = common_offsets; db->abbrev; db++) {\n\t\t\tif (offset == db->offset) return (char*)db->abbrev;\n\t\t}\n\t\t/* Is it some number of hours? */\n\t\tif (offset % HOURS == 0) {\n\t\t\tif (offset > 0) {\n\t\t\t\tsnprintf(buf, 20, \"UTC+%d\", offset / HOURS);\n\t\t\t} else {\n\t\t\t\tsnprintf(buf, 20, \"UTC-%d\", -offset / HOURS);\n\t\t\t}\n\t\t\treturn buf;\n\t\t}\n\t\treturn \"???\";\n\t}\n\treturn tzEnv;\n}\n\n\nstruct tm *localtime_r(const time_t *timep, struct tm * _timevalue) {\n\treturn fill_time(timep, _timevalue, get_timezone(), get_timezone_offset());\n}\n\nstruct tm * gmtime_r(const time_t * timep, struct tm * tm) {\n\treturn fill_time(timep, tm, \"UTC\", 0);\n}\n\nstruct tm * localtime(const time_t *timep) {\n\treturn fill_time(timep, &_timevalue, get_timezone(), get_timezone_offset());\n}\n\nstruct tm *gmtime(const time_t *timep) {\n\treturn fill_time(timep, &_timevalue, \"UTC\", 0);\n}\n\nstatic unsigned int secs_of_years(int years) {\n\tunsigned int days = 0;\n\twhile (years > 1969) {\n\t\tdays += 365;\n\t\tif (year_is_leap(years)) {\n\t\t\tdays++;\n\t\t}\n\t\tyears--;\n\t}\n\treturn days * 86400;\n}\n\nstatic long secs_of_month(int months, int year) {\n\tlong days = 0;\n\tfor (int i = 1; i < months; ++i) {\n\t\tdays += days_in_month(i, year);\n\t}\n\treturn days * SEC_DAY;\n}\n\ntime_t mktime(struct tm *tm) {\n\treturn\n\t  secs_of_years(tm->tm_year + 1899) +\n\t  secs_of_month(tm->tm_mon + 1, tm->tm_year + 1900) +\n\t  (tm->tm_mday - 1) * 86400 +\n\t  (tm->tm_hour) * 3600 +\n\t  (tm->tm_min) * 60 +\n\t  (tm->tm_sec) - tm->_tm_zone_offset;\n}\n\n\n"
  },
  {
    "path": "libc/time/settimeofday.c",
    "content": "#include <sys/time.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(settimeofday, SYS_SETTIMEOFDAY, void *, void *);\n\nint settimeofday(struct timeval *p, void *z){\n\treturn syscall_settimeofday(p,z);\n}\n\n\n"
  },
  {
    "path": "libc/time/strftime.c",
    "content": "#include <stddef.h>\n#include <time.h>\n#include <sys/time.h>\n#include <stdio.h>\n\nstatic char * weekdays[] = {\n\t\"Sunday\",\n\t\"Monday\",\n\t\"Tuesday\",\n\t\"Wednesday\",\n\t\"Thursday\",\n\t\"Friday\",\n\t\"Saturday\"\n};\n\nstatic char * weekdays_short[] = {\n\t\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"\n};\n\nstatic char * months[] = {\n\t\"January\",\n\t\"February\",\n\t\"March\",\n\t\"April\",\n\t\"May\",\n\t\"June\",\n\t\"July\",\n\t\"August\",\n\t\"September\",\n\t\"October\",\n\t\"November\",\n\t\"December\"\n};\n\nstatic char * months_short[] = {\n\t\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"\n};\n\nsize_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm) {\n\tif (!tm) {\n\t\tif (max < sizeof(\"[tm is null]\")) return 0;\n\t\treturn sprintf(s, \"[tm is null]\");\n\t}\n\tchar * b = s;\n\tfor (const char *f = fmt; *f; f++) {\n\t\tif (*f != '%') {\n\t\t\tif (max == 0) return 0;\n\t\t\tmax--;\n\t\t\t*b++ = *f;\n\t\t\tcontinue;\n\t\t}\n\t\t++f;\n\t\tint _alte = 0;\n\t\tint _alto = 0;\n\t\tif (*f == 'E') {\n\t\t\t_alte = 1;\n\t\t\t++f;\n\t\t} else if (*f == '0') {\n\t\t\t_alto = 1;\n\t\t\t++f;\n\t\t}\n\t\t(void)_alte; /* TODO: Implement these */\n\t\t(void)_alto;\n\t\tint w = 0;\n\t\tswitch (*f) {\n\t\t\tcase 'a':\n\t\t\t\tw = snprintf(b, max, \"%s\", weekdays_short[tm->tm_wday]);\n\t\t\t\tbreak;\n\t\t\tcase 'A':\n\t\t\t\tw = snprintf(b, max, \"%s\", weekdays[tm->tm_wday]);\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\tcase 'b':\n\t\t\t\tw = snprintf(b, max, \"%s\", months_short[tm->tm_mon]);\n\t\t\t\tbreak;\n\t\t\tcase 'B':\n\t\t\t\tw = snprintf(b, max, \"%s\", months[tm->tm_mon]);\n\t\t\t\tbreak;\n\t\t\tcase 'c':\n\t\t\t\tw = snprintf(b, max, \"%s %s %02d %02d:%02d:%02d %04d\",\n\t\t\t\t\t\tweekdays_short[tm->tm_wday],\n\t\t\t\t\t\tmonths_short[tm->tm_mon],\n\t\t\t\t\t\ttm->tm_mday,\n\t\t\t\t\t\ttm->tm_hour,\n\t\t\t\t\t\ttm->tm_min,\n\t\t\t\t\t\ttm->tm_sec,\n\t\t\t\t\t\ttm->tm_year + 1900);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tw = snprintf(b, max, \"%02d\", (tm->tm_year + 1900) / 100);\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_mday);\n\t\t\t\tbreak;\n\t\t\tcase 'D':\n\t\t\t\tw = snprintf(b, max, \"%02d/%02d/%02d\", tm->tm_mon+1, tm->tm_mday, tm->tm_year % 100);\n\t\t\t\tbreak;\n\t\t\tcase 'e':\n\t\t\t\tw = snprintf(b, max, \"%2d\", tm->tm_mday);\n\t\t\t\tbreak;\n\t\t\tcase 'F':\n\t\t\t\tw = snprintf(b, max, \"%04d-%02d-%02d\", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday);\n\t\t\t\tbreak;\n\t\t\tcase 'H':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_hour);\n\t\t\t\tbreak;\n\t\t\tcase 'I':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12)));\n\t\t\t\tbreak;\n\t\t\tcase 'j':\n\t\t\t\tw = snprintf(b, max, \"%03d\", tm->tm_yday);\n\t\t\t\tbreak;\n\t\t\tcase 'k':\n\t\t\t\tw = snprintf(b, max, \"%2d\", tm->tm_hour);\n\t\t\t\tbreak;\n\t\t\tcase 'l':\n\t\t\t\tw = snprintf(b, max, \"%2d\", tm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12)));\n\t\t\t\tbreak;\n\t\t\tcase 'm':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_mon+1);\n\t\t\t\tbreak;\n\t\t\tcase 'M':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_min);\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tw = snprintf(b, max, \"\\n\");\n\t\t\t\tbreak;\n\t\t\tcase 'p':\n\t\t\t\tw = snprintf(b, max, \"%s\", tm->tm_hour < 12 ? \"AM\" : \"PM\");\n\t\t\t\tbreak;\n\t\t\tcase 'P':\n\t\t\t\tw = snprintf(b, max, \"%s\", tm->tm_hour < 12 ? \"am\" : \"pm\");\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tw = snprintf(b, max, \"%02d:%02d:%02d %s\",\n\t\t\t\t\t\ttm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12)),\n\t\t\t\t\t\ttm->tm_min,\n\t\t\t\t\t\ttm->tm_sec,\n\t\t\t\t\t\ttm->tm_hour < 12 ? \"AM\" : \"PM\");\n\t\t\t\tbreak;\n\t\t\tcase 'R':\n\t\t\t\tw = snprintf(b, max, \"%02d:%02d\",\n\t\t\t\t\t\ttm->tm_hour,\n\t\t\t\t\t\ttm->tm_min);\n\t\t\t\tbreak;\n\t\t\tcase 's':\n\t\t\t\tw = snprintf(b, max, \"%ld\", mktime((struct tm*)tm));\n\t\t\t\tbreak;\n\t\t\tcase 'S':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_sec);\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tw = snprintf(b, max, \"\\t\");\n\t\t\t\tbreak;\n\t\t\tcase 'T':\n\t\t\t\tw = snprintf(b, max, \"%02d:%02d:%02d\",\n\t\t\t\t\t\ttm->tm_hour,\n\t\t\t\t\t\ttm->tm_min,\n\t\t\t\t\t\ttm->tm_sec);\n\t\t\t\tbreak;\n\t\t\tcase 'u':\n\t\t\t\tw = snprintf(b, max, \"%d\", tm->tm_wday == 0 ? 7 : tm->tm_wday);\n\t\t\t\tbreak;\n\t\t\tcase 'w':\n\t\t\t\tw = snprintf(b, max, \"%d\", tm->tm_wday);\n\t\t\t\tbreak;\n\t\t\tcase 'x':\n\t\t\t\tw = snprintf(b, max, \"%02d/%02d/%02d\", tm->tm_mon+1, tm->tm_mday, tm->tm_year % 100);\n\t\t\t\tbreak;\n\t\t\tcase 'X':\n\t\t\t\tw = snprintf(b, max, \"%02d:%02d:%02d\",\n\t\t\t\t\t\ttm->tm_hour,\n\t\t\t\t\t\ttm->tm_min,\n\t\t\t\t\t\ttm->tm_sec);\n\t\t\t\tbreak;\n\t\t\tcase 'y':\n\t\t\t\tw = snprintf(b, max, \"%02d\", tm->tm_year % 100);\n\t\t\t\tbreak;\n\t\t\tcase 'Y':\n\t\t\t\tw = snprintf(b, max, \"%04d\", tm->tm_year + 1900);\n\t\t\t\tbreak;\n\t\t\tcase 'z': {\n\t\t\t\tint zone_offset = tm->_tm_zone_offset >= 0 ? tm->_tm_zone_offset : -tm->_tm_zone_offset;\n\t\t\t\tchar sign = tm->_tm_zone_offset >= 0 ? '+' : '-';\n\t\t\t\tint hour = zone_offset / 3600;\n\t\t\t\tint mins = (zone_offset / 60) % 60;\n\t\t\t\tw = snprintf(b, max, \"%c%02d%02d\", sign, hour, mins);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'Z':\n\t\t\t\tw = snprintf(b, max, tm->_tm_zone_name);\n\t\t\t\tbreak;\n\t\t\tcase '%':\n\t\t\t\tw = snprintf(b, max, \"%c\", '%');\n\t\t\t\tbreak;\n\t\t\tcase 'V':\n\t\t\tcase 'W':\n\t\t\tcase 'U':\n\t\t\tcase 'G':\n\t\t\tcase 'g':\n\t\t\t\tw = snprintf(b, max, \"<%c unsupported>\", *f);\n\t\t\t\tbreak;\n\t\t}\n\t\tif (w < 0) return 0; /* error while formatting */\n\t\tif ((size_t)w >= max) return 0; /* output was truncated */\n\t\tmax -= w;\n\t\tb += w;\n\t}\n\t/* Ensure the buffer ends in a null */\n\t*b = '\\0';\n\treturn b - s;\n}\n\nstatic char output[26];\nchar * asctime(const struct tm *tm) {\n\tstrftime(output, 26, \"%a %b %d %T %Y\\n\", tm);\n\treturn output;\n}\n\n"
  },
  {
    "path": "libc/time/time.c",
    "content": "#include <time.h>\n#include <sys/time.h>\n\ntime_t time(time_t * out) {\n\tstruct timeval p;\n\tgettimeofday(&p, NULL);\n\tif (out) {\n\t\t*out = p.tv_sec;\n\t}\n\treturn p.tv_sec;\n}\n\ndouble difftime(time_t a, time_t b) {\n\treturn (double)(a - b);\n}\n"
  },
  {
    "path": "libc/time/times.c",
    "content": "#include <sys/times.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(times, SYS_TIMES, struct tms *);\n\nclock_t times(struct tms * buf) {\n\t__sets_errno(syscall_times(buf));\n}\n\n"
  },
  {
    "path": "libc/unistd/access.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(access, SYS_ACCESS, char *, int);\n\nint access(const char *pathname, int mode) {\n\tint result = syscall_access((char *)pathname, mode);\n\tif (result < 0) {\n\t\terrno = ENOENT; /* XXX */\n\t\treturn -1;\n\t}\n\treturn result;\n}\n\n"
  },
  {
    "path": "libc/unistd/alarm.c",
    "content": "#include <stdio.h>\n\nextern char * _argv_0;\n\nunsigned int alarm(unsigned int seconds) {\n\tfprintf(stderr, \"%s: alarm requested (%d seconds)\\n\", _argv_0, seconds);\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/unistd/chdir.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(chdir, SYS_CHDIR, char *);\n\nint chdir(const char *path) {\n\t__sets_errno(syscall_chdir((char*)path));\n}\n\n"
  },
  {
    "path": "libc/unistd/chmod.c",
    "content": "#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/stat.h>\n\nDEFN_SYSCALL2(chmod, SYS_CHMOD, char *, int);\n\nint chmod(const char *path, mode_t mode) {\n\t__sets_errno(syscall_chmod((char *)path, mode));\n}\n\nDEFN_SYSCALL2(fchmod, SYS_FCHMOD, int, int);\n\nint fchmod(int fd, mode_t mode) {\n\t__sets_errno(syscall_fchmod(fd, mode));\n}\n"
  },
  {
    "path": "libc/unistd/chown.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(chown, SYS_CHOWN, char *, int, int);\n\nint chown(const char * pathname, uid_t owner, gid_t group) {\n\t__sets_errno(syscall_chown((char*)pathname,owner,group));\n}\n\nDEFN_SYSCALL3(fchown, SYS_FCHOWN, int, int, int);\n\nint fchown(int fd, uid_t owner, gid_t group) {\n\t__sets_errno(syscall_fchown(fd,owner,group));\n}\n\n"
  },
  {
    "path": "libc/unistd/close.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(close, SYS_CLOSE, int);\n\nint close(int file) {\n\treturn syscall_close(file);\n}\n"
  },
  {
    "path": "libc/unistd/creat.c",
    "content": "#include <unistd.h>\n#include <fcntl.h>\n\nint creat(const char *path, mode_t mode) {\n\treturn open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);\n}\n"
  },
  {
    "path": "libc/unistd/dup2.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(dup2, SYS_DUP2, int, int);\n\nint dup2(int oldfd, int newfd) {\n\treturn syscall_dup2(oldfd, newfd);\n}\n\nint dup(int oldfd) {\n\treturn dup2(oldfd, -1);\n}\n"
  },
  {
    "path": "libc/unistd/execvp.c",
    "content": "#include <stdio.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/stat.h>\n\nDEFN_SYSCALL3(execve, SYS_EXECVE, char *, char **, char **);\n\nextern char ** environ;\n\n#define DEFAULT_PATH \"/bin:/usr/bin\"\n\nint execve(const char *name, char * const argv[], char * const envp[]) {\n\t__sets_errno(syscall_execve((char*)name,(char**)argv,(char**)envp));\n}\n\nint execvpe(const char *file, char *const argv[], char *const envp[]) {\n\tif (file && (!strstr(file, \"/\"))) {\n\t\t/* We don't quite understand \"$PATH\", so... */\n\t\tchar * path = getenv(\"PATH\");\n\t\tif (!path) {\n\t\t\tpath = DEFAULT_PATH;\n\t\t}\n\t\tchar * xpath = strdup(path);\n\t\tchar * p, * last;\n\t\tfor ((p = strtok_r(xpath, \":\", &last)); p; p = strtok_r(NULL, \":\", &last)) {\n\t\t\tint r;\n\t\t\tstruct stat stat_buf;\n\t\t\tchar * exe = malloc(strlen(p) + strlen(file) + 2);\n\t\t\tstrcpy(exe, p);\n\t\t\tstrcat(exe, \"/\");\n\t\t\tstrcat(exe, file);\n\n\t\t\tr = stat(exe, &stat_buf);\n\t\t\tif (r != 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!(stat_buf.st_mode & 0111)) {\n\t\t\t\tcontinue; /* XXX not technically correct; need to test perms */\n\t\t\t}\n\n\t\t\treturn execve(exe, argv, envp);\n\t\t}\n\t\tfree(xpath);\n\t\terrno = ENOENT;\n\t\treturn -1;\n\t} else if (file) {\n\t\treturn execve(file, argv, envp);\n\t}\n\terrno = ENOENT;\n\treturn -1;\n}\n\nint execvp(const char *file, char *const argv[]) {\n\treturn execvpe(file, argv, environ);\n}\n\nint execv(const char * file, char * const argv[]) {\n\treturn execve(file, argv, environ);\n}\n\nint execl(const char *path, const char *arg, ...) {\n\tint argc = 1; /* Count */\n\tva_list ap;\n\n\t/* Count */\n\tva_start(ap, arg);\n\twhile (va_arg(ap, char *)) argc++;\n\tva_end(ap);\n\n\t/* Copy */\n\tchar * argv[argc+1];\n\tva_start(ap, arg);\n\targv[0] = (char*)arg;\n\tfor (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*);\n\tva_end(ap);\n\n\t/* Exec */\n\treturn execv(path, argv);\n}\n\nint execlp(const char *path, const char *arg, ...) {\n\tint argc = 1; /* Count */\n\tva_list ap;\n\n\t/* Count */\n\tva_start(ap, arg);\n\twhile (va_arg(ap, char *)) argc++;\n\tva_end(ap);\n\n\t/* Copy */\n\tchar * argv[argc+1];\n\tva_start(ap, arg);\n\targv[0] = (char*)arg;\n\tfor (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*);\n\tva_end(ap);\n\n\t/* Exec */\n\treturn execvp(path, argv);\n}\n\nint execle(const char *path, const char *arg, ...) {\n\tint argc = 1; /* Count */\n\tva_list ap;\n\n\t/* Count */\n\tva_start(ap, arg);\n\twhile (va_arg(ap, char *)) argc++;\n\tva_end(ap);\n\n\t/* Copy */\n\tchar * argv[argc+1];\n\tva_start(ap, arg);\n\targv[0] = (char*)arg;\n\tfor (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*);\n\n\tchar ** envp = va_arg(ap, char**);\n\tva_end(ap);\n\n\t/* Exec */\n\treturn execve(path, argv, envp);\n}\n"
  },
  {
    "path": "libc/unistd/exit.c",
    "content": "#include <unistd.h>\n#include <stdlib.h>\n\nvoid exit(int val) {\n\t_handle_atexit();\n\t_exit(val);\n}\n"
  },
  {
    "path": "libc/unistd/fcntl.c",
    "content": "#include <errno.h>\n#include <fcntl.h>\n#include <va_list.h>\n#include <stdint.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(fcntl, SYS_FCNTL, int, int, long);\n\nint fcntl(int fd, int cmd, ...) {\n\tva_list ap;\n\tva_start(ap, cmd);\n\tlong arg = 0;\n\tswitch (cmd) {\n\t\tcase F_SETFD:\n\t\tcase F_SETFL:\n\t\tcase F_DUPFD:\n\t\t\targ = va_arg(ap, int); /* \"taken as an integer of type int\" */\n\t\t\tbreak;\n\t\tcase F_GETLK:\n\t\tcase F_SETLK:\n\t\tcase F_SETLKW:\n\t\t\targ = (long)(uintptr_t)va_arg(ap, struct flock *);\n\t\t\tbreak;\n\t}\n\tva_end(ap);\n\t__sets_errno(syscall_fcntl(fd, cmd, arg));\n}\n"
  },
  {
    "path": "libc/unistd/fork.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(fork, SYS_FORK);\n\npid_t fork(void) {\n\treturn syscall_fork();\n}\n"
  },
  {
    "path": "libc/unistd/fstat.c",
    "content": "#include <unistd.h>\n#include <sys/stat.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(stat, SYS_STAT, int, void *);\n\nint fstat(int file, struct stat *st) {\n\t__sets_errno(syscall_stat(file, st));\n}\n"
  },
  {
    "path": "libc/unistd/getcwd.c",
    "content": "#include <unistd.h>\n#include <stdlib.h>\n#include <string.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(getcwd, SYS_GETCWD, char *, size_t);\n\nchar *getcwd(char *buf, size_t size) {\n\tif (!buf) buf = malloc(size);\n\treturn (char *)syscall_getcwd(buf, size);\n}\n\n"
  },
  {
    "path": "libc/unistd/getegid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(getegid, SYS_GETEGID);\n\ngid_t getegid(void) {\n\treturn syscall_getegid();\n}\n\n"
  },
  {
    "path": "libc/unistd/geteuid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(geteuid, SYS_GETEUID);\n\nuid_t geteuid(void) {\n\treturn syscall_geteuid();\n}\n"
  },
  {
    "path": "libc/unistd/getgid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(getgid, SYS_GETGID);\n\ngid_t getgid(void) {\n\treturn syscall_getgid();\n}\n\n"
  },
  {
    "path": "libc/unistd/getgroups.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(getgroups, SYS_GETGROUPS, int, gid_t *);\n\nint getgroups(int size, gid_t list[]) {\n\t__sets_errno(syscall_getgroups(size, list));\n}\n\n"
  },
  {
    "path": "libc/unistd/getlogin.c",
    "content": "#include <unistd.h>\n#include <pwd.h>\n#include <string.h>\n#include <errno.h>\n#include <sys/stat.h>\n\nstatic char _name[64]; /* NAME_MAX ? */\n\nchar * getlogin(void) {\n\n\tint tty = STDIN_FILENO;\n\tif (!isatty(tty)) {\n\t\ttty = STDOUT_FILENO;\n\t\tif (!isatty(tty)) {\n\t\t\ttty = STDERR_FILENO;\n\t\t\tif (!isatty(tty)) {\n\t\t\t\terrno = ENOTTY;\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t}\n\t}\n\n\tchar * name = ttyname(tty);\n\tif (!name) return NULL;\n\n\t/* Get the owner */\n\tstruct stat statbuf;\n\tstat(name, &statbuf);\n\n\tstruct passwd * passwd = getpwuid(statbuf.st_uid);\n\n\tif (!passwd) return NULL;\n\tif (!passwd->pw_name) return NULL;\n\n\tmemcpy(_name, passwd->pw_name, strlen(passwd->pw_name));\n\treturn _name;\n}\n"
  },
  {
    "path": "libc/unistd/getopt.c",
    "content": "#include <unistd.h>\n#include <getopt.h>\n\nchar * optarg = NULL;\nint optind = 1;\nint opterr = 1;\nint optopt = 0;\n\nint getopt(int argc, char * const argv[], const char * optstring) {\n\treturn getopt_long(argc, argv, optstring, NULL, 0);\n}\n"
  },
  {
    "path": "libc/unistd/getopt_long.c",
    "content": "#include <unistd.h>\n#include <getopt.h>\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n\n/**\n * getopt / getopt_long\n */\n\nint getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) {\n\tstatic char * nextchar = NULL;\n\n\tif (optind >= argc) {\n\t\treturn -1;\n\t}\n\n\tint print_errors = !!opterr;\n\tint was_colon = 0;\n\n\tif (*optstring == ':') {\n\t\tprint_errors = 0;\n\t\toptstring++;\n\t\twas_colon = 1;\n\t}\n\n\tdo {\n\t\tif (!nextchar) {\n\t\t\tnextchar = argv[optind];\n\t\t\tif (*nextchar != '-') {\n\t\t\t\treturn -1;\n\t\t\t} else {\n\t\t\t\tnextchar++;\n\n\t\t\t\tif (*nextchar == '\\0') {\n\t\t\t\t\t/* Special case - is a non-option argument */\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t\tif (*nextchar == '-') {\n\t\t\t\t\tif (nextchar[1] == '\\0') {\n\t\t\t\t\t\t/* End of arguments */\n\t\t\t\t\t\toptind++;\n\t\t\t\t\t\treturn -1;\n\t\t\t\t\t} else if (longopts) {\n\t\t\t\t\t\t/* Scan through options */\n\t\t\t\t\t\tnextchar++;\n\t\t\t\t\t\tchar tmp[strlen(nextchar)+1];\n\t\t\t\t\t\tstrcpy(tmp, nextchar);\n\t\t\t\t\t\tchar * eq = strchr(tmp, '=');\n\t\t\t\t\t\tif (eq) {\n\t\t\t\t\t\t\t*eq = '\\0';\n\t\t\t\t\t\t\toptarg = nextchar + (eq - tmp + 1);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\toptarg = NULL;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tint found = -1;\n\t\t\t\t\t\tfor (int index = 0; longopts[index].name; ++index) {\n\t\t\t\t\t\t\tif (!strcmp(longopts[index].name, tmp)) {\n\t\t\t\t\t\t\t\tfound = index;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (found == -1) {\n\t\t\t\t\t\t\tif (longindex) {\n\t\t\t\t\t\t\t\t*longindex = -1;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (print_errors) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"%s: Unknown long argument: %s\\n\", argv[0], tmp);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnextchar = NULL;\n\t\t\t\t\t\t\toptind++;\n\t\t\t\t\t\t\toptopt = '\\0';\n\t\t\t\t\t\t\treturn '?';\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (longindex) {\n\t\t\t\t\t\t\t\t*longindex = found;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (longopts[found].has_arg == required_argument) {\n\t\t\t\t\t\t\t\tif (!optarg) {\n\t\t\t\t\t\t\t\t\toptarg = argv[optind+1];\n\t\t\t\t\t\t\t\t\toptind++;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tnextchar = NULL;\n\t\t\t\t\t\t\toptind++;\n\t\t\t\t\t\t\tif (!longopts[found].flag) {\n\t\t\t\t\t\t\t\treturn longopts[found].val;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t*longopts[found].flag = longopts[found].val;\n\t\t\t\t\t\t\t\treturn 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t/* else: --foo but not long, see if -: is set, otherwise continue as if - was an option */\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (*nextchar == '\\0') {\n\t\t\tnextchar = NULL;\n\t\t\toptind++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!isalnum(*nextchar) && *nextchar != '?' && *nextchar != '-') {\n\t\t\tif (print_errors) {\n\t\t\t\tfprintf(stderr, \"%s: Invalid option character: %c\\n\", argv[0], *nextchar);\n\t\t\t}\n\t\t\toptopt = *nextchar;\n\t\t\tnextchar++;\n\t\t\treturn '?';\n\t\t}\n\n\t\tchar * opt = strchr(optstring, *nextchar);\n\n\t\tif (!opt) {\n\t\t\tif (print_errors) {\n\t\t\t\tfprintf(stderr, \"%s: Invalid option character: %c\\n\", argv[0], *nextchar);\n\t\t\t}\n\t\t\toptopt = *nextchar;\n\t\t\tnextchar++;\n\t\t\treturn '?';\n\t\t}\n\n\t\tint optout = *nextchar;\n\n\t\tif (opt[1] == ':') {\n\t\t\tif (nextchar[1] != '\\0') {\n\t\t\t\toptarg = &nextchar[1];\n\t\t\t\tnextchar = NULL;\n\t\t\t\toptind++;\n\t\t\t} else {\n\t\t\t\tif (optind + 1 == argc) {\n\t\t\t\t\tif (print_errors) {\n\t\t\t\t\t\tfprintf(stderr, \"%s: Option requires an argument: '%c'\\n\", argv[0], *nextchar);\n\t\t\t\t\t}\n\t\t\t\t\toptopt = *nextchar;\n\t\t\t\t\tnextchar++;\n\t\t\t\t\treturn was_colon ? ':' : '?';\n\t\t\t\t}\n\t\t\t\toptarg = argv[optind+1];\n\t\t\t\toptind += 2;\n\t\t\t\tnextchar = NULL;\n\t\t\t}\n\t\t} else {\n\t\t\tnextchar++;\n\t\t}\n\n\t\treturn optout;\n\n\t} while (optind < argc);\n\n\treturn -1;\n}\n"
  },
  {
    "path": "libc/unistd/getpgrp.c",
    "content": "#include <unistd.h>\n\nint getpgrp() {\n\treturn getpgid(0);\n}\n"
  },
  {
    "path": "libc/unistd/getpid.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(getpid, SYS_GETPID);\n\npid_t getpid(void) {\n\treturn syscall_getpid();\n}\n\npid_t getppid(void) {\n\terrno = ENOTSUP;\n\treturn -1;\n}\n"
  },
  {
    "path": "libc/unistd/getuid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL0(getuid, SYS_GETUID);\n\nuid_t getuid(void) {\n\treturn syscall_getuid();\n}\n\n"
  },
  {
    "path": "libc/unistd/getwd.c",
    "content": "#include <unistd.h>\n\nchar *getwd(char *buf) {\n\treturn getcwd(buf, 256);\n}\n"
  },
  {
    "path": "libc/unistd/hostname.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(sethostname, SYS_SETHOSTNAME, char *);\nDEFN_SYSCALL1(gethostname, SYS_GETHOSTNAME, char *);\n\nint gethostname(char * name, size_t len) {\n\t(void)len; /* TODO */\n\t__sets_errno(syscall_gethostname(name));\n}\n\nint sethostname(const char * name, size_t len) {\n\t(void)len; /* TODO */\n\t__sets_errno(syscall_sethostname((char*)name));\n}\n"
  },
  {
    "path": "libc/unistd/isatty.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <sys/ioctl.h>\n\nint isatty(int fd) {\n\tint dtype = ioctl(fd, IOCTLDTYPE, NULL);\n\tif (dtype == IOCTL_DTYPE_TTY) return 1;\n\terrno = EINVAL;\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/unistd/link.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n\n// TODO:\n//  We have a system call for this?\nint link(const char *old, const char *new) {\n\terrno = ENOTSUP;\n\treturn -1;\n}\n"
  },
  {
    "path": "libc/unistd/lseek.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(seek, SYS_SEEK, int, long, int);\n\noff_t lseek(int file, off_t ptr, int dir) {\n\t__sets_errno(syscall_seek(file,ptr,dir));\n}\n\n"
  },
  {
    "path": "libc/unistd/open.c",
    "content": "#include <unistd.h>\n#include <fcntl.h>\n#include <va_list.h>\n#include <errno.h>\n\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(open,  SYS_OPEN, const char *, int, int);\n\nint open(const char *name, int flags, ...) {\n\tva_list argp;\n\tint mode = 0;\n\tint result;\n\tva_start(argp, flags);\n\tif (flags & O_CREAT) mode = va_arg(argp, int);\n\tva_end(argp);\n\n\tresult = syscall_open(name, flags, mode);\n\tif (result == -1) {\n\t\t/* Not sure this is necessary */\n\t\tif (flags & O_CREAT) {\n\t\t\terrno = EACCES;\n\t\t} else {\n\t\t\terrno = ENOENT;\n\t\t}\n\t} else if (result < 0) {\n\t\terrno = -result;\n\t\tresult = -1;\n\t}\n\treturn result;\n}\n\n"
  },
  {
    "path": "libc/unistd/pathconf.c",
    "content": "#include <errno.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nlong pathconf(const char *path, int name) {\n\tswitch (name) {\n\t\tcase _PC_PATH_MAX:\n\t\t\treturn PATH_MAX;\n\t\tdefault:\n\t\t\terrno = EINVAL;\n\t\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "libc/unistd/pipe.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(pipe, SYS_PIPE, int *);\n\nint pipe(int fildes[2]) {\n\t__sets_errno(syscall_pipe((int *)fildes));\n}\n"
  },
  {
    "path": "libc/unistd/pread.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL4(pread,  SYS_PREAD, int, void *, size_t, off_t);\n\nssize_t pread(int fd, void *buf, size_t count, off_t offset) {\n\t__sets_errno(syscall_pread(fd,buf,count,offset));\n}\n\n"
  },
  {
    "path": "libc/unistd/pwrite.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL4(pwrite, SYS_PWRITE, int, const void *, size_t, off_t);\n\nssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) {\n\t__sets_errno(syscall_pwrite(fd,buf,count,offset));\n}\n\n"
  },
  {
    "path": "libc/unistd/read.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(read,  SYS_READ, int, char *, size_t);\n\nssize_t read(int file, void *ptr, size_t len) {\n\t__sets_errno(syscall_read(file,ptr,len));\n}\n"
  },
  {
    "path": "libc/unistd/readlink.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(readlink, SYS_READLINK, char *, char *, int);\n\nssize_t readlink(const char * name, char * buf, size_t len) {\n\t__sets_errno(syscall_readlink((char*)name, buf, len));\n}\n\n"
  },
  {
    "path": "libc/unistd/rmdir.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <sys/stat.h>\n\nint rmdir(const char *pathname) {\n\t/* XXX: This is subject to TOCTOU issues, but whatever. */\n\tstruct stat st;\n\n\t/* pathname must directly name a directory, not a symlink */\n\tif (lstat(pathname, &st) < 0) return -1;\n\tif (!S_ISDIR(st.st_mode)) {\n\t\terrno = ENOTDIR;\n\t\treturn -1;\n\t}\n\n\t/* our unlink can remove empty directories */\n\treturn unlink(pathname);\n}\n"
  },
  {
    "path": "libc/unistd/sbrk.c",
    "content": "#include <unistd.h>\n#include <syscall_nums.h>\n#include <syscall.h>\n\nDEFN_SYSCALL1(sbrk,  SYS_SBRK, int);\n\nvoid *sbrk(intptr_t increment) {\n\treturn (void *)syscall_sbrk(increment);\n}\n"
  },
  {
    "path": "libc/unistd/setgid.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/types.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(setgid, SYS_SETGID, unsigned int);\n\nint setgid(gid_t uid) {\n\t__sets_errno(syscall_setgid(uid));\n}\n\n"
  },
  {
    "path": "libc/unistd/setgroups.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(setgroups, SYS_SETGROUPS, int, const gid_t *);\n\nint setgroups(int size, const gid_t list[]) {\n\t__sets_errno(syscall_setgroups(size, list));\n}\n\n\n"
  },
  {
    "path": "libc/unistd/setpgid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL2(setpgid, SYS_SETPGID, int, int);\nDEFN_SYSCALL1(getpgid, SYS_GETPGID, int);\n\nint setpgid(pid_t pid, pid_t pgid) {\n\t__sets_errno(syscall_setpgid((int)pid,(int)pgid));\n}\n\npid_t getpgid(pid_t pid) {\n\t__sets_errno(syscall_getpgid((int)pid));\n}\n\n"
  },
  {
    "path": "libc/unistd/setsid.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <errno.h>\n\nDEFN_SYSCALL0(setsid, SYS_SETSID);\n\npid_t setsid(void) {\n\t__sets_errno(syscall_setsid());\n}\n\n"
  },
  {
    "path": "libc/unistd/setuid.c",
    "content": "#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/types.h>\n#include <errno.h>\n\nDEFN_SYSCALL1(setuid, SYS_SETUID, unsigned int);\n\nint setuid(uid_t uid) {\n\t__sets_errno(syscall_setuid(uid));\n}\n"
  },
  {
    "path": "libc/unistd/sleep.c",
    "content": "#include <syscall.h>\n\nunsigned int sleep(unsigned int seconds) {\n\tsyscall_sleep(seconds, 0);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "libc/unistd/stat.c",
    "content": "#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n#include <sys/stat.h>\n#include <string.h>\n\nDEFN_SYSCALL2(statf, SYS_STATF, char *, void *);\nDEFN_SYSCALL2(lstat, SYS_LSTAT, char *, void *);\n\nint stat(const char *file, struct stat *st){\n\tint ret = syscall_statf((char *)file, (void *)st);\n\tif (ret >= 0) {\n\t\treturn ret;\n\t} else {\n\t\terrno = -ret;\n\t\tmemset(st, 0x00, sizeof(struct stat));\n\t\treturn -1;\n\t}\n}\n\nint lstat(const char *path, struct stat *st) {\n\tint ret = syscall_lstat((char *)path, (void *)st);\n\tif (ret >= 0) {\n\t\treturn ret;\n\t} else {\n\t\terrno = -ret;\n\t\tmemset(st, 0x00, sizeof(struct stat));\n\t\treturn -1;\n\t}\n}\n"
  },
  {
    "path": "libc/unistd/statcompat.c",
    "content": "#include <string.h>\n#include <sys/stat.h>\n\nstruct stat_compat  {\n\tint  st_dev;\n\tint  st_ino;\n\tmode_t  st_mode;\n\tnlink_t  st_nlink;\n\tuid_t  st_uid;\n\tgid_t  st_gid;\n\tdev_t  st_rdev;\n\toff_t  st_size;\n\ttime_t __st_atime;\n\ttime_t __st_mtime;\n\ttime_t __st_ctime;\n\tblksize_t  st_blksize;\n\tblkcnt_t  st_blocks;\n};\n\nstatic void convert(const struct stat *nst, struct stat_compat *ost) {\n\tost->st_dev = nst->st_dev;\n\tost->st_ino = nst->st_ino;\n\tost->st_mode = nst->st_mode;\n\tost->st_nlink = nst->st_nlink;\n\tost->st_uid = nst->st_uid;\n\tost->st_gid = nst->st_gid;\n\tost->st_rdev = nst->st_rdev;\n\tost->st_size = nst->st_size;\n\n\tost->__st_atime = nst->st_atim.tv_sec;\n\tost->__st_mtime = nst->st_mtim.tv_sec;\n\tost->__st_ctime = nst->st_ctim.tv_sec;\n\tost->st_blksize = nst->st_blksize;\n\tost->st_blocks = nst->st_blocks;\n}\n\nint __stat_compat(const char *, struct stat_compat *) __asm__(\"stat\");\nint __stat_compat(const char *path, struct stat_compat *st) {\n\tstruct stat nst;\n\tint ret = stat(path, &nst);\n\tif (ret < 0) return ret;\n\tconvert(&nst,st);\n\treturn ret;\n}\n\nint __lstat_compat(const char *, struct stat_compat *) __asm__(\"lstat\");\nint __lstat_compat(const char *path, struct stat_compat *st) {\n\tstruct stat nst;\n\tint ret = lstat(path, &nst);\n\tif (ret < 0) return ret;\n\tconvert(&nst,st);\n\treturn ret;\n}\n\nint __fstat_compat(int, struct stat_compat *) __asm__(\"fstat\");\nint __fstat_compat(int fd, struct stat_compat *st) {\n\tstruct stat nst;\n\tint ret = fstat(fd, &nst);\n\tif (ret < 0) return ret;\n\tconvert(&nst,st);\n\treturn ret;\n}\n"
  },
  {
    "path": "libc/unistd/symlink.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(symlink, SYS_SYMLINK, const char *, const char *);\n\nint symlink(const char *target, const char *name) {\n\t__sets_errno(syscall_symlink(target, name));\n}\n\n"
  },
  {
    "path": "libc/unistd/sync.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <sys/types.h>\n\nvoid sync(void) {\n\t/* TODO */\n}\n\n"
  },
  {
    "path": "libc/unistd/truncate.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(truncate, SYS_TRUNCATE, char *, off_t);\n\nint truncate(const char * path, off_t length) {\n\t__sets_errno(syscall_truncate((char*)path, length));\n}\n\nDEFN_SYSCALL2(ftruncate, SYS_FTRUNCATE, int, off_t);\n\nint ftruncate(int fd, off_t length) {\n\t__sets_errno(syscall_ftruncate(fd, length));\n}\n\n"
  },
  {
    "path": "libc/unistd/ttyname.c",
    "content": "#include <stdint.h>\n#include <unistd.h>\n#include <errno.h>\n#include <sys/ioctl.h>\n\nstatic char _tty_name[30]; /* only needs to hold /dev/pty/ttyXXXXXXX */\n\nchar * ttyname(int fd) {\n\n\tif (!isatty(fd)) {\n\t\terrno = ENOTTY;\n\t\treturn NULL;\n\t}\n\n\tioctl(fd, IOCTLTTYNAME, _tty_name);\n\n\treturn _tty_name;\n}\n\nint ttyname_r(int fd, char * buf, size_t buflen) {\n\tif (!isatty(fd)) return ENOTTY;\n\tif (buflen < 30) return ERANGE;\n\tioctl(fd, IOCTLTTYNAME, buf);\n\treturn 0;\n}\n"
  },
  {
    "path": "libc/unistd/umask.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(umask, SYS_UMASK, int);\n\nmode_t umask(mode_t mask) {\n\treturn syscall_umask(mask);\n}\n"
  },
  {
    "path": "libc/unistd/unlink.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL1(unlink, SYS_UNLINK, char *);\n\nint unlink(const char * pathname) {\n\t__sets_errno(syscall_unlink((char *)pathname));\n}\n"
  },
  {
    "path": "libc/unistd/usleep.c",
    "content": "#include <unistd.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL2(sleep,  SYS_SLEEP, unsigned long, unsigned long);\n\nint usleep(useconds_t usec) {\n\tsyscall_sleep((usec / 10000) / 1000, (usec / 10000) % 1000);\n\treturn 0;\n}\n\n"
  },
  {
    "path": "libc/unistd/write.c",
    "content": "#include <unistd.h>\n#include <errno.h>\n#include <syscall.h>\n#include <syscall_nums.h>\n\nDEFN_SYSCALL3(write, SYS_WRITE, int, char *, size_t);\n\nssize_t write(int file, const void *ptr, size_t len) {\n\t__sets_errno(syscall_write(file,(char *)ptr,len));\n}\n"
  },
  {
    "path": "libc/utime/utime.c",
    "content": "#include <utime.h>\n#include <errno.h>\n\nint utime(const char *filename, const struct utimbuf *times) {\n\t/* Unimplemented */\n\terrno = ENOTSUP;\n\treturn -1;\n}\n"
  },
  {
    "path": "libc/wchar/wcscat.c",
    "content": "#include <wchar.h>\n#include <stdio.h>\n\nwchar_t * wcscat(wchar_t *dest, const wchar_t *src) {\n\twchar_t * end = dest;\n\twhile (*end != 0) {\n\t\t++end;\n\t}\n\twhile (*src) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t}\n\t*end = 0;\n\treturn dest;\n}\n\nwchar_t * wcsncat(wchar_t *dest, const wchar_t * src, size_t n) {\n\twchar_t * end = dest;\n\tsize_t c = 0;\n\twhile (*end != 0) {\n\t\t++end;\n\t}\n\twhile (*src && c < n) {\n\t\t*end = *src;\n\t\tend++;\n\t\tsrc++;\n\t\tc++;\n\t}\n\t*end = 0;\n\treturn dest;\n}\n"
  },
  {
    "path": "libc/wchar/wcscmp.c",
    "content": "#include <wchar.h>\n\nint wcscmp(const wchar_t *l, const wchar_t *r) {\n\tfor (; *l == *r && *l; l++, r++);\n\treturn *(unsigned int *)l - *(unsigned int *)r;\n}\n\nint wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n) {\n\tif (n == 0) return 0;\n\n\twhile (n-- && *s1 == *s2) {\n\t\tif (!n || !*s1) break;\n\t\ts1++;\n\t\ts2++;\n\t}\n\treturn (*s1) - (*s2);\n}\n"
  },
  {
    "path": "libc/wchar/wcscpy.c",
    "content": "#include <wchar.h>\n\nwchar_t * wcscpy(wchar_t * restrict dest, const wchar_t * restrict src) {\n\twchar_t * out = dest;\n\tfor (; (*dest=*src); src++, dest++);\n\treturn out;\n}\n"
  },
  {
    "path": "libc/wchar/wcslen.c",
    "content": "#include <wchar.h>\n\nsize_t wcslen(const wchar_t * s) {\n\tsize_t out = 0;\n\twhile (*s) {\n\t\tout++;\n\t\ts++;\n\t}\n\treturn out;\n}\n"
  },
  {
    "path": "libc/wchar/wcsncpy.c",
    "content": "#include <wchar.h>\n\nwchar_t * wcsncpy(wchar_t * dest, const wchar_t * src, size_t n) {\n\twchar_t * out = dest;\n\twhile (n > 0) {\n\t\t*dest = *src;\n\t\tif (!*src) break;\n\t\tdest++;\n\t\tsrc++;\n\t\tn--;\n\t}\n\treturn out;\n}\n"
  },
  {
    "path": "libc/wchar/wcstok.c",
    "content": "#include <wchar.h>\n\nsize_t wcsspn(const wchar_t * wcs, const wchar_t * accept) {\n\tsize_t out = 0;\n\n\twhile (*wcs) {\n\t\tint good = 0;\n\t\tfor (const wchar_t * a = accept; *a; ++a) {\n\t\t\tif (*wcs == *a) {\n\t\t\t\tgood = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!good) break;\n\t\tout++;\n\t\twcs++;\n\t}\n\n\treturn out;\n}\n\nwchar_t *wcspbrk(const wchar_t *wcs, const wchar_t *accept) {\n\twhile (*wcs) {\n\t\tfor (const wchar_t * a = accept; *a; ++a) {\n\t\t\tif (*wcs == *a) {\n\t\t\t\treturn (wchar_t *)wcs;\n\t\t\t}\n\t\t}\n\t\twcs++;\n\t}\n\treturn NULL;\n}\n\nwchar_t * wcschr(const wchar_t *wcs, wchar_t wc) {\n\twhile (*wcs != wc && *wcs != 0) {\n\t\twcs++;\n\t}\n\tif (!*wcs) return NULL;\n\treturn (wchar_t *)wcs;\n}\n\nwchar_t * wcsrchr(const wchar_t *wcs, wchar_t wc) {\n\twchar_t * last = NULL;\n\twhile (*wcs != 0) {\n\t\tif (*wcs == wc) {\n\t\t\tlast = (wchar_t *)wcs;\n\t\t}\n\t\twcs++;\n\t}\n\treturn last;\n}\n\nwchar_t * wcstok(wchar_t * str, const wchar_t * delim, wchar_t ** saveptr) {\n\twchar_t * token;\n\tif (str == NULL) {\n\t\tstr = *saveptr;\n\t}\n\tstr += wcsspn(str, delim);\n\tif (*str == '\\0') {\n\t\t*saveptr = str;\n\t\treturn NULL;\n\t}\n\ttoken = str;\n\tstr = wcspbrk(token, delim);\n\tif (str == NULL) {\n\t\t*saveptr = (wchar_t *)wcschr(token, '\\0');\n\t} else {\n\t\t*str = '\\0';\n\t\t*saveptr = str + 1;\n\t}\n\treturn token;\n}\n\n\n"
  },
  {
    "path": "libc/wchar/wcstol.c",
    "content": "#include <stdlib.h>\n#include <ctype.h>\n#include <limits.h>\n#include <wchar.h>\n#include <errno.h>\n\nstatic int is_valid(int base, wchar_t c) {\n\tif (c < '0') return 0;\n\tif (base <= 10) {\n\t\treturn c < ('0' + (wchar_t)base);\n\t}\n\n\tif (c >= 'a' && c < 'a' + ((wchar_t)base - 10)) return 1;\n\tif (c >= 'A' && c < 'A' + ((wchar_t)base - 10)) return 1;\n\tif (c >= '0' && c <= '9') return 1;\n\treturn 0;\n}\n\nstatic int convert_digit(wchar_t c) {\n\tif (c >= '0' && c <= '9') {\n\t\treturn c - '0';\n\t}\n\tif (c >= 'a' && c <= 'z') {\n\t\treturn c - 'a' + 0xa;\n\t}\n\tif (c >= 'A' && c <= 'Z') {\n\t\treturn c - 'A' + 0xa;\n\t}\n\treturn 0;\n}\n\n#define strtox(max, type) \\\n\tif (base < 0 || base == 1 || base > 36) { \\\n\t\terrno = EINVAL; \\\n\t\treturn max; \\\n\t} \\\n\twhile (*nptr && isspace(*nptr)) nptr++; \\\n\tint sign = 1; \\\n\tif (*nptr == '-') { \\\n\t\tsign = -1; \\\n\t\tnptr++; \\\n\t} else if (*nptr == '+') { \\\n\t\tnptr++; \\\n\t} \\\n\tif (base == 16) { \\\n\t\tif (*nptr == '0') { \\\n\t\t\tnptr++; \\\n\t\t\tif (*nptr == 'x') { \\\n\t\t\t\tnptr++; \\\n\t\t\t} \\\n\t\t} \\\n\t} \\\n\tif (base == 0) { \\\n\t\tif (*nptr == '0') { \\\n\t\t\tbase = 8; \\\n\t\t\tnptr++; \\\n\t\t\tif (*nptr == 'x') { \\\n\t\t\t\tbase = 16; \\\n\t\t\t\tnptr++; \\\n\t\t\t} \\\n\t\t} else { \\\n\t\t\tbase = 10; \\\n\t\t} \\\n\t} \\\n\ttype val = 0; \\\n\twhile (is_valid(base, *nptr)) { \\\n\t\tval *= base; \\\n\t\tval += convert_digit(*nptr); \\\n\t\tnptr++; \\\n\t} \\\n\tif (endptr) { \\\n\t\t*endptr = (wchar_t *)nptr; \\\n\t} \\\n\tif (sign == -1) { \\\n\t\treturn -val; \\\n\t} else { \\\n\t\treturn val; \\\n\t}\n\nunsigned long int wcstoul(const wchar_t *nptr, wchar_t **endptr, int base) {\n\tstrtox(ULONG_MAX, unsigned long int);\n}\n\nunsigned long long int wcstoull(const char *nptr, wchar_t **endptr, int base) {\n\tstrtox(ULLONG_MAX, unsigned long int);\n}\n\nlong int wcstol(const wchar_t *nptr, wchar_t **endptr, int base) {\n\tstrtox(LONG_MAX, unsigned long int);\n}\n\nlong long int wcstoll(const wchar_t *nptr, wchar_t **endptr, int base) {\n\tstrtox(LLONG_MAX, unsigned long long int);\n}\n\n"
  },
  {
    "path": "libc/wchar/wcwidth.c",
    "content": "/* Generated by util/gen_wcwidth.krk */\n#include <wchar.h>\n\nint wcwidth(wchar_t wc) {\n\tif (wc == 0) return 0;\n\telse if (wc < 0x20) return -1;\n\telse if (wc < 0x7f) return 1;\n\telse if (wc < 0xa0) return -1;\n\telse if (wc < 0x300) return 1;\n\telse if (wc < 0x370) return 0;\n\telse if (wc < 0x378) return 1;\n\telse if (wc < 0x37a) return -1;\n\telse if (wc < 0x380) return 1;\n\telse if (wc < 0x384) return -1;\n\telse if (wc < 0x38b) return 1;\n\telse if (wc < 0x38c) return -1;\n\telse if (wc < 0x38d) return 1;\n\telse if (wc < 0x38e) return -1;\n\telse if (wc < 0x3a2) return 1;\n\telse if (wc < 0x3a3) return -1;\n\telse if (wc < 0x483) return 1;\n\telse if (wc < 0x48a) return 0;\n\telse if (wc < 0x530) return 1;\n\telse if (wc < 0x531) return -1;\n\telse if (wc < 0x557) return 1;\n\telse if (wc < 0x559) return -1;\n\telse if (wc < 0x58b) return 1;\n\telse if (wc < 0x58d) return -1;\n\telse if (wc < 0x590) return 1;\n\telse if (wc < 0x591) return -1;\n\telse if (wc < 0x5be) return 0;\n\telse if (wc < 0x5bf) return 1;\n\telse if (wc < 0x5c0) return 0;\n\telse if (wc < 0x5c1) return 1;\n\telse if (wc < 0x5c3) return 0;\n\telse if (wc < 0x5c4) return 1;\n\telse if (wc < 0x5c6) return 0;\n\telse if (wc < 0x5c7) return 1;\n\telse if (wc < 0x5c8) return 0;\n\telse if (wc < 0x5d0) return -1;\n\telse if (wc < 0x5eb) return 1;\n\telse if (wc < 0x5ef) return -1;\n\telse if (wc < 0x5f5) return 1;\n\telse if (wc < 0x600) return -1;\n\telse if (wc < 0x606) return 0;\n\telse if (wc < 0x610) return 1;\n\telse if (wc < 0x61b) return 0;\n\telse if (wc < 0x61c) return 1;\n\telse if (wc < 0x61d) return 0;\n\telse if (wc < 0x64b) return 1;\n\telse if (wc < 0x660) return 0;\n\telse if (wc < 0x670) return 1;\n\telse if (wc < 0x671) return 0;\n\telse if (wc < 0x6d6) return 1;\n\telse if (wc < 0x6de) return 0;\n\telse if (wc < 0x6df) return 1;\n\telse if (wc < 0x6e5) return 0;\n\telse if (wc < 0x6e7) return 1;\n\telse if (wc < 0x6e9) return 0;\n\telse if (wc < 0x6ea) return 1;\n\telse if (wc < 0x6ee) return 0;\n\telse if (wc < 0x70e) return 1;\n\telse if (wc < 0x70f) return -1;\n\telse if (wc < 0x710) return 0;\n\telse if (wc < 0x711) return 1;\n\telse if (wc < 0x712) return 0;\n\telse if (wc < 0x730) return 1;\n\telse if (wc < 0x74b) return 0;\n\telse if (wc < 0x74d) return -1;\n\telse if (wc < 0x7a6) return 1;\n\telse if (wc < 0x7b1) return 0;\n\telse if (wc < 0x7b2) return 1;\n\telse if (wc < 0x7c0) return -1;\n\telse if (wc < 0x7eb) return 1;\n\telse if (wc < 0x7f4) return 0;\n\telse if (wc < 0x7fb) return 1;\n\telse if (wc < 0x7fd) return -1;\n\telse if (wc < 0x7fe) return 0;\n\telse if (wc < 0x816) return 1;\n\telse if (wc < 0x81a) return 0;\n\telse if (wc < 0x81b) return 1;\n\telse if (wc < 0x824) return 0;\n\telse if (wc < 0x825) return 1;\n\telse if (wc < 0x828) return 0;\n\telse if (wc < 0x829) return 1;\n\telse if (wc < 0x82e) return 0;\n\telse if (wc < 0x830) return -1;\n\telse if (wc < 0x83f) return 1;\n\telse if (wc < 0x840) return -1;\n\telse if (wc < 0x859) return 1;\n\telse if (wc < 0x85c) return 0;\n\telse if (wc < 0x85e) return -1;\n\telse if (wc < 0x85f) return 1;\n\telse if (wc < 0x860) return -1;\n\telse if (wc < 0x86b) return 1;\n\telse if (wc < 0x870) return -1;\n\telse if (wc < 0x88f) return 1;\n\telse if (wc < 0x890) return -1;\n\telse if (wc < 0x892) return 0;\n\telse if (wc < 0x898) return -1;\n\telse if (wc < 0x8a0) return 0;\n\telse if (wc < 0x8ca) return 1;\n\telse if (wc < 0x903) return 0;\n\telse if (wc < 0x93a) return 1;\n\telse if (wc < 0x93b) return 0;\n\telse if (wc < 0x93c) return 1;\n\telse if (wc < 0x93d) return 0;\n\telse if (wc < 0x941) return 1;\n\telse if (wc < 0x949) return 0;\n\telse if (wc < 0x94d) return 1;\n\telse if (wc < 0x94e) return 0;\n\telse if (wc < 0x951) return 1;\n\telse if (wc < 0x958) return 0;\n\telse if (wc < 0x962) return 1;\n\telse if (wc < 0x964) return 0;\n\telse if (wc < 0x981) return 1;\n\telse if (wc < 0x982) return 0;\n\telse if (wc < 0x984) return 1;\n\telse if (wc < 0x985) return -1;\n\telse if (wc < 0x98d) return 1;\n\telse if (wc < 0x98f) return -1;\n\telse if (wc < 0x991) return 1;\n\telse if (wc < 0x993) return -1;\n\telse if (wc < 0x9a9) return 1;\n\telse if (wc < 0x9aa) return -1;\n\telse if (wc < 0x9b1) return 1;\n\telse if (wc < 0x9b2) return -1;\n\telse if (wc < 0x9b3) return 1;\n\telse if (wc < 0x9b6) return -1;\n\telse if (wc < 0x9ba) return 1;\n\telse if (wc < 0x9bc) return -1;\n\telse if (wc < 0x9bd) return 0;\n\telse if (wc < 0x9c1) return 1;\n\telse if (wc < 0x9c5) return 0;\n\telse if (wc < 0x9c7) return -1;\n\telse if (wc < 0x9c9) return 1;\n\telse if (wc < 0x9cb) return -1;\n\telse if (wc < 0x9cd) return 1;\n\telse if (wc < 0x9ce) return 0;\n\telse if (wc < 0x9cf) return 1;\n\telse if (wc < 0x9d7) return -1;\n\telse if (wc < 0x9d8) return 1;\n\telse if (wc < 0x9dc) return -1;\n\telse if (wc < 0x9de) return 1;\n\telse if (wc < 0x9df) return -1;\n\telse if (wc < 0x9e2) return 1;\n\telse if (wc < 0x9e4) return 0;\n\telse if (wc < 0x9e6) return -1;\n\telse if (wc < 0x9fe) return 1;\n\telse if (wc < 0x9ff) return 0;\n\telse if (wc < 0xa01) return -1;\n\telse if (wc < 0xa03) return 0;\n\telse if (wc < 0xa04) return 1;\n\telse if (wc < 0xa05) return -1;\n\telse if (wc < 0xa0b) return 1;\n\telse if (wc < 0xa0f) return -1;\n\telse if (wc < 0xa11) return 1;\n\telse if (wc < 0xa13) return -1;\n\telse if (wc < 0xa29) return 1;\n\telse if (wc < 0xa2a) return -1;\n\telse if (wc < 0xa31) return 1;\n\telse if (wc < 0xa32) return -1;\n\telse if (wc < 0xa34) return 1;\n\telse if (wc < 0xa35) return -1;\n\telse if (wc < 0xa37) return 1;\n\telse if (wc < 0xa38) return -1;\n\telse if (wc < 0xa3a) return 1;\n\telse if (wc < 0xa3c) return -1;\n\telse if (wc < 0xa3d) return 0;\n\telse if (wc < 0xa3e) return -1;\n\telse if (wc < 0xa41) return 1;\n\telse if (wc < 0xa43) return 0;\n\telse if (wc < 0xa47) return -1;\n\telse if (wc < 0xa49) return 0;\n\telse if (wc < 0xa4b) return -1;\n\telse if (wc < 0xa4e) return 0;\n\telse if (wc < 0xa51) return -1;\n\telse if (wc < 0xa52) return 0;\n\telse if (wc < 0xa59) return -1;\n\telse if (wc < 0xa5d) return 1;\n\telse if (wc < 0xa5e) return -1;\n\telse if (wc < 0xa5f) return 1;\n\telse if (wc < 0xa66) return -1;\n\telse if (wc < 0xa70) return 1;\n\telse if (wc < 0xa72) return 0;\n\telse if (wc < 0xa75) return 1;\n\telse if (wc < 0xa76) return 0;\n\telse if (wc < 0xa77) return 1;\n\telse if (wc < 0xa81) return -1;\n\telse if (wc < 0xa83) return 0;\n\telse if (wc < 0xa84) return 1;\n\telse if (wc < 0xa85) return -1;\n\telse if (wc < 0xa8e) return 1;\n\telse if (wc < 0xa8f) return -1;\n\telse if (wc < 0xa92) return 1;\n\telse if (wc < 0xa93) return -1;\n\telse if (wc < 0xaa9) return 1;\n\telse if (wc < 0xaaa) return -1;\n\telse if (wc < 0xab1) return 1;\n\telse if (wc < 0xab2) return -1;\n\telse if (wc < 0xab4) return 1;\n\telse if (wc < 0xab5) return -1;\n\telse if (wc < 0xaba) return 1;\n\telse if (wc < 0xabc) return -1;\n\telse if (wc < 0xabd) return 0;\n\telse if (wc < 0xac1) return 1;\n\telse if (wc < 0xac6) return 0;\n\telse if (wc < 0xac7) return -1;\n\telse if (wc < 0xac9) return 0;\n\telse if (wc < 0xaca) return 1;\n\telse if (wc < 0xacb) return -1;\n\telse if (wc < 0xacd) return 1;\n\telse if (wc < 0xace) return 0;\n\telse if (wc < 0xad0) return -1;\n\telse if (wc < 0xad1) return 1;\n\telse if (wc < 0xae0) return -1;\n\telse if (wc < 0xae2) return 1;\n\telse if (wc < 0xae4) return 0;\n\telse if (wc < 0xae6) return -1;\n\telse if (wc < 0xaf2) return 1;\n\telse if (wc < 0xaf9) return -1;\n\telse if (wc < 0xafa) return 1;\n\telse if (wc < 0xb00) return 0;\n\telse if (wc < 0xb01) return -1;\n\telse if (wc < 0xb02) return 0;\n\telse if (wc < 0xb04) return 1;\n\telse if (wc < 0xb05) return -1;\n\telse if (wc < 0xb0d) return 1;\n\telse if (wc < 0xb0f) return -1;\n\telse if (wc < 0xb11) return 1;\n\telse if (wc < 0xb13) return -1;\n\telse if (wc < 0xb29) return 1;\n\telse if (wc < 0xb2a) return -1;\n\telse if (wc < 0xb31) return 1;\n\telse if (wc < 0xb32) return -1;\n\telse if (wc < 0xb34) return 1;\n\telse if (wc < 0xb35) return -1;\n\telse if (wc < 0xb3a) return 1;\n\telse if (wc < 0xb3c) return -1;\n\telse if (wc < 0xb3d) return 0;\n\telse if (wc < 0xb3f) return 1;\n\telse if (wc < 0xb40) return 0;\n\telse if (wc < 0xb41) return 1;\n\telse if (wc < 0xb45) return 0;\n\telse if (wc < 0xb47) return -1;\n\telse if (wc < 0xb49) return 1;\n\telse if (wc < 0xb4b) return -1;\n\telse if (wc < 0xb4d) return 1;\n\telse if (wc < 0xb4e) return 0;\n\telse if (wc < 0xb55) return -1;\n\telse if (wc < 0xb57) return 0;\n\telse if (wc < 0xb58) return 1;\n\telse if (wc < 0xb5c) return -1;\n\telse if (wc < 0xb5e) return 1;\n\telse if (wc < 0xb5f) return -1;\n\telse if (wc < 0xb62) return 1;\n\telse if (wc < 0xb64) return 0;\n\telse if (wc < 0xb66) return -1;\n\telse if (wc < 0xb78) return 1;\n\telse if (wc < 0xb82) return -1;\n\telse if (wc < 0xb83) return 0;\n\telse if (wc < 0xb84) return 1;\n\telse if (wc < 0xb85) return -1;\n\telse if (wc < 0xb8b) return 1;\n\telse if (wc < 0xb8e) return -1;\n\telse if (wc < 0xb91) return 1;\n\telse if (wc < 0xb92) return -1;\n\telse if (wc < 0xb96) return 1;\n\telse if (wc < 0xb99) return -1;\n\telse if (wc < 0xb9b) return 1;\n\telse if (wc < 0xb9c) return -1;\n\telse if (wc < 0xb9d) return 1;\n\telse if (wc < 0xb9e) return -1;\n\telse if (wc < 0xba0) return 1;\n\telse if (wc < 0xba3) return -1;\n\telse if (wc < 0xba5) return 1;\n\telse if (wc < 0xba8) return -1;\n\telse if (wc < 0xbab) return 1;\n\telse if (wc < 0xbae) return -1;\n\telse if (wc < 0xbba) return 1;\n\telse if (wc < 0xbbe) return -1;\n\telse if (wc < 0xbc0) return 1;\n\telse if (wc < 0xbc1) return 0;\n\telse if (wc < 0xbc3) return 1;\n\telse if (wc < 0xbc6) return -1;\n\telse if (wc < 0xbc9) return 1;\n\telse if (wc < 0xbca) return -1;\n\telse if (wc < 0xbcd) return 1;\n\telse if (wc < 0xbce) return 0;\n\telse if (wc < 0xbd0) return -1;\n\telse if (wc < 0xbd1) return 1;\n\telse if (wc < 0xbd7) return -1;\n\telse if (wc < 0xbd8) return 1;\n\telse if (wc < 0xbe6) return -1;\n\telse if (wc < 0xbfb) return 1;\n\telse if (wc < 0xc00) return -1;\n\telse if (wc < 0xc01) return 0;\n\telse if (wc < 0xc04) return 1;\n\telse if (wc < 0xc05) return 0;\n\telse if (wc < 0xc0d) return 1;\n\telse if (wc < 0xc0e) return -1;\n\telse if (wc < 0xc11) return 1;\n\telse if (wc < 0xc12) return -1;\n\telse if (wc < 0xc29) return 1;\n\telse if (wc < 0xc2a) return -1;\n\telse if (wc < 0xc3a) return 1;\n\telse if (wc < 0xc3c) return -1;\n\telse if (wc < 0xc3d) return 0;\n\telse if (wc < 0xc3e) return 1;\n\telse if (wc < 0xc41) return 0;\n\telse if (wc < 0xc45) return 1;\n\telse if (wc < 0xc46) return -1;\n\telse if (wc < 0xc49) return 0;\n\telse if (wc < 0xc4a) return -1;\n\telse if (wc < 0xc4e) return 0;\n\telse if (wc < 0xc55) return -1;\n\telse if (wc < 0xc57) return 0;\n\telse if (wc < 0xc58) return -1;\n\telse if (wc < 0xc5b) return 1;\n\telse if (wc < 0xc5d) return -1;\n\telse if (wc < 0xc5e) return 1;\n\telse if (wc < 0xc60) return -1;\n\telse if (wc < 0xc62) return 1;\n\telse if (wc < 0xc64) return 0;\n\telse if (wc < 0xc66) return -1;\n\telse if (wc < 0xc70) return 1;\n\telse if (wc < 0xc77) return -1;\n\telse if (wc < 0xc81) return 1;\n\telse if (wc < 0xc82) return 0;\n\telse if (wc < 0xc8d) return 1;\n\telse if (wc < 0xc8e) return -1;\n\telse if (wc < 0xc91) return 1;\n\telse if (wc < 0xc92) return -1;\n\telse if (wc < 0xca9) return 1;\n\telse if (wc < 0xcaa) return -1;\n\telse if (wc < 0xcb4) return 1;\n\telse if (wc < 0xcb5) return -1;\n\telse if (wc < 0xcba) return 1;\n\telse if (wc < 0xcbc) return -1;\n\telse if (wc < 0xcbd) return 0;\n\telse if (wc < 0xcbf) return 1;\n\telse if (wc < 0xcc0) return 0;\n\telse if (wc < 0xcc5) return 1;\n\telse if (wc < 0xcc6) return -1;\n\telse if (wc < 0xcc7) return 0;\n\telse if (wc < 0xcc9) return 1;\n\telse if (wc < 0xcca) return -1;\n\telse if (wc < 0xccc) return 1;\n\telse if (wc < 0xcce) return 0;\n\telse if (wc < 0xcd5) return -1;\n\telse if (wc < 0xcd7) return 1;\n\telse if (wc < 0xcdd) return -1;\n\telse if (wc < 0xcdf) return 1;\n\telse if (wc < 0xce0) return -1;\n\telse if (wc < 0xce2) return 1;\n\telse if (wc < 0xce4) return 0;\n\telse if (wc < 0xce6) return -1;\n\telse if (wc < 0xcf0) return 1;\n\telse if (wc < 0xcf1) return -1;\n\telse if (wc < 0xcf4) return 1;\n\telse if (wc < 0xd00) return -1;\n\telse if (wc < 0xd02) return 0;\n\telse if (wc < 0xd0d) return 1;\n\telse if (wc < 0xd0e) return -1;\n\telse if (wc < 0xd11) return 1;\n\telse if (wc < 0xd12) return -1;\n\telse if (wc < 0xd3b) return 1;\n\telse if (wc < 0xd3d) return 0;\n\telse if (wc < 0xd41) return 1;\n\telse if (wc < 0xd45) return 0;\n\telse if (wc < 0xd46) return -1;\n\telse if (wc < 0xd49) return 1;\n\telse if (wc < 0xd4a) return -1;\n\telse if (wc < 0xd4d) return 1;\n\telse if (wc < 0xd4e) return 0;\n\telse if (wc < 0xd50) return 1;\n\telse if (wc < 0xd54) return -1;\n\telse if (wc < 0xd62) return 1;\n\telse if (wc < 0xd64) return 0;\n\telse if (wc < 0xd66) return -1;\n\telse if (wc < 0xd80) return 1;\n\telse if (wc < 0xd81) return -1;\n\telse if (wc < 0xd82) return 0;\n\telse if (wc < 0xd84) return 1;\n\telse if (wc < 0xd85) return -1;\n\telse if (wc < 0xd97) return 1;\n\telse if (wc < 0xd9a) return -1;\n\telse if (wc < 0xdb2) return 1;\n\telse if (wc < 0xdb3) return -1;\n\telse if (wc < 0xdbc) return 1;\n\telse if (wc < 0xdbd) return -1;\n\telse if (wc < 0xdbe) return 1;\n\telse if (wc < 0xdc0) return -1;\n\telse if (wc < 0xdc7) return 1;\n\telse if (wc < 0xdca) return -1;\n\telse if (wc < 0xdcb) return 0;\n\telse if (wc < 0xdcf) return -1;\n\telse if (wc < 0xdd2) return 1;\n\telse if (wc < 0xdd5) return 0;\n\telse if (wc < 0xdd6) return -1;\n\telse if (wc < 0xdd7) return 0;\n\telse if (wc < 0xdd8) return -1;\n\telse if (wc < 0xde0) return 1;\n\telse if (wc < 0xde6) return -1;\n\telse if (wc < 0xdf0) return 1;\n\telse if (wc < 0xdf2) return -1;\n\telse if (wc < 0xdf5) return 1;\n\telse if (wc < 0xe01) return -1;\n\telse if (wc < 0xe31) return 1;\n\telse if (wc < 0xe32) return 0;\n\telse if (wc < 0xe34) return 1;\n\telse if (wc < 0xe3b) return 0;\n\telse if (wc < 0xe3f) return -1;\n\telse if (wc < 0xe47) return 1;\n\telse if (wc < 0xe4f) return 0;\n\telse if (wc < 0xe5c) return 1;\n\telse if (wc < 0xe81) return -1;\n\telse if (wc < 0xe83) return 1;\n\telse if (wc < 0xe84) return -1;\n\telse if (wc < 0xe85) return 1;\n\telse if (wc < 0xe86) return -1;\n\telse if (wc < 0xe8b) return 1;\n\telse if (wc < 0xe8c) return -1;\n\telse if (wc < 0xea4) return 1;\n\telse if (wc < 0xea5) return -1;\n\telse if (wc < 0xea6) return 1;\n\telse if (wc < 0xea7) return -1;\n\telse if (wc < 0xeb1) return 1;\n\telse if (wc < 0xeb2) return 0;\n\telse if (wc < 0xeb4) return 1;\n\telse if (wc < 0xebd) return 0;\n\telse if (wc < 0xebe) return 1;\n\telse if (wc < 0xec0) return -1;\n\telse if (wc < 0xec5) return 1;\n\telse if (wc < 0xec6) return -1;\n\telse if (wc < 0xec7) return 1;\n\telse if (wc < 0xec8) return -1;\n\telse if (wc < 0xecf) return 0;\n\telse if (wc < 0xed0) return -1;\n\telse if (wc < 0xeda) return 1;\n\telse if (wc < 0xedc) return -1;\n\telse if (wc < 0xee0) return 1;\n\telse if (wc < 0xf00) return -1;\n\telse if (wc < 0xf18) return 1;\n\telse if (wc < 0xf1a) return 0;\n\telse if (wc < 0xf35) return 1;\n\telse if (wc < 0xf36) return 0;\n\telse if (wc < 0xf37) return 1;\n\telse if (wc < 0xf38) return 0;\n\telse if (wc < 0xf39) return 1;\n\telse if (wc < 0xf3a) return 0;\n\telse if (wc < 0xf48) return 1;\n\telse if (wc < 0xf49) return -1;\n\telse if (wc < 0xf6d) return 1;\n\telse if (wc < 0xf71) return -1;\n\telse if (wc < 0xf7f) return 0;\n\telse if (wc < 0xf80) return 1;\n\telse if (wc < 0xf85) return 0;\n\telse if (wc < 0xf86) return 1;\n\telse if (wc < 0xf88) return 0;\n\telse if (wc < 0xf8d) return 1;\n\telse if (wc < 0xf98) return 0;\n\telse if (wc < 0xf99) return -1;\n\telse if (wc < 0xfbd) return 0;\n\telse if (wc < 0xfbe) return -1;\n\telse if (wc < 0xfc6) return 1;\n\telse if (wc < 0xfc7) return 0;\n\telse if (wc < 0xfcd) return 1;\n\telse if (wc < 0xfce) return -1;\n\telse if (wc < 0xfdb) return 1;\n\telse if (wc < 0x1000) return -1;\n\telse if (wc < 0x102d) return 1;\n\telse if (wc < 0x1031) return 0;\n\telse if (wc < 0x1032) return 1;\n\telse if (wc < 0x1038) return 0;\n\telse if (wc < 0x1039) return 1;\n\telse if (wc < 0x103b) return 0;\n\telse if (wc < 0x103d) return 1;\n\telse if (wc < 0x103f) return 0;\n\telse if (wc < 0x1058) return 1;\n\telse if (wc < 0x105a) return 0;\n\telse if (wc < 0x105e) return 1;\n\telse if (wc < 0x1061) return 0;\n\telse if (wc < 0x1071) return 1;\n\telse if (wc < 0x1075) return 0;\n\telse if (wc < 0x1082) return 1;\n\telse if (wc < 0x1083) return 0;\n\telse if (wc < 0x1085) return 1;\n\telse if (wc < 0x1087) return 0;\n\telse if (wc < 0x108d) return 1;\n\telse if (wc < 0x108e) return 0;\n\telse if (wc < 0x109d) return 1;\n\telse if (wc < 0x109e) return 0;\n\telse if (wc < 0x10c6) return 1;\n\telse if (wc < 0x10c7) return -1;\n\telse if (wc < 0x10c8) return 1;\n\telse if (wc < 0x10cd) return -1;\n\telse if (wc < 0x10ce) return 1;\n\telse if (wc < 0x10d0) return -1;\n\telse if (wc < 0x1100) return 1;\n\telse if (wc < 0x1160) return 2;\n\telse if (wc < 0x1200) return 0;\n\telse if (wc < 0x1249) return 1;\n\telse if (wc < 0x124a) return -1;\n\telse if (wc < 0x124e) return 1;\n\telse if (wc < 0x1250) return -1;\n\telse if (wc < 0x1257) return 1;\n\telse if (wc < 0x1258) return -1;\n\telse if (wc < 0x1259) return 1;\n\telse if (wc < 0x125a) return -1;\n\telse if (wc < 0x125e) return 1;\n\telse if (wc < 0x1260) return -1;\n\telse if (wc < 0x1289) return 1;\n\telse if (wc < 0x128a) return -1;\n\telse if (wc < 0x128e) return 1;\n\telse if (wc < 0x1290) return -1;\n\telse if (wc < 0x12b1) return 1;\n\telse if (wc < 0x12b2) return -1;\n\telse if (wc < 0x12b6) return 1;\n\telse if (wc < 0x12b8) return -1;\n\telse if (wc < 0x12bf) return 1;\n\telse if (wc < 0x12c0) return -1;\n\telse if (wc < 0x12c1) return 1;\n\telse if (wc < 0x12c2) return -1;\n\telse if (wc < 0x12c6) return 1;\n\telse if (wc < 0x12c8) return -1;\n\telse if (wc < 0x12d7) return 1;\n\telse if (wc < 0x12d8) return -1;\n\telse if (wc < 0x1311) return 1;\n\telse if (wc < 0x1312) return -1;\n\telse if (wc < 0x1316) return 1;\n\telse if (wc < 0x1318) return -1;\n\telse if (wc < 0x135b) return 1;\n\telse if (wc < 0x135d) return -1;\n\telse if (wc < 0x1360) return 0;\n\telse if (wc < 0x137d) return 1;\n\telse if (wc < 0x1380) return -1;\n\telse if (wc < 0x139a) return 1;\n\telse if (wc < 0x13a0) return -1;\n\telse if (wc < 0x13f6) return 1;\n\telse if (wc < 0x13f8) return -1;\n\telse if (wc < 0x13fe) return 1;\n\telse if (wc < 0x1400) return -1;\n\telse if (wc < 0x169d) return 1;\n\telse if (wc < 0x16a0) return -1;\n\telse if (wc < 0x16f9) return 1;\n\telse if (wc < 0x1700) return -1;\n\telse if (wc < 0x1712) return 1;\n\telse if (wc < 0x1715) return 0;\n\telse if (wc < 0x1716) return 1;\n\telse if (wc < 0x171f) return -1;\n\telse if (wc < 0x1732) return 1;\n\telse if (wc < 0x1734) return 0;\n\telse if (wc < 0x1737) return 1;\n\telse if (wc < 0x1740) return -1;\n\telse if (wc < 0x1752) return 1;\n\telse if (wc < 0x1754) return 0;\n\telse if (wc < 0x1760) return -1;\n\telse if (wc < 0x176d) return 1;\n\telse if (wc < 0x176e) return -1;\n\telse if (wc < 0x1771) return 1;\n\telse if (wc < 0x1772) return -1;\n\telse if (wc < 0x1774) return 0;\n\telse if (wc < 0x1780) return -1;\n\telse if (wc < 0x17b4) return 1;\n\telse if (wc < 0x17b6) return 0;\n\telse if (wc < 0x17b7) return 1;\n\telse if (wc < 0x17be) return 0;\n\telse if (wc < 0x17c6) return 1;\n\telse if (wc < 0x17c7) return 0;\n\telse if (wc < 0x17c9) return 1;\n\telse if (wc < 0x17d4) return 0;\n\telse if (wc < 0x17dd) return 1;\n\telse if (wc < 0x17de) return 0;\n\telse if (wc < 0x17e0) return -1;\n\telse if (wc < 0x17ea) return 1;\n\telse if (wc < 0x17f0) return -1;\n\telse if (wc < 0x17fa) return 1;\n\telse if (wc < 0x1800) return -1;\n\telse if (wc < 0x180b) return 1;\n\telse if (wc < 0x1810) return 0;\n\telse if (wc < 0x181a) return 1;\n\telse if (wc < 0x1820) return -1;\n\telse if (wc < 0x1879) return 1;\n\telse if (wc < 0x1880) return -1;\n\telse if (wc < 0x1885) return 1;\n\telse if (wc < 0x1887) return 0;\n\telse if (wc < 0x18a9) return 1;\n\telse if (wc < 0x18aa) return 0;\n\telse if (wc < 0x18ab) return 1;\n\telse if (wc < 0x18b0) return -1;\n\telse if (wc < 0x18f6) return 1;\n\telse if (wc < 0x1900) return -1;\n\telse if (wc < 0x191f) return 1;\n\telse if (wc < 0x1920) return -1;\n\telse if (wc < 0x1923) return 0;\n\telse if (wc < 0x1927) return 1;\n\telse if (wc < 0x1929) return 0;\n\telse if (wc < 0x192c) return 1;\n\telse if (wc < 0x1930) return -1;\n\telse if (wc < 0x1932) return 1;\n\telse if (wc < 0x1933) return 0;\n\telse if (wc < 0x1939) return 1;\n\telse if (wc < 0x193c) return 0;\n\telse if (wc < 0x1940) return -1;\n\telse if (wc < 0x1941) return 1;\n\telse if (wc < 0x1944) return -1;\n\telse if (wc < 0x196e) return 1;\n\telse if (wc < 0x1970) return -1;\n\telse if (wc < 0x1975) return 1;\n\telse if (wc < 0x1980) return -1;\n\telse if (wc < 0x19ac) return 1;\n\telse if (wc < 0x19b0) return -1;\n\telse if (wc < 0x19ca) return 1;\n\telse if (wc < 0x19d0) return -1;\n\telse if (wc < 0x19db) return 1;\n\telse if (wc < 0x19de) return -1;\n\telse if (wc < 0x1a17) return 1;\n\telse if (wc < 0x1a19) return 0;\n\telse if (wc < 0x1a1b) return 1;\n\telse if (wc < 0x1a1c) return 0;\n\telse if (wc < 0x1a1e) return -1;\n\telse if (wc < 0x1a56) return 1;\n\telse if (wc < 0x1a57) return 0;\n\telse if (wc < 0x1a58) return 1;\n\telse if (wc < 0x1a5f) return 0;\n\telse if (wc < 0x1a60) return -1;\n\telse if (wc < 0x1a61) return 0;\n\telse if (wc < 0x1a62) return 1;\n\telse if (wc < 0x1a63) return 0;\n\telse if (wc < 0x1a65) return 1;\n\telse if (wc < 0x1a6d) return 0;\n\telse if (wc < 0x1a73) return 1;\n\telse if (wc < 0x1a7d) return 0;\n\telse if (wc < 0x1a7f) return -1;\n\telse if (wc < 0x1a80) return 0;\n\telse if (wc < 0x1a8a) return 1;\n\telse if (wc < 0x1a90) return -1;\n\telse if (wc < 0x1a9a) return 1;\n\telse if (wc < 0x1aa0) return -1;\n\telse if (wc < 0x1aae) return 1;\n\telse if (wc < 0x1ab0) return -1;\n\telse if (wc < 0x1acf) return 0;\n\telse if (wc < 0x1b00) return -1;\n\telse if (wc < 0x1b04) return 0;\n\telse if (wc < 0x1b34) return 1;\n\telse if (wc < 0x1b35) return 0;\n\telse if (wc < 0x1b36) return 1;\n\telse if (wc < 0x1b3b) return 0;\n\telse if (wc < 0x1b3c) return 1;\n\telse if (wc < 0x1b3d) return 0;\n\telse if (wc < 0x1b42) return 1;\n\telse if (wc < 0x1b43) return 0;\n\telse if (wc < 0x1b4d) return 1;\n\telse if (wc < 0x1b50) return -1;\n\telse if (wc < 0x1b6b) return 1;\n\telse if (wc < 0x1b74) return 0;\n\telse if (wc < 0x1b7f) return 1;\n\telse if (wc < 0x1b80) return -1;\n\telse if (wc < 0x1b82) return 0;\n\telse if (wc < 0x1ba2) return 1;\n\telse if (wc < 0x1ba6) return 0;\n\telse if (wc < 0x1ba8) return 1;\n\telse if (wc < 0x1baa) return 0;\n\telse if (wc < 0x1bab) return 1;\n\telse if (wc < 0x1bae) return 0;\n\telse if (wc < 0x1be6) return 1;\n\telse if (wc < 0x1be7) return 0;\n\telse if (wc < 0x1be8) return 1;\n\telse if (wc < 0x1bea) return 0;\n\telse if (wc < 0x1bed) return 1;\n\telse if (wc < 0x1bee) return 0;\n\telse if (wc < 0x1bef) return 1;\n\telse if (wc < 0x1bf2) return 0;\n\telse if (wc < 0x1bf4) return 1;\n\telse if (wc < 0x1bfc) return -1;\n\telse if (wc < 0x1c2c) return 1;\n\telse if (wc < 0x1c34) return 0;\n\telse if (wc < 0x1c36) return 1;\n\telse if (wc < 0x1c38) return 0;\n\telse if (wc < 0x1c3b) return -1;\n\telse if (wc < 0x1c4a) return 1;\n\telse if (wc < 0x1c4d) return -1;\n\telse if (wc < 0x1c89) return 1;\n\telse if (wc < 0x1c90) return -1;\n\telse if (wc < 0x1cbb) return 1;\n\telse if (wc < 0x1cbd) return -1;\n\telse if (wc < 0x1cc8) return 1;\n\telse if (wc < 0x1cd0) return -1;\n\telse if (wc < 0x1cd3) return 0;\n\telse if (wc < 0x1cd4) return 1;\n\telse if (wc < 0x1ce1) return 0;\n\telse if (wc < 0x1ce2) return 1;\n\telse if (wc < 0x1ce9) return 0;\n\telse if (wc < 0x1ced) return 1;\n\telse if (wc < 0x1cee) return 0;\n\telse if (wc < 0x1cf4) return 1;\n\telse if (wc < 0x1cf5) return 0;\n\telse if (wc < 0x1cf8) return 1;\n\telse if (wc < 0x1cfa) return 0;\n\telse if (wc < 0x1cfb) return 1;\n\telse if (wc < 0x1d00) return -1;\n\telse if (wc < 0x1dc0) return 1;\n\telse if (wc < 0x1e00) return 0;\n\telse if (wc < 0x1f16) return 1;\n\telse if (wc < 0x1f18) return -1;\n\telse if (wc < 0x1f1e) return 1;\n\telse if (wc < 0x1f20) return -1;\n\telse if (wc < 0x1f46) return 1;\n\telse if (wc < 0x1f48) return -1;\n\telse if (wc < 0x1f4e) return 1;\n\telse if (wc < 0x1f50) return -1;\n\telse if (wc < 0x1f58) return 1;\n\telse if (wc < 0x1f59) return -1;\n\telse if (wc < 0x1f5a) return 1;\n\telse if (wc < 0x1f5b) return -1;\n\telse if (wc < 0x1f5c) return 1;\n\telse if (wc < 0x1f5d) return -1;\n\telse if (wc < 0x1f5e) return 1;\n\telse if (wc < 0x1f5f) return -1;\n\telse if (wc < 0x1f7e) return 1;\n\telse if (wc < 0x1f80) return -1;\n\telse if (wc < 0x1fb5) return 1;\n\telse if (wc < 0x1fb6) return -1;\n\telse if (wc < 0x1fc5) return 1;\n\telse if (wc < 0x1fc6) return -1;\n\telse if (wc < 0x1fd4) return 1;\n\telse if (wc < 0x1fd6) return -1;\n\telse if (wc < 0x1fdc) return 1;\n\telse if (wc < 0x1fdd) return -1;\n\telse if (wc < 0x1ff0) return 1;\n\telse if (wc < 0x1ff2) return -1;\n\telse if (wc < 0x1ff5) return 1;\n\telse if (wc < 0x1ff6) return -1;\n\telse if (wc < 0x1fff) return 1;\n\telse if (wc < 0x2000) return -1;\n\telse if (wc < 0x200b) return 1;\n\telse if (wc < 0x2010) return 0;\n\telse if (wc < 0x202a) return 1;\n\telse if (wc < 0x202f) return 0;\n\telse if (wc < 0x2060) return 1;\n\telse if (wc < 0x2065) return 0;\n\telse if (wc < 0x2066) return -1;\n\telse if (wc < 0x2070) return 0;\n\telse if (wc < 0x2072) return 1;\n\telse if (wc < 0x2074) return -1;\n\telse if (wc < 0x208f) return 1;\n\telse if (wc < 0x2090) return -1;\n\telse if (wc < 0x209d) return 1;\n\telse if (wc < 0x20a0) return -1;\n\telse if (wc < 0x20c1) return 1;\n\telse if (wc < 0x20d0) return -1;\n\telse if (wc < 0x20f1) return 0;\n\telse if (wc < 0x2100) return -1;\n\telse if (wc < 0x218c) return 1;\n\telse if (wc < 0x2190) return -1;\n\telse if (wc < 0x231a) return 1;\n\telse if (wc < 0x231c) return 2;\n\telse if (wc < 0x2329) return 1;\n\telse if (wc < 0x232b) return 2;\n\telse if (wc < 0x23e9) return 1;\n\telse if (wc < 0x23ed) return 2;\n\telse if (wc < 0x23f0) return 1;\n\telse if (wc < 0x23f1) return 2;\n\telse if (wc < 0x23f3) return 1;\n\telse if (wc < 0x23f4) return 2;\n\telse if (wc < 0x2427) return 1;\n\telse if (wc < 0x2440) return -1;\n\telse if (wc < 0x244b) return 1;\n\telse if (wc < 0x2460) return -1;\n\telse if (wc < 0x25fd) return 1;\n\telse if (wc < 0x25ff) return 2;\n\telse if (wc < 0x2614) return 1;\n\telse if (wc < 0x2616) return 2;\n\telse if (wc < 0x2648) return 1;\n\telse if (wc < 0x2654) return 2;\n\telse if (wc < 0x267f) return 1;\n\telse if (wc < 0x2680) return 2;\n\telse if (wc < 0x2693) return 1;\n\telse if (wc < 0x2694) return 2;\n\telse if (wc < 0x26a1) return 1;\n\telse if (wc < 0x26a2) return 2;\n\telse if (wc < 0x26aa) return 1;\n\telse if (wc < 0x26ac) return 2;\n\telse if (wc < 0x26bd) return 1;\n\telse if (wc < 0x26bf) return 2;\n\telse if (wc < 0x26c4) return 1;\n\telse if (wc < 0x26c6) return 2;\n\telse if (wc < 0x26ce) return 1;\n\telse if (wc < 0x26cf) return 2;\n\telse if (wc < 0x26d4) return 1;\n\telse if (wc < 0x26d5) return 2;\n\telse if (wc < 0x26ea) return 1;\n\telse if (wc < 0x26eb) return 2;\n\telse if (wc < 0x26f2) return 1;\n\telse if (wc < 0x26f4) return 2;\n\telse if (wc < 0x26f5) return 1;\n\telse if (wc < 0x26f6) return 2;\n\telse if (wc < 0x26fa) return 1;\n\telse if (wc < 0x26fb) return 2;\n\telse if (wc < 0x26fd) return 1;\n\telse if (wc < 0x26fe) return 2;\n\telse if (wc < 0x2705) return 1;\n\telse if (wc < 0x2706) return 2;\n\telse if (wc < 0x270a) return 1;\n\telse if (wc < 0x270c) return 2;\n\telse if (wc < 0x2728) return 1;\n\telse if (wc < 0x2729) return 2;\n\telse if (wc < 0x274c) return 1;\n\telse if (wc < 0x274d) return 2;\n\telse if (wc < 0x274e) return 1;\n\telse if (wc < 0x274f) return 2;\n\telse if (wc < 0x2753) return 1;\n\telse if (wc < 0x2756) return 2;\n\telse if (wc < 0x2757) return 1;\n\telse if (wc < 0x2758) return 2;\n\telse if (wc < 0x2795) return 1;\n\telse if (wc < 0x2798) return 2;\n\telse if (wc < 0x27b0) return 1;\n\telse if (wc < 0x27b1) return 2;\n\telse if (wc < 0x27bf) return 1;\n\telse if (wc < 0x27c0) return 2;\n\telse if (wc < 0x2b1b) return 1;\n\telse if (wc < 0x2b1d) return 2;\n\telse if (wc < 0x2b50) return 1;\n\telse if (wc < 0x2b51) return 2;\n\telse if (wc < 0x2b55) return 1;\n\telse if (wc < 0x2b56) return 2;\n\telse if (wc < 0x2b74) return 1;\n\telse if (wc < 0x2b76) return -1;\n\telse if (wc < 0x2b96) return 1;\n\telse if (wc < 0x2b97) return -1;\n\telse if (wc < 0x2cef) return 1;\n\telse if (wc < 0x2cf2) return 0;\n\telse if (wc < 0x2cf4) return 1;\n\telse if (wc < 0x2cf9) return -1;\n\telse if (wc < 0x2d26) return 1;\n\telse if (wc < 0x2d27) return -1;\n\telse if (wc < 0x2d28) return 1;\n\telse if (wc < 0x2d2d) return -1;\n\telse if (wc < 0x2d2e) return 1;\n\telse if (wc < 0x2d30) return -1;\n\telse if (wc < 0x2d68) return 1;\n\telse if (wc < 0x2d6f) return -1;\n\telse if (wc < 0x2d71) return 1;\n\telse if (wc < 0x2d7f) return -1;\n\telse if (wc < 0x2d80) return 0;\n\telse if (wc < 0x2d97) return 1;\n\telse if (wc < 0x2da0) return -1;\n\telse if (wc < 0x2da7) return 1;\n\telse if (wc < 0x2da8) return -1;\n\telse if (wc < 0x2daf) return 1;\n\telse if (wc < 0x2db0) return -1;\n\telse if (wc < 0x2db7) return 1;\n\telse if (wc < 0x2db8) return -1;\n\telse if (wc < 0x2dbf) return 1;\n\telse if (wc < 0x2dc0) return -1;\n\telse if (wc < 0x2dc7) return 1;\n\telse if (wc < 0x2dc8) return -1;\n\telse if (wc < 0x2dcf) return 1;\n\telse if (wc < 0x2dd0) return -1;\n\telse if (wc < 0x2dd7) return 1;\n\telse if (wc < 0x2dd8) return -1;\n\telse if (wc < 0x2ddf) return 1;\n\telse if (wc < 0x2de0) return -1;\n\telse if (wc < 0x2e00) return 0;\n\telse if (wc < 0x2e5e) return 1;\n\telse if (wc < 0x2e80) return -1;\n\telse if (wc < 0x2e9a) return 2;\n\telse if (wc < 0x2e9b) return -1;\n\telse if (wc < 0x2ef4) return 2;\n\telse if (wc < 0x2f00) return -1;\n\telse if (wc < 0x2fd6) return 2;\n\telse if (wc < 0x2ff0) return -1;\n\telse if (wc < 0x302a) return 2;\n\telse if (wc < 0x302e) return 0;\n\telse if (wc < 0x303f) return 2;\n\telse if (wc < 0x3040) return 1;\n\telse if (wc < 0x3041) return -1;\n\telse if (wc < 0x3097) return 2;\n\telse if (wc < 0x3099) return -1;\n\telse if (wc < 0x309b) return 0;\n\telse if (wc < 0x3100) return 2;\n\telse if (wc < 0x3105) return -1;\n\telse if (wc < 0x3130) return 2;\n\telse if (wc < 0x3131) return -1;\n\telse if (wc < 0x318f) return 2;\n\telse if (wc < 0x3190) return -1;\n\telse if (wc < 0x31e4) return 2;\n\telse if (wc < 0x31ef) return -1;\n\telse if (wc < 0x321f) return 2;\n\telse if (wc < 0x3220) return -1;\n\telse if (wc < 0x3248) return 2;\n\telse if (wc < 0x3250) return 1;\n\telse if (wc < 0x4dc0) return 2;\n\telse if (wc < 0x4e00) return 1;\n\telse if (wc < 0xa48d) return 2;\n\telse if (wc < 0xa490) return -1;\n\telse if (wc < 0xa4c7) return 2;\n\telse if (wc < 0xa4d0) return -1;\n\telse if (wc < 0xa62c) return 1;\n\telse if (wc < 0xa640) return -1;\n\telse if (wc < 0xa66f) return 1;\n\telse if (wc < 0xa673) return 0;\n\telse if (wc < 0xa674) return 1;\n\telse if (wc < 0xa67e) return 0;\n\telse if (wc < 0xa69e) return 1;\n\telse if (wc < 0xa6a0) return 0;\n\telse if (wc < 0xa6f0) return 1;\n\telse if (wc < 0xa6f2) return 0;\n\telse if (wc < 0xa6f8) return 1;\n\telse if (wc < 0xa700) return -1;\n\telse if (wc < 0xa7cb) return 1;\n\telse if (wc < 0xa7d0) return -1;\n\telse if (wc < 0xa7d2) return 1;\n\telse if (wc < 0xa7d3) return -1;\n\telse if (wc < 0xa7d4) return 1;\n\telse if (wc < 0xa7d5) return -1;\n\telse if (wc < 0xa7da) return 1;\n\telse if (wc < 0xa7f2) return -1;\n\telse if (wc < 0xa802) return 1;\n\telse if (wc < 0xa803) return 0;\n\telse if (wc < 0xa806) return 1;\n\telse if (wc < 0xa807) return 0;\n\telse if (wc < 0xa80b) return 1;\n\telse if (wc < 0xa80c) return 0;\n\telse if (wc < 0xa825) return 1;\n\telse if (wc < 0xa827) return 0;\n\telse if (wc < 0xa82c) return 1;\n\telse if (wc < 0xa82d) return 0;\n\telse if (wc < 0xa830) return -1;\n\telse if (wc < 0xa83a) return 1;\n\telse if (wc < 0xa840) return -1;\n\telse if (wc < 0xa878) return 1;\n\telse if (wc < 0xa880) return -1;\n\telse if (wc < 0xa8c4) return 1;\n\telse if (wc < 0xa8c6) return 0;\n\telse if (wc < 0xa8ce) return -1;\n\telse if (wc < 0xa8da) return 1;\n\telse if (wc < 0xa8e0) return -1;\n\telse if (wc < 0xa8f2) return 0;\n\telse if (wc < 0xa8ff) return 1;\n\telse if (wc < 0xa900) return 0;\n\telse if (wc < 0xa926) return 1;\n\telse if (wc < 0xa92e) return 0;\n\telse if (wc < 0xa947) return 1;\n\telse if (wc < 0xa952) return 0;\n\telse if (wc < 0xa954) return 1;\n\telse if (wc < 0xa95f) return -1;\n\telse if (wc < 0xa960) return 1;\n\telse if (wc < 0xa97d) return 2;\n\telse if (wc < 0xa980) return -1;\n\telse if (wc < 0xa983) return 0;\n\telse if (wc < 0xa9b3) return 1;\n\telse if (wc < 0xa9b4) return 0;\n\telse if (wc < 0xa9b6) return 1;\n\telse if (wc < 0xa9ba) return 0;\n\telse if (wc < 0xa9bc) return 1;\n\telse if (wc < 0xa9be) return 0;\n\telse if (wc < 0xa9ce) return 1;\n\telse if (wc < 0xa9cf) return -1;\n\telse if (wc < 0xa9da) return 1;\n\telse if (wc < 0xa9de) return -1;\n\telse if (wc < 0xa9e5) return 1;\n\telse if (wc < 0xa9e6) return 0;\n\telse if (wc < 0xa9ff) return 1;\n\telse if (wc < 0xaa00) return -1;\n\telse if (wc < 0xaa29) return 1;\n\telse if (wc < 0xaa2f) return 0;\n\telse if (wc < 0xaa31) return 1;\n\telse if (wc < 0xaa33) return 0;\n\telse if (wc < 0xaa35) return 1;\n\telse if (wc < 0xaa37) return 0;\n\telse if (wc < 0xaa40) return -1;\n\telse if (wc < 0xaa43) return 1;\n\telse if (wc < 0xaa44) return 0;\n\telse if (wc < 0xaa4c) return 1;\n\telse if (wc < 0xaa4d) return 0;\n\telse if (wc < 0xaa4e) return 1;\n\telse if (wc < 0xaa50) return -1;\n\telse if (wc < 0xaa5a) return 1;\n\telse if (wc < 0xaa5c) return -1;\n\telse if (wc < 0xaa7c) return 1;\n\telse if (wc < 0xaa7d) return 0;\n\telse if (wc < 0xaab0) return 1;\n\telse if (wc < 0xaab1) return 0;\n\telse if (wc < 0xaab2) return 1;\n\telse if (wc < 0xaab5) return 0;\n\telse if (wc < 0xaab7) return 1;\n\telse if (wc < 0xaab9) return 0;\n\telse if (wc < 0xaabe) return 1;\n\telse if (wc < 0xaac0) return 0;\n\telse if (wc < 0xaac1) return 1;\n\telse if (wc < 0xaac2) return 0;\n\telse if (wc < 0xaac3) return 1;\n\telse if (wc < 0xaadb) return -1;\n\telse if (wc < 0xaaec) return 1;\n\telse if (wc < 0xaaee) return 0;\n\telse if (wc < 0xaaf6) return 1;\n\telse if (wc < 0xaaf7) return 0;\n\telse if (wc < 0xab01) return -1;\n\telse if (wc < 0xab07) return 1;\n\telse if (wc < 0xab09) return -1;\n\telse if (wc < 0xab0f) return 1;\n\telse if (wc < 0xab11) return -1;\n\telse if (wc < 0xab17) return 1;\n\telse if (wc < 0xab20) return -1;\n\telse if (wc < 0xab27) return 1;\n\telse if (wc < 0xab28) return -1;\n\telse if (wc < 0xab2f) return 1;\n\telse if (wc < 0xab30) return -1;\n\telse if (wc < 0xab6c) return 1;\n\telse if (wc < 0xab70) return -1;\n\telse if (wc < 0xabe5) return 1;\n\telse if (wc < 0xabe6) return 0;\n\telse if (wc < 0xabe8) return 1;\n\telse if (wc < 0xabe9) return 0;\n\telse if (wc < 0xabed) return 1;\n\telse if (wc < 0xabee) return 0;\n\telse if (wc < 0xabf0) return -1;\n\telse if (wc < 0xabfa) return 1;\n\telse if (wc < 0xac00) return -1;\n\telse if (wc < 0xd7a4) return 2;\n\telse if (wc < 0xd7b0) return -1;\n\telse if (wc < 0xd7c7) return 1;\n\telse if (wc < 0xd7cb) return -1;\n\telse if (wc < 0xd7fc) return 1;\n\telse if (wc < 0xe000) return -1;\n\telse if (wc < 0xf900) return 1;\n\telse if (wc < 0xfb00) return 2;\n\telse if (wc < 0xfb07) return 1;\n\telse if (wc < 0xfb13) return -1;\n\telse if (wc < 0xfb18) return 1;\n\telse if (wc < 0xfb1d) return -1;\n\telse if (wc < 0xfb1e) return 1;\n\telse if (wc < 0xfb1f) return 0;\n\telse if (wc < 0xfb37) return 1;\n\telse if (wc < 0xfb38) return -1;\n\telse if (wc < 0xfb3d) return 1;\n\telse if (wc < 0xfb3e) return -1;\n\telse if (wc < 0xfb3f) return 1;\n\telse if (wc < 0xfb40) return -1;\n\telse if (wc < 0xfb42) return 1;\n\telse if (wc < 0xfb43) return -1;\n\telse if (wc < 0xfb45) return 1;\n\telse if (wc < 0xfb46) return -1;\n\telse if (wc < 0xfbc3) return 1;\n\telse if (wc < 0xfbd3) return -1;\n\telse if (wc < 0xfd90) return 1;\n\telse if (wc < 0xfd92) return -1;\n\telse if (wc < 0xfdc8) return 1;\n\telse if (wc < 0xfdcf) return -1;\n\telse if (wc < 0xfdd0) return 1;\n\telse if (wc < 0xfdf0) return -1;\n\telse if (wc < 0xfe00) return 1;\n\telse if (wc < 0xfe10) return 0;\n\telse if (wc < 0xfe1a) return 2;\n\telse if (wc < 0xfe20) return -1;\n\telse if (wc < 0xfe30) return 0;\n\telse if (wc < 0xfe53) return 2;\n\telse if (wc < 0xfe54) return -1;\n\telse if (wc < 0xfe67) return 2;\n\telse if (wc < 0xfe68) return -1;\n\telse if (wc < 0xfe6c) return 2;\n\telse if (wc < 0xfe70) return -1;\n\telse if (wc < 0xfe75) return 1;\n\telse if (wc < 0xfe76) return -1;\n\telse if (wc < 0xfefd) return 1;\n\telse if (wc < 0xfeff) return -1;\n\telse if (wc < 0xff00) return 0;\n\telse if (wc < 0xff01) return -1;\n\telse if (wc < 0xff61) return 2;\n\telse if (wc < 0xffbf) return 1;\n\telse if (wc < 0xffc2) return -1;\n\telse if (wc < 0xffc8) return 1;\n\telse if (wc < 0xffca) return -1;\n\telse if (wc < 0xffd0) return 1;\n\telse if (wc < 0xffd2) return -1;\n\telse if (wc < 0xffd8) return 1;\n\telse if (wc < 0xffda) return -1;\n\telse if (wc < 0xffdd) return 1;\n\telse if (wc < 0xffe0) return -1;\n\telse if (wc < 0xffe7) return 2;\n\telse if (wc < 0xffe8) return -1;\n\telse if (wc < 0xffef) return 1;\n\telse if (wc < 0xfff9) return -1;\n\telse if (wc < 0xfffc) return 0;\n\telse if (wc < 0xfffe) return 1;\n\telse if (wc < 0x10000) return -1;\n\telse if (wc < 0x1000c) return 1;\n\telse if (wc < 0x1000d) return -1;\n\telse if (wc < 0x10027) return 1;\n\telse if (wc < 0x10028) return -1;\n\telse if (wc < 0x1003b) return 1;\n\telse if (wc < 0x1003c) return -1;\n\telse if (wc < 0x1003e) return 1;\n\telse if (wc < 0x1003f) return -1;\n\telse if (wc < 0x1004e) return 1;\n\telse if (wc < 0x10050) return -1;\n\telse if (wc < 0x1005e) return 1;\n\telse if (wc < 0x10080) return -1;\n\telse if (wc < 0x100fb) return 1;\n\telse if (wc < 0x10100) return -1;\n\telse if (wc < 0x10103) return 1;\n\telse if (wc < 0x10107) return -1;\n\telse if (wc < 0x10134) return 1;\n\telse if (wc < 0x10137) return -1;\n\telse if (wc < 0x1018f) return 1;\n\telse if (wc < 0x10190) return -1;\n\telse if (wc < 0x1019d) return 1;\n\telse if (wc < 0x101a0) return -1;\n\telse if (wc < 0x101a1) return 1;\n\telse if (wc < 0x101d0) return -1;\n\telse if (wc < 0x101fd) return 1;\n\telse if (wc < 0x101fe) return 0;\n\telse if (wc < 0x10280) return -1;\n\telse if (wc < 0x1029d) return 1;\n\telse if (wc < 0x102a0) return -1;\n\telse if (wc < 0x102d1) return 1;\n\telse if (wc < 0x102e0) return -1;\n\telse if (wc < 0x102e1) return 0;\n\telse if (wc < 0x102fc) return 1;\n\telse if (wc < 0x10300) return -1;\n\telse if (wc < 0x10324) return 1;\n\telse if (wc < 0x1032d) return -1;\n\telse if (wc < 0x1034b) return 1;\n\telse if (wc < 0x10350) return -1;\n\telse if (wc < 0x10376) return 1;\n\telse if (wc < 0x1037b) return 0;\n\telse if (wc < 0x10380) return -1;\n\telse if (wc < 0x1039e) return 1;\n\telse if (wc < 0x1039f) return -1;\n\telse if (wc < 0x103c4) return 1;\n\telse if (wc < 0x103c8) return -1;\n\telse if (wc < 0x103d6) return 1;\n\telse if (wc < 0x10400) return -1;\n\telse if (wc < 0x1049e) return 1;\n\telse if (wc < 0x104a0) return -1;\n\telse if (wc < 0x104aa) return 1;\n\telse if (wc < 0x104b0) return -1;\n\telse if (wc < 0x104d4) return 1;\n\telse if (wc < 0x104d8) return -1;\n\telse if (wc < 0x104fc) return 1;\n\telse if (wc < 0x10500) return -1;\n\telse if (wc < 0x10528) return 1;\n\telse if (wc < 0x10530) return -1;\n\telse if (wc < 0x10564) return 1;\n\telse if (wc < 0x1056f) return -1;\n\telse if (wc < 0x1057b) return 1;\n\telse if (wc < 0x1057c) return -1;\n\telse if (wc < 0x1058b) return 1;\n\telse if (wc < 0x1058c) return -1;\n\telse if (wc < 0x10593) return 1;\n\telse if (wc < 0x10594) return -1;\n\telse if (wc < 0x10596) return 1;\n\telse if (wc < 0x10597) return -1;\n\telse if (wc < 0x105a2) return 1;\n\telse if (wc < 0x105a3) return -1;\n\telse if (wc < 0x105b2) return 1;\n\telse if (wc < 0x105b3) return -1;\n\telse if (wc < 0x105ba) return 1;\n\telse if (wc < 0x105bb) return -1;\n\telse if (wc < 0x105bd) return 1;\n\telse if (wc < 0x10600) return -1;\n\telse if (wc < 0x10737) return 1;\n\telse if (wc < 0x10740) return -1;\n\telse if (wc < 0x10756) return 1;\n\telse if (wc < 0x10760) return -1;\n\telse if (wc < 0x10768) return 1;\n\telse if (wc < 0x10780) return -1;\n\telse if (wc < 0x10786) return 1;\n\telse if (wc < 0x10787) return -1;\n\telse if (wc < 0x107b1) return 1;\n\telse if (wc < 0x107b2) return -1;\n\telse if (wc < 0x107bb) return 1;\n\telse if (wc < 0x10800) return -1;\n\telse if (wc < 0x10806) return 1;\n\telse if (wc < 0x10808) return -1;\n\telse if (wc < 0x10809) return 1;\n\telse if (wc < 0x1080a) return -1;\n\telse if (wc < 0x10836) return 1;\n\telse if (wc < 0x10837) return -1;\n\telse if (wc < 0x10839) return 1;\n\telse if (wc < 0x1083c) return -1;\n\telse if (wc < 0x1083d) return 1;\n\telse if (wc < 0x1083f) return -1;\n\telse if (wc < 0x10856) return 1;\n\telse if (wc < 0x10857) return -1;\n\telse if (wc < 0x1089f) return 1;\n\telse if (wc < 0x108a7) return -1;\n\telse if (wc < 0x108b0) return 1;\n\telse if (wc < 0x108e0) return -1;\n\telse if (wc < 0x108f3) return 1;\n\telse if (wc < 0x108f4) return -1;\n\telse if (wc < 0x108f6) return 1;\n\telse if (wc < 0x108fb) return -1;\n\telse if (wc < 0x1091c) return 1;\n\telse if (wc < 0x1091f) return -1;\n\telse if (wc < 0x1093a) return 1;\n\telse if (wc < 0x1093f) return -1;\n\telse if (wc < 0x10940) return 1;\n\telse if (wc < 0x10980) return -1;\n\telse if (wc < 0x109b8) return 1;\n\telse if (wc < 0x109bc) return -1;\n\telse if (wc < 0x109d0) return 1;\n\telse if (wc < 0x109d2) return -1;\n\telse if (wc < 0x10a01) return 1;\n\telse if (wc < 0x10a04) return 0;\n\telse if (wc < 0x10a05) return -1;\n\telse if (wc < 0x10a07) return 0;\n\telse if (wc < 0x10a0c) return -1;\n\telse if (wc < 0x10a10) return 0;\n\telse if (wc < 0x10a14) return 1;\n\telse if (wc < 0x10a15) return -1;\n\telse if (wc < 0x10a18) return 1;\n\telse if (wc < 0x10a19) return -1;\n\telse if (wc < 0x10a36) return 1;\n\telse if (wc < 0x10a38) return -1;\n\telse if (wc < 0x10a3b) return 0;\n\telse if (wc < 0x10a3f) return -1;\n\telse if (wc < 0x10a40) return 0;\n\telse if (wc < 0x10a49) return 1;\n\telse if (wc < 0x10a50) return -1;\n\telse if (wc < 0x10a59) return 1;\n\telse if (wc < 0x10a60) return -1;\n\telse if (wc < 0x10aa0) return 1;\n\telse if (wc < 0x10ac0) return -1;\n\telse if (wc < 0x10ae5) return 1;\n\telse if (wc < 0x10ae7) return 0;\n\telse if (wc < 0x10aeb) return -1;\n\telse if (wc < 0x10af7) return 1;\n\telse if (wc < 0x10b00) return -1;\n\telse if (wc < 0x10b36) return 1;\n\telse if (wc < 0x10b39) return -1;\n\telse if (wc < 0x10b56) return 1;\n\telse if (wc < 0x10b58) return -1;\n\telse if (wc < 0x10b73) return 1;\n\telse if (wc < 0x10b78) return -1;\n\telse if (wc < 0x10b92) return 1;\n\telse if (wc < 0x10b99) return -1;\n\telse if (wc < 0x10b9d) return 1;\n\telse if (wc < 0x10ba9) return -1;\n\telse if (wc < 0x10bb0) return 1;\n\telse if (wc < 0x10c00) return -1;\n\telse if (wc < 0x10c49) return 1;\n\telse if (wc < 0x10c80) return -1;\n\telse if (wc < 0x10cb3) return 1;\n\telse if (wc < 0x10cc0) return -1;\n\telse if (wc < 0x10cf3) return 1;\n\telse if (wc < 0x10cfa) return -1;\n\telse if (wc < 0x10d24) return 1;\n\telse if (wc < 0x10d28) return 0;\n\telse if (wc < 0x10d30) return -1;\n\telse if (wc < 0x10d3a) return 1;\n\telse if (wc < 0x10e60) return -1;\n\telse if (wc < 0x10e7f) return 1;\n\telse if (wc < 0x10e80) return -1;\n\telse if (wc < 0x10eaa) return 1;\n\telse if (wc < 0x10eab) return -1;\n\telse if (wc < 0x10ead) return 0;\n\telse if (wc < 0x10eae) return 1;\n\telse if (wc < 0x10eb0) return -1;\n\telse if (wc < 0x10eb2) return 1;\n\telse if (wc < 0x10efd) return -1;\n\telse if (wc < 0x10f00) return 0;\n\telse if (wc < 0x10f28) return 1;\n\telse if (wc < 0x10f30) return -1;\n\telse if (wc < 0x10f46) return 1;\n\telse if (wc < 0x10f51) return 0;\n\telse if (wc < 0x10f5a) return 1;\n\telse if (wc < 0x10f70) return -1;\n\telse if (wc < 0x10f82) return 1;\n\telse if (wc < 0x10f86) return 0;\n\telse if (wc < 0x10f8a) return 1;\n\telse if (wc < 0x10fb0) return -1;\n\telse if (wc < 0x10fcc) return 1;\n\telse if (wc < 0x10fe0) return -1;\n\telse if (wc < 0x10ff7) return 1;\n\telse if (wc < 0x11000) return -1;\n\telse if (wc < 0x11001) return 1;\n\telse if (wc < 0x11002) return 0;\n\telse if (wc < 0x11038) return 1;\n\telse if (wc < 0x11047) return 0;\n\telse if (wc < 0x1104e) return 1;\n\telse if (wc < 0x11052) return -1;\n\telse if (wc < 0x11070) return 1;\n\telse if (wc < 0x11071) return 0;\n\telse if (wc < 0x11073) return 1;\n\telse if (wc < 0x11075) return 0;\n\telse if (wc < 0x11076) return 1;\n\telse if (wc < 0x1107f) return -1;\n\telse if (wc < 0x11082) return 0;\n\telse if (wc < 0x110b3) return 1;\n\telse if (wc < 0x110b7) return 0;\n\telse if (wc < 0x110b9) return 1;\n\telse if (wc < 0x110bb) return 0;\n\telse if (wc < 0x110bd) return 1;\n\telse if (wc < 0x110be) return 0;\n\telse if (wc < 0x110c2) return 1;\n\telse if (wc < 0x110c3) return 0;\n\telse if (wc < 0x110cd) return -1;\n\telse if (wc < 0x110ce) return 0;\n\telse if (wc < 0x110d0) return -1;\n\telse if (wc < 0x110e9) return 1;\n\telse if (wc < 0x110f0) return -1;\n\telse if (wc < 0x110fa) return 1;\n\telse if (wc < 0x11100) return -1;\n\telse if (wc < 0x11103) return 0;\n\telse if (wc < 0x11127) return 1;\n\telse if (wc < 0x1112c) return 0;\n\telse if (wc < 0x1112d) return 1;\n\telse if (wc < 0x11135) return 0;\n\telse if (wc < 0x11136) return -1;\n\telse if (wc < 0x11148) return 1;\n\telse if (wc < 0x11150) return -1;\n\telse if (wc < 0x11173) return 1;\n\telse if (wc < 0x11174) return 0;\n\telse if (wc < 0x11177) return 1;\n\telse if (wc < 0x11180) return -1;\n\telse if (wc < 0x11182) return 0;\n\telse if (wc < 0x111b6) return 1;\n\telse if (wc < 0x111bf) return 0;\n\telse if (wc < 0x111c9) return 1;\n\telse if (wc < 0x111cd) return 0;\n\telse if (wc < 0x111cf) return 1;\n\telse if (wc < 0x111d0) return 0;\n\telse if (wc < 0x111e0) return 1;\n\telse if (wc < 0x111e1) return -1;\n\telse if (wc < 0x111f5) return 1;\n\telse if (wc < 0x11200) return -1;\n\telse if (wc < 0x11212) return 1;\n\telse if (wc < 0x11213) return -1;\n\telse if (wc < 0x1122f) return 1;\n\telse if (wc < 0x11232) return 0;\n\telse if (wc < 0x11234) return 1;\n\telse if (wc < 0x11235) return 0;\n\telse if (wc < 0x11236) return 1;\n\telse if (wc < 0x11238) return 0;\n\telse if (wc < 0x1123e) return 1;\n\telse if (wc < 0x1123f) return 0;\n\telse if (wc < 0x11241) return 1;\n\telse if (wc < 0x11242) return 0;\n\telse if (wc < 0x11280) return -1;\n\telse if (wc < 0x11287) return 1;\n\telse if (wc < 0x11288) return -1;\n\telse if (wc < 0x11289) return 1;\n\telse if (wc < 0x1128a) return -1;\n\telse if (wc < 0x1128e) return 1;\n\telse if (wc < 0x1128f) return -1;\n\telse if (wc < 0x1129e) return 1;\n\telse if (wc < 0x1129f) return -1;\n\telse if (wc < 0x112aa) return 1;\n\telse if (wc < 0x112b0) return -1;\n\telse if (wc < 0x112df) return 1;\n\telse if (wc < 0x112e0) return 0;\n\telse if (wc < 0x112e3) return 1;\n\telse if (wc < 0x112eb) return 0;\n\telse if (wc < 0x112f0) return -1;\n\telse if (wc < 0x112fa) return 1;\n\telse if (wc < 0x11300) return -1;\n\telse if (wc < 0x11302) return 0;\n\telse if (wc < 0x11304) return 1;\n\telse if (wc < 0x11305) return -1;\n\telse if (wc < 0x1130d) return 1;\n\telse if (wc < 0x1130f) return -1;\n\telse if (wc < 0x11311) return 1;\n\telse if (wc < 0x11313) return -1;\n\telse if (wc < 0x11329) return 1;\n\telse if (wc < 0x1132a) return -1;\n\telse if (wc < 0x11331) return 1;\n\telse if (wc < 0x11332) return -1;\n\telse if (wc < 0x11334) return 1;\n\telse if (wc < 0x11335) return -1;\n\telse if (wc < 0x1133a) return 1;\n\telse if (wc < 0x1133b) return -1;\n\telse if (wc < 0x1133d) return 0;\n\telse if (wc < 0x11340) return 1;\n\telse if (wc < 0x11341) return 0;\n\telse if (wc < 0x11345) return 1;\n\telse if (wc < 0x11347) return -1;\n\telse if (wc < 0x11349) return 1;\n\telse if (wc < 0x1134b) return -1;\n\telse if (wc < 0x1134e) return 1;\n\telse if (wc < 0x11350) return -1;\n\telse if (wc < 0x11351) return 1;\n\telse if (wc < 0x11357) return -1;\n\telse if (wc < 0x11358) return 1;\n\telse if (wc < 0x1135d) return -1;\n\telse if (wc < 0x11364) return 1;\n\telse if (wc < 0x11366) return -1;\n\telse if (wc < 0x1136d) return 0;\n\telse if (wc < 0x11370) return -1;\n\telse if (wc < 0x11375) return 0;\n\telse if (wc < 0x11400) return -1;\n\telse if (wc < 0x11438) return 1;\n\telse if (wc < 0x11440) return 0;\n\telse if (wc < 0x11442) return 1;\n\telse if (wc < 0x11445) return 0;\n\telse if (wc < 0x11446) return 1;\n\telse if (wc < 0x11447) return 0;\n\telse if (wc < 0x1145c) return 1;\n\telse if (wc < 0x1145d) return -1;\n\telse if (wc < 0x1145e) return 1;\n\telse if (wc < 0x1145f) return 0;\n\telse if (wc < 0x11462) return 1;\n\telse if (wc < 0x11480) return -1;\n\telse if (wc < 0x114b3) return 1;\n\telse if (wc < 0x114b9) return 0;\n\telse if (wc < 0x114ba) return 1;\n\telse if (wc < 0x114bb) return 0;\n\telse if (wc < 0x114bf) return 1;\n\telse if (wc < 0x114c1) return 0;\n\telse if (wc < 0x114c2) return 1;\n\telse if (wc < 0x114c4) return 0;\n\telse if (wc < 0x114c8) return 1;\n\telse if (wc < 0x114d0) return -1;\n\telse if (wc < 0x114da) return 1;\n\telse if (wc < 0x11580) return -1;\n\telse if (wc < 0x115b2) return 1;\n\telse if (wc < 0x115b6) return 0;\n\telse if (wc < 0x115b8) return -1;\n\telse if (wc < 0x115bc) return 1;\n\telse if (wc < 0x115be) return 0;\n\telse if (wc < 0x115bf) return 1;\n\telse if (wc < 0x115c1) return 0;\n\telse if (wc < 0x115dc) return 1;\n\telse if (wc < 0x115de) return 0;\n\telse if (wc < 0x11600) return -1;\n\telse if (wc < 0x11633) return 1;\n\telse if (wc < 0x1163b) return 0;\n\telse if (wc < 0x1163d) return 1;\n\telse if (wc < 0x1163e) return 0;\n\telse if (wc < 0x1163f) return 1;\n\telse if (wc < 0x11641) return 0;\n\telse if (wc < 0x11645) return 1;\n\telse if (wc < 0x11650) return -1;\n\telse if (wc < 0x1165a) return 1;\n\telse if (wc < 0x11660) return -1;\n\telse if (wc < 0x1166d) return 1;\n\telse if (wc < 0x11680) return -1;\n\telse if (wc < 0x116ab) return 1;\n\telse if (wc < 0x116ac) return 0;\n\telse if (wc < 0x116ad) return 1;\n\telse if (wc < 0x116ae) return 0;\n\telse if (wc < 0x116b0) return 1;\n\telse if (wc < 0x116b6) return 0;\n\telse if (wc < 0x116b7) return 1;\n\telse if (wc < 0x116b8) return 0;\n\telse if (wc < 0x116ba) return 1;\n\telse if (wc < 0x116c0) return -1;\n\telse if (wc < 0x116ca) return 1;\n\telse if (wc < 0x11700) return -1;\n\telse if (wc < 0x1171b) return 1;\n\telse if (wc < 0x1171d) return -1;\n\telse if (wc < 0x11720) return 0;\n\telse if (wc < 0x11722) return 1;\n\telse if (wc < 0x11726) return 0;\n\telse if (wc < 0x11727) return 1;\n\telse if (wc < 0x1172c) return 0;\n\telse if (wc < 0x11730) return -1;\n\telse if (wc < 0x11747) return 1;\n\telse if (wc < 0x11800) return -1;\n\telse if (wc < 0x1182f) return 1;\n\telse if (wc < 0x11838) return 0;\n\telse if (wc < 0x11839) return 1;\n\telse if (wc < 0x1183b) return 0;\n\telse if (wc < 0x1183c) return 1;\n\telse if (wc < 0x118a0) return -1;\n\telse if (wc < 0x118f3) return 1;\n\telse if (wc < 0x118ff) return -1;\n\telse if (wc < 0x11907) return 1;\n\telse if (wc < 0x11909) return -1;\n\telse if (wc < 0x1190a) return 1;\n\telse if (wc < 0x1190c) return -1;\n\telse if (wc < 0x11914) return 1;\n\telse if (wc < 0x11915) return -1;\n\telse if (wc < 0x11917) return 1;\n\telse if (wc < 0x11918) return -1;\n\telse if (wc < 0x11936) return 1;\n\telse if (wc < 0x11937) return -1;\n\telse if (wc < 0x11939) return 1;\n\telse if (wc < 0x1193b) return -1;\n\telse if (wc < 0x1193d) return 0;\n\telse if (wc < 0x1193e) return 1;\n\telse if (wc < 0x1193f) return 0;\n\telse if (wc < 0x11943) return 1;\n\telse if (wc < 0x11944) return 0;\n\telse if (wc < 0x11947) return 1;\n\telse if (wc < 0x11950) return -1;\n\telse if (wc < 0x1195a) return 1;\n\telse if (wc < 0x119a0) return -1;\n\telse if (wc < 0x119a8) return 1;\n\telse if (wc < 0x119aa) return -1;\n\telse if (wc < 0x119d4) return 1;\n\telse if (wc < 0x119d8) return 0;\n\telse if (wc < 0x119da) return -1;\n\telse if (wc < 0x119dc) return 0;\n\telse if (wc < 0x119e0) return 1;\n\telse if (wc < 0x119e1) return 0;\n\telse if (wc < 0x119e5) return 1;\n\telse if (wc < 0x11a00) return -1;\n\telse if (wc < 0x11a01) return 1;\n\telse if (wc < 0x11a0b) return 0;\n\telse if (wc < 0x11a33) return 1;\n\telse if (wc < 0x11a39) return 0;\n\telse if (wc < 0x11a3b) return 1;\n\telse if (wc < 0x11a3f) return 0;\n\telse if (wc < 0x11a47) return 1;\n\telse if (wc < 0x11a48) return 0;\n\telse if (wc < 0x11a50) return -1;\n\telse if (wc < 0x11a51) return 1;\n\telse if (wc < 0x11a57) return 0;\n\telse if (wc < 0x11a59) return 1;\n\telse if (wc < 0x11a5c) return 0;\n\telse if (wc < 0x11a8a) return 1;\n\telse if (wc < 0x11a97) return 0;\n\telse if (wc < 0x11a98) return 1;\n\telse if (wc < 0x11a9a) return 0;\n\telse if (wc < 0x11aa3) return 1;\n\telse if (wc < 0x11ab0) return -1;\n\telse if (wc < 0x11af9) return 1;\n\telse if (wc < 0x11b00) return -1;\n\telse if (wc < 0x11b0a) return 1;\n\telse if (wc < 0x11c00) return -1;\n\telse if (wc < 0x11c09) return 1;\n\telse if (wc < 0x11c0a) return -1;\n\telse if (wc < 0x11c30) return 1;\n\telse if (wc < 0x11c37) return 0;\n\telse if (wc < 0x11c38) return -1;\n\telse if (wc < 0x11c3e) return 0;\n\telse if (wc < 0x11c3f) return 1;\n\telse if (wc < 0x11c40) return 0;\n\telse if (wc < 0x11c46) return 1;\n\telse if (wc < 0x11c50) return -1;\n\telse if (wc < 0x11c6d) return 1;\n\telse if (wc < 0x11c70) return -1;\n\telse if (wc < 0x11c90) return 1;\n\telse if (wc < 0x11c92) return -1;\n\telse if (wc < 0x11ca8) return 0;\n\telse if (wc < 0x11ca9) return -1;\n\telse if (wc < 0x11caa) return 1;\n\telse if (wc < 0x11cb1) return 0;\n\telse if (wc < 0x11cb2) return 1;\n\telse if (wc < 0x11cb4) return 0;\n\telse if (wc < 0x11cb5) return 1;\n\telse if (wc < 0x11cb7) return 0;\n\telse if (wc < 0x11d00) return -1;\n\telse if (wc < 0x11d07) return 1;\n\telse if (wc < 0x11d08) return -1;\n\telse if (wc < 0x11d0a) return 1;\n\telse if (wc < 0x11d0b) return -1;\n\telse if (wc < 0x11d31) return 1;\n\telse if (wc < 0x11d37) return 0;\n\telse if (wc < 0x11d3a) return -1;\n\telse if (wc < 0x11d3b) return 0;\n\telse if (wc < 0x11d3c) return -1;\n\telse if (wc < 0x11d3e) return 0;\n\telse if (wc < 0x11d3f) return -1;\n\telse if (wc < 0x11d46) return 0;\n\telse if (wc < 0x11d47) return 1;\n\telse if (wc < 0x11d48) return 0;\n\telse if (wc < 0x11d50) return -1;\n\telse if (wc < 0x11d5a) return 1;\n\telse if (wc < 0x11d60) return -1;\n\telse if (wc < 0x11d66) return 1;\n\telse if (wc < 0x11d67) return -1;\n\telse if (wc < 0x11d69) return 1;\n\telse if (wc < 0x11d6a) return -1;\n\telse if (wc < 0x11d8f) return 1;\n\telse if (wc < 0x11d90) return -1;\n\telse if (wc < 0x11d92) return 0;\n\telse if (wc < 0x11d93) return -1;\n\telse if (wc < 0x11d95) return 1;\n\telse if (wc < 0x11d96) return 0;\n\telse if (wc < 0x11d97) return 1;\n\telse if (wc < 0x11d98) return 0;\n\telse if (wc < 0x11d99) return 1;\n\telse if (wc < 0x11da0) return -1;\n\telse if (wc < 0x11daa) return 1;\n\telse if (wc < 0x11ee0) return -1;\n\telse if (wc < 0x11ef3) return 1;\n\telse if (wc < 0x11ef5) return 0;\n\telse if (wc < 0x11ef9) return 1;\n\telse if (wc < 0x11f00) return -1;\n\telse if (wc < 0x11f02) return 0;\n\telse if (wc < 0x11f11) return 1;\n\telse if (wc < 0x11f12) return -1;\n\telse if (wc < 0x11f36) return 1;\n\telse if (wc < 0x11f3b) return 0;\n\telse if (wc < 0x11f3e) return -1;\n\telse if (wc < 0x11f40) return 1;\n\telse if (wc < 0x11f41) return 0;\n\telse if (wc < 0x11f42) return 1;\n\telse if (wc < 0x11f43) return 0;\n\telse if (wc < 0x11f5a) return 1;\n\telse if (wc < 0x11fb0) return -1;\n\telse if (wc < 0x11fb1) return 1;\n\telse if (wc < 0x11fc0) return -1;\n\telse if (wc < 0x11ff2) return 1;\n\telse if (wc < 0x11fff) return -1;\n\telse if (wc < 0x1239a) return 1;\n\telse if (wc < 0x12400) return -1;\n\telse if (wc < 0x1246f) return 1;\n\telse if (wc < 0x12470) return -1;\n\telse if (wc < 0x12475) return 1;\n\telse if (wc < 0x12480) return -1;\n\telse if (wc < 0x12544) return 1;\n\telse if (wc < 0x12f90) return -1;\n\telse if (wc < 0x12ff3) return 1;\n\telse if (wc < 0x13000) return -1;\n\telse if (wc < 0x13430) return 1;\n\telse if (wc < 0x13441) return 0;\n\telse if (wc < 0x13447) return 1;\n\telse if (wc < 0x13456) return 0;\n\telse if (wc < 0x14400) return -1;\n\telse if (wc < 0x14647) return 1;\n\telse if (wc < 0x16800) return -1;\n\telse if (wc < 0x16a39) return 1;\n\telse if (wc < 0x16a40) return -1;\n\telse if (wc < 0x16a5f) return 1;\n\telse if (wc < 0x16a60) return -1;\n\telse if (wc < 0x16a6a) return 1;\n\telse if (wc < 0x16a6e) return -1;\n\telse if (wc < 0x16abf) return 1;\n\telse if (wc < 0x16ac0) return -1;\n\telse if (wc < 0x16aca) return 1;\n\telse if (wc < 0x16ad0) return -1;\n\telse if (wc < 0x16aee) return 1;\n\telse if (wc < 0x16af0) return -1;\n\telse if (wc < 0x16af5) return 0;\n\telse if (wc < 0x16af6) return 1;\n\telse if (wc < 0x16b00) return -1;\n\telse if (wc < 0x16b30) return 1;\n\telse if (wc < 0x16b37) return 0;\n\telse if (wc < 0x16b46) return 1;\n\telse if (wc < 0x16b50) return -1;\n\telse if (wc < 0x16b5a) return 1;\n\telse if (wc < 0x16b5b) return -1;\n\telse if (wc < 0x16b62) return 1;\n\telse if (wc < 0x16b63) return -1;\n\telse if (wc < 0x16b78) return 1;\n\telse if (wc < 0x16b7d) return -1;\n\telse if (wc < 0x16b90) return 1;\n\telse if (wc < 0x16e40) return -1;\n\telse if (wc < 0x16e9b) return 1;\n\telse if (wc < 0x16f00) return -1;\n\telse if (wc < 0x16f4b) return 1;\n\telse if (wc < 0x16f4f) return -1;\n\telse if (wc < 0x16f50) return 0;\n\telse if (wc < 0x16f88) return 1;\n\telse if (wc < 0x16f8f) return -1;\n\telse if (wc < 0x16f93) return 0;\n\telse if (wc < 0x16fa0) return 1;\n\telse if (wc < 0x16fe0) return -1;\n\telse if (wc < 0x16fe4) return 2;\n\telse if (wc < 0x16fe5) return 0;\n\telse if (wc < 0x16ff0) return -1;\n\telse if (wc < 0x16ff2) return 2;\n\telse if (wc < 0x17000) return -1;\n\telse if (wc < 0x187f8) return 2;\n\telse if (wc < 0x18800) return -1;\n\telse if (wc < 0x18cd6) return 2;\n\telse if (wc < 0x18d00) return -1;\n\telse if (wc < 0x18d09) return 2;\n\telse if (wc < 0x1aff0) return -1;\n\telse if (wc < 0x1aff4) return 2;\n\telse if (wc < 0x1aff5) return -1;\n\telse if (wc < 0x1affc) return 2;\n\telse if (wc < 0x1affd) return -1;\n\telse if (wc < 0x1afff) return 2;\n\telse if (wc < 0x1b000) return -1;\n\telse if (wc < 0x1b123) return 2;\n\telse if (wc < 0x1b132) return -1;\n\telse if (wc < 0x1b133) return 2;\n\telse if (wc < 0x1b150) return -1;\n\telse if (wc < 0x1b153) return 2;\n\telse if (wc < 0x1b155) return -1;\n\telse if (wc < 0x1b156) return 2;\n\telse if (wc < 0x1b164) return -1;\n\telse if (wc < 0x1b168) return 2;\n\telse if (wc < 0x1b170) return -1;\n\telse if (wc < 0x1b2fc) return 2;\n\telse if (wc < 0x1bc00) return -1;\n\telse if (wc < 0x1bc6b) return 1;\n\telse if (wc < 0x1bc70) return -1;\n\telse if (wc < 0x1bc7d) return 1;\n\telse if (wc < 0x1bc80) return -1;\n\telse if (wc < 0x1bc89) return 1;\n\telse if (wc < 0x1bc90) return -1;\n\telse if (wc < 0x1bc9a) return 1;\n\telse if (wc < 0x1bc9c) return -1;\n\telse if (wc < 0x1bc9d) return 1;\n\telse if (wc < 0x1bc9f) return 0;\n\telse if (wc < 0x1bca0) return 1;\n\telse if (wc < 0x1bca4) return 0;\n\telse if (wc < 0x1cf00) return -1;\n\telse if (wc < 0x1cf2e) return 0;\n\telse if (wc < 0x1cf30) return -1;\n\telse if (wc < 0x1cf47) return 0;\n\telse if (wc < 0x1cf50) return -1;\n\telse if (wc < 0x1cfc4) return 1;\n\telse if (wc < 0x1d000) return -1;\n\telse if (wc < 0x1d0f6) return 1;\n\telse if (wc < 0x1d100) return -1;\n\telse if (wc < 0x1d127) return 1;\n\telse if (wc < 0x1d129) return -1;\n\telse if (wc < 0x1d167) return 1;\n\telse if (wc < 0x1d16a) return 0;\n\telse if (wc < 0x1d173) return 1;\n\telse if (wc < 0x1d183) return 0;\n\telse if (wc < 0x1d185) return 1;\n\telse if (wc < 0x1d18c) return 0;\n\telse if (wc < 0x1d1aa) return 1;\n\telse if (wc < 0x1d1ae) return 0;\n\telse if (wc < 0x1d1eb) return 1;\n\telse if (wc < 0x1d200) return -1;\n\telse if (wc < 0x1d242) return 1;\n\telse if (wc < 0x1d245) return 0;\n\telse if (wc < 0x1d246) return 1;\n\telse if (wc < 0x1d2c0) return -1;\n\telse if (wc < 0x1d2d4) return 1;\n\telse if (wc < 0x1d2e0) return -1;\n\telse if (wc < 0x1d2f4) return 1;\n\telse if (wc < 0x1d300) return -1;\n\telse if (wc < 0x1d357) return 1;\n\telse if (wc < 0x1d360) return -1;\n\telse if (wc < 0x1d379) return 1;\n\telse if (wc < 0x1d400) return -1;\n\telse if (wc < 0x1d455) return 1;\n\telse if (wc < 0x1d456) return -1;\n\telse if (wc < 0x1d49d) return 1;\n\telse if (wc < 0x1d49e) return -1;\n\telse if (wc < 0x1d4a0) return 1;\n\telse if (wc < 0x1d4a2) return -1;\n\telse if (wc < 0x1d4a3) return 1;\n\telse if (wc < 0x1d4a5) return -1;\n\telse if (wc < 0x1d4a7) return 1;\n\telse if (wc < 0x1d4a9) return -1;\n\telse if (wc < 0x1d4ad) return 1;\n\telse if (wc < 0x1d4ae) return -1;\n\telse if (wc < 0x1d4ba) return 1;\n\telse if (wc < 0x1d4bb) return -1;\n\telse if (wc < 0x1d4bc) return 1;\n\telse if (wc < 0x1d4bd) return -1;\n\telse if (wc < 0x1d4c4) return 1;\n\telse if (wc < 0x1d4c5) return -1;\n\telse if (wc < 0x1d506) return 1;\n\telse if (wc < 0x1d507) return -1;\n\telse if (wc < 0x1d50b) return 1;\n\telse if (wc < 0x1d50d) return -1;\n\telse if (wc < 0x1d515) return 1;\n\telse if (wc < 0x1d516) return -1;\n\telse if (wc < 0x1d51d) return 1;\n\telse if (wc < 0x1d51e) return -1;\n\telse if (wc < 0x1d53a) return 1;\n\telse if (wc < 0x1d53b) return -1;\n\telse if (wc < 0x1d53f) return 1;\n\telse if (wc < 0x1d540) return -1;\n\telse if (wc < 0x1d545) return 1;\n\telse if (wc < 0x1d546) return -1;\n\telse if (wc < 0x1d547) return 1;\n\telse if (wc < 0x1d54a) return -1;\n\telse if (wc < 0x1d551) return 1;\n\telse if (wc < 0x1d552) return -1;\n\telse if (wc < 0x1d6a6) return 1;\n\telse if (wc < 0x1d6a8) return -1;\n\telse if (wc < 0x1d7cc) return 1;\n\telse if (wc < 0x1d7ce) return -1;\n\telse if (wc < 0x1da00) return 1;\n\telse if (wc < 0x1da37) return 0;\n\telse if (wc < 0x1da3b) return 1;\n\telse if (wc < 0x1da6d) return 0;\n\telse if (wc < 0x1da75) return 1;\n\telse if (wc < 0x1da76) return 0;\n\telse if (wc < 0x1da84) return 1;\n\telse if (wc < 0x1da85) return 0;\n\telse if (wc < 0x1da8c) return 1;\n\telse if (wc < 0x1da9b) return -1;\n\telse if (wc < 0x1daa0) return 0;\n\telse if (wc < 0x1daa1) return -1;\n\telse if (wc < 0x1dab0) return 0;\n\telse if (wc < 0x1df00) return -1;\n\telse if (wc < 0x1df1f) return 1;\n\telse if (wc < 0x1df25) return -1;\n\telse if (wc < 0x1df2b) return 1;\n\telse if (wc < 0x1e000) return -1;\n\telse if (wc < 0x1e007) return 0;\n\telse if (wc < 0x1e008) return -1;\n\telse if (wc < 0x1e019) return 0;\n\telse if (wc < 0x1e01b) return -1;\n\telse if (wc < 0x1e022) return 0;\n\telse if (wc < 0x1e023) return -1;\n\telse if (wc < 0x1e025) return 0;\n\telse if (wc < 0x1e026) return -1;\n\telse if (wc < 0x1e02b) return 0;\n\telse if (wc < 0x1e030) return -1;\n\telse if (wc < 0x1e06e) return 1;\n\telse if (wc < 0x1e08f) return -1;\n\telse if (wc < 0x1e090) return 0;\n\telse if (wc < 0x1e100) return -1;\n\telse if (wc < 0x1e12d) return 1;\n\telse if (wc < 0x1e130) return -1;\n\telse if (wc < 0x1e137) return 0;\n\telse if (wc < 0x1e13e) return 1;\n\telse if (wc < 0x1e140) return -1;\n\telse if (wc < 0x1e14a) return 1;\n\telse if (wc < 0x1e14e) return -1;\n\telse if (wc < 0x1e150) return 1;\n\telse if (wc < 0x1e290) return -1;\n\telse if (wc < 0x1e2ae) return 1;\n\telse if (wc < 0x1e2af) return 0;\n\telse if (wc < 0x1e2c0) return -1;\n\telse if (wc < 0x1e2ec) return 1;\n\telse if (wc < 0x1e2f0) return 0;\n\telse if (wc < 0x1e2fa) return 1;\n\telse if (wc < 0x1e2ff) return -1;\n\telse if (wc < 0x1e300) return 1;\n\telse if (wc < 0x1e4d0) return -1;\n\telse if (wc < 0x1e4ec) return 1;\n\telse if (wc < 0x1e4f0) return 0;\n\telse if (wc < 0x1e4fa) return 1;\n\telse if (wc < 0x1e7e0) return -1;\n\telse if (wc < 0x1e7e7) return 1;\n\telse if (wc < 0x1e7e8) return -1;\n\telse if (wc < 0x1e7ec) return 1;\n\telse if (wc < 0x1e7ed) return -1;\n\telse if (wc < 0x1e7ef) return 1;\n\telse if (wc < 0x1e7f0) return -1;\n\telse if (wc < 0x1e7ff) return 1;\n\telse if (wc < 0x1e800) return -1;\n\telse if (wc < 0x1e8c5) return 1;\n\telse if (wc < 0x1e8c7) return -1;\n\telse if (wc < 0x1e8d0) return 1;\n\telse if (wc < 0x1e8d7) return 0;\n\telse if (wc < 0x1e900) return -1;\n\telse if (wc < 0x1e944) return 1;\n\telse if (wc < 0x1e94b) return 0;\n\telse if (wc < 0x1e94c) return 1;\n\telse if (wc < 0x1e950) return -1;\n\telse if (wc < 0x1e95a) return 1;\n\telse if (wc < 0x1e95e) return -1;\n\telse if (wc < 0x1e960) return 1;\n\telse if (wc < 0x1ec71) return -1;\n\telse if (wc < 0x1ecb5) return 1;\n\telse if (wc < 0x1ed01) return -1;\n\telse if (wc < 0x1ed3e) return 1;\n\telse if (wc < 0x1ee00) return -1;\n\telse if (wc < 0x1ee04) return 1;\n\telse if (wc < 0x1ee05) return -1;\n\telse if (wc < 0x1ee20) return 1;\n\telse if (wc < 0x1ee21) return -1;\n\telse if (wc < 0x1ee23) return 1;\n\telse if (wc < 0x1ee24) return -1;\n\telse if (wc < 0x1ee25) return 1;\n\telse if (wc < 0x1ee27) return -1;\n\telse if (wc < 0x1ee28) return 1;\n\telse if (wc < 0x1ee29) return -1;\n\telse if (wc < 0x1ee33) return 1;\n\telse if (wc < 0x1ee34) return -1;\n\telse if (wc < 0x1ee38) return 1;\n\telse if (wc < 0x1ee39) return -1;\n\telse if (wc < 0x1ee3a) return 1;\n\telse if (wc < 0x1ee3b) return -1;\n\telse if (wc < 0x1ee3c) return 1;\n\telse if (wc < 0x1ee42) return -1;\n\telse if (wc < 0x1ee43) return 1;\n\telse if (wc < 0x1ee47) return -1;\n\telse if (wc < 0x1ee48) return 1;\n\telse if (wc < 0x1ee49) return -1;\n\telse if (wc < 0x1ee4a) return 1;\n\telse if (wc < 0x1ee4b) return -1;\n\telse if (wc < 0x1ee4c) return 1;\n\telse if (wc < 0x1ee4d) return -1;\n\telse if (wc < 0x1ee50) return 1;\n\telse if (wc < 0x1ee51) return -1;\n\telse if (wc < 0x1ee53) return 1;\n\telse if (wc < 0x1ee54) return -1;\n\telse if (wc < 0x1ee55) return 1;\n\telse if (wc < 0x1ee57) return -1;\n\telse if (wc < 0x1ee58) return 1;\n\telse if (wc < 0x1ee59) return -1;\n\telse if (wc < 0x1ee5a) return 1;\n\telse if (wc < 0x1ee5b) return -1;\n\telse if (wc < 0x1ee5c) return 1;\n\telse if (wc < 0x1ee5d) return -1;\n\telse if (wc < 0x1ee5e) return 1;\n\telse if (wc < 0x1ee5f) return -1;\n\telse if (wc < 0x1ee60) return 1;\n\telse if (wc < 0x1ee61) return -1;\n\telse if (wc < 0x1ee63) return 1;\n\telse if (wc < 0x1ee64) return -1;\n\telse if (wc < 0x1ee65) return 1;\n\telse if (wc < 0x1ee67) return -1;\n\telse if (wc < 0x1ee6b) return 1;\n\telse if (wc < 0x1ee6c) return -1;\n\telse if (wc < 0x1ee73) return 1;\n\telse if (wc < 0x1ee74) return -1;\n\telse if (wc < 0x1ee78) return 1;\n\telse if (wc < 0x1ee79) return -1;\n\telse if (wc < 0x1ee7d) return 1;\n\telse if (wc < 0x1ee7e) return -1;\n\telse if (wc < 0x1ee7f) return 1;\n\telse if (wc < 0x1ee80) return -1;\n\telse if (wc < 0x1ee8a) return 1;\n\telse if (wc < 0x1ee8b) return -1;\n\telse if (wc < 0x1ee9c) return 1;\n\telse if (wc < 0x1eea1) return -1;\n\telse if (wc < 0x1eea4) return 1;\n\telse if (wc < 0x1eea5) return -1;\n\telse if (wc < 0x1eeaa) return 1;\n\telse if (wc < 0x1eeab) return -1;\n\telse if (wc < 0x1eebc) return 1;\n\telse if (wc < 0x1eef0) return -1;\n\telse if (wc < 0x1eef2) return 1;\n\telse if (wc < 0x1f000) return -1;\n\telse if (wc < 0x1f004) return 1;\n\telse if (wc < 0x1f005) return 2;\n\telse if (wc < 0x1f02c) return 1;\n\telse if (wc < 0x1f030) return -1;\n\telse if (wc < 0x1f094) return 1;\n\telse if (wc < 0x1f0a0) return -1;\n\telse if (wc < 0x1f0af) return 1;\n\telse if (wc < 0x1f0b1) return -1;\n\telse if (wc < 0x1f0c0) return 1;\n\telse if (wc < 0x1f0c1) return -1;\n\telse if (wc < 0x1f0cf) return 1;\n\telse if (wc < 0x1f0d0) return 2;\n\telse if (wc < 0x1f0d1) return -1;\n\telse if (wc < 0x1f0f6) return 1;\n\telse if (wc < 0x1f100) return -1;\n\telse if (wc < 0x1f18e) return 1;\n\telse if (wc < 0x1f18f) return 2;\n\telse if (wc < 0x1f191) return 1;\n\telse if (wc < 0x1f19b) return 2;\n\telse if (wc < 0x1f1ae) return 1;\n\telse if (wc < 0x1f1e6) return -1;\n\telse if (wc < 0x1f200) return 1;\n\telse if (wc < 0x1f203) return 2;\n\telse if (wc < 0x1f210) return -1;\n\telse if (wc < 0x1f23c) return 2;\n\telse if (wc < 0x1f240) return -1;\n\telse if (wc < 0x1f249) return 2;\n\telse if (wc < 0x1f250) return -1;\n\telse if (wc < 0x1f252) return 2;\n\telse if (wc < 0x1f260) return -1;\n\telse if (wc < 0x1f266) return 2;\n\telse if (wc < 0x1f300) return -1;\n\telse if (wc < 0x1f321) return 2;\n\telse if (wc < 0x1f32d) return 1;\n\telse if (wc < 0x1f336) return 2;\n\telse if (wc < 0x1f337) return 1;\n\telse if (wc < 0x1f37d) return 2;\n\telse if (wc < 0x1f37e) return 1;\n\telse if (wc < 0x1f394) return 2;\n\telse if (wc < 0x1f3a0) return 1;\n\telse if (wc < 0x1f3cb) return 2;\n\telse if (wc < 0x1f3cf) return 1;\n\telse if (wc < 0x1f3d4) return 2;\n\telse if (wc < 0x1f3e0) return 1;\n\telse if (wc < 0x1f3f1) return 2;\n\telse if (wc < 0x1f3f4) return 1;\n\telse if (wc < 0x1f3f5) return 2;\n\telse if (wc < 0x1f3f8) return 1;\n\telse if (wc < 0x1f43f) return 2;\n\telse if (wc < 0x1f440) return 1;\n\telse if (wc < 0x1f441) return 2;\n\telse if (wc < 0x1f442) return 1;\n\telse if (wc < 0x1f4fd) return 2;\n\telse if (wc < 0x1f4ff) return 1;\n\telse if (wc < 0x1f53e) return 2;\n\telse if (wc < 0x1f54b) return 1;\n\telse if (wc < 0x1f54f) return 2;\n\telse if (wc < 0x1f550) return 1;\n\telse if (wc < 0x1f568) return 2;\n\telse if (wc < 0x1f57a) return 1;\n\telse if (wc < 0x1f57b) return 2;\n\telse if (wc < 0x1f595) return 1;\n\telse if (wc < 0x1f597) return 2;\n\telse if (wc < 0x1f5a4) return 1;\n\telse if (wc < 0x1f5a5) return 2;\n\telse if (wc < 0x1f5fb) return 1;\n\telse if (wc < 0x1f650) return 2;\n\telse if (wc < 0x1f680) return 1;\n\telse if (wc < 0x1f6c6) return 2;\n\telse if (wc < 0x1f6cc) return 1;\n\telse if (wc < 0x1f6cd) return 2;\n\telse if (wc < 0x1f6d0) return 1;\n\telse if (wc < 0x1f6d3) return 2;\n\telse if (wc < 0x1f6d5) return 1;\n\telse if (wc < 0x1f6d8) return 2;\n\telse if (wc < 0x1f6dc) return -1;\n\telse if (wc < 0x1f6e0) return 2;\n\telse if (wc < 0x1f6eb) return 1;\n\telse if (wc < 0x1f6ed) return 2;\n\telse if (wc < 0x1f6f0) return -1;\n\telse if (wc < 0x1f6f4) return 1;\n\telse if (wc < 0x1f6fd) return 2;\n\telse if (wc < 0x1f700) return -1;\n\telse if (wc < 0x1f777) return 1;\n\telse if (wc < 0x1f77b) return -1;\n\telse if (wc < 0x1f7da) return 1;\n\telse if (wc < 0x1f7e0) return -1;\n\telse if (wc < 0x1f7ec) return 2;\n\telse if (wc < 0x1f7f0) return -1;\n\telse if (wc < 0x1f7f1) return 2;\n\telse if (wc < 0x1f800) return -1;\n\telse if (wc < 0x1f80c) return 1;\n\telse if (wc < 0x1f810) return -1;\n\telse if (wc < 0x1f848) return 1;\n\telse if (wc < 0x1f850) return -1;\n\telse if (wc < 0x1f85a) return 1;\n\telse if (wc < 0x1f860) return -1;\n\telse if (wc < 0x1f888) return 1;\n\telse if (wc < 0x1f890) return -1;\n\telse if (wc < 0x1f8ae) return 1;\n\telse if (wc < 0x1f8b0) return -1;\n\telse if (wc < 0x1f8b2) return 1;\n\telse if (wc < 0x1f900) return -1;\n\telse if (wc < 0x1f90c) return 1;\n\telse if (wc < 0x1f93b) return 2;\n\telse if (wc < 0x1f93c) return 1;\n\telse if (wc < 0x1f946) return 2;\n\telse if (wc < 0x1f947) return 1;\n\telse if (wc < 0x1fa00) return 2;\n\telse if (wc < 0x1fa54) return 1;\n\telse if (wc < 0x1fa60) return -1;\n\telse if (wc < 0x1fa6e) return 1;\n\telse if (wc < 0x1fa70) return -1;\n\telse if (wc < 0x1fa7d) return 2;\n\telse if (wc < 0x1fa80) return -1;\n\telse if (wc < 0x1fa89) return 2;\n\telse if (wc < 0x1fa90) return -1;\n\telse if (wc < 0x1fabe) return 2;\n\telse if (wc < 0x1fabf) return -1;\n\telse if (wc < 0x1fac6) return 2;\n\telse if (wc < 0x1face) return -1;\n\telse if (wc < 0x1fadc) return 2;\n\telse if (wc < 0x1fae0) return -1;\n\telse if (wc < 0x1fae9) return 2;\n\telse if (wc < 0x1faf0) return -1;\n\telse if (wc < 0x1faf9) return 2;\n\telse if (wc < 0x1fb00) return -1;\n\telse if (wc < 0x1fb93) return 1;\n\telse if (wc < 0x1fb94) return -1;\n\telse if (wc < 0x1fbcb) return 1;\n\telse if (wc < 0x1fbf0) return -1;\n\telse if (wc < 0x1fbfa) return 1;\n\telse if (wc < 0x20000) return -1;\n\telse if (wc < 0x2fffe) return 2;\n\telse if (wc < 0x30000) return -1;\n\telse if (wc < 0x3fffe) return 2;\n\telse if (wc < 0xe0001) return -1;\n\telse if (wc < 0xe0002) return 0;\n\telse if (wc < 0xe0020) return -1;\n\telse if (wc < 0xe0080) return 0;\n\telse if (wc < 0xe0100) return -1;\n\telse if (wc < 0xe01f0) return 0;\n\telse if (wc < 0xf0000) return -1;\n\telse if (wc < 0xffffe) return 1;\n\telse if (wc < 0x100000) return -1;\n\telse if (wc < 0x10fffe) return 1;\n\telse if (wc < 0x110000) return -1;\n\treturn -1;\n}\n"
  },
  {
    "path": "linker/README.md",
    "content": "# ELF Shared Library Dynamic Linker/Loader\n\nToaruOS employs *shared objects* to allow for smaller on-disk binary sizes and provide runtime loading and linking.\n\nThe linker here becomes `/lib/ld.so` and is called as the interpreter for dynamically-linked binaries in the OS.\n\n## ld.so Implementation\n\nThe linker is a minimal implementation of 32-bit x86 ELF dynamic linking. It does not (yet) employ shared file mappings for libraries or binaries. This does mean that memory usage of dynamically linked programs is generally higher than if they were statically linked, but disk space can be saved if multiple programs are using the same libraries (such as the C standard library itself). Actually sharing program code between processes is planned for the future, but requires additional functionally not yet available from the kernel.\n\n## ld.so Debugging\n\nYou can enable debug output from the linker/loader by setting the environment variable `LD_DEBUG=1`. This will provide details on where ld.so is loading libraries, as well as reporting any unresolved symbols which it normally ignores.\n\n\n"
  },
  {
    "path": "linker/link.ld",
    "content": "ENTRY(_start)\n\nSECTIONS\n{\n\t. = 0x3F000000;\n\tphys = .;\n\n\t.text BLOCK(4K) : ALIGN(4K)\n\t{\n\t\tcode = .;\n\t\t*(.text)\n\t}\n\n\t.rodata BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.rodata)\n\t}\n\n\t.data BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(.data)\n\t}\n\n\t.bss BLOCK(4K) : ALIGN(4K)\n\t{\n\t\t*(COMMON)\n\t\t*(.bss)\n\t}\n\n\t.eh_frame BLOCK(4K) : ALIGN(4K) {\n\t\t*(.eh_frame)\n\t}\n\n\t.init_array : {\n\t\tPROVIDE_HIDDEN (__init_array_start = .);\n\t\t*(.init_array);\n\t\tPROVIDE_HIDDEN (__init_array_end = .);\n\t}\n\n\tend = .;\n\n\t/DISCARD/ :\n\t{\n\t\t*(.comment)\n\t\t*(.note.gnu.build-id)\n\t}\n}\n"
  },
  {
    "path": "linker/linker.c",
    "content": "/**\n * @file linker/linker.c\n * @brief ELF Dynamic Linker/Loader\n *\n * Loads ELF executables and links them at runtime to their\n * shared library dependencies.\n *\n * As of writing, this is a simplistic and not-fully-compliant\n * implementation of ELF dynamic linking. It suffers from a number\n * of issues, including not actually sharing libraries (there\n * isn't a sufficient mechanism in the kernel at the moment for\n * doing that - we need something with copy-on-write, preferably\n * an mmap-file mechanism), as well as not handling symbol\n * resolution correctly.\n *\n * However, it's sufficient for our purposes, and works well enough\n * to load Python C modules.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2021 K. Lange\n */\n#include <stdlib.h>\n#include <stdint.h>\n#include <alloca.h>\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/sysfunc.h>\n#include <syscall.h>\n\n#include <kernel/elf.h>\n\nvoid * (*_malloc)(size_t size) = malloc;\nvoid (*_free)(void * ptr) = free;\n\n#undef malloc\n#undef free\n#define malloc ld_x_malloc\n#define free ld_x_free\n\nuintptr_t _malloc_minimum = 0;\n\nstatic void * malloc(size_t size) {\n\treturn _malloc(size);\n}\n\nstatic void free(void * ptr) {\n\tif ((uintptr_t)ptr < _malloc_minimum) return;\n\t_free(ptr);\n}\n\n/*\n * When the LD_DEBUG environment variable is set, TRACE_LD messages\n * will be printed to stderr\n */\n#define TRACE_APP_NAME \"ld.so\"\n#define TRACE_LD(...) do { if (__trace_ld) { TRACE(__VA_ARGS__); } } while (0)\n\nstatic int __trace_ld = 0;\n\n#include <toaru/trace.h>\n\n/*\n * This libraries are included in source form to avoid having\n * to build separate objects for them and complicate linking,\n * since ld is specially built as a static object.\n */\n#include \"../lib/list.c\"\n#include \"../lib/hashmap.c\"\n\ntypedef int (*entry_point_t)(int, char *[], char**);\n\n/* Global linking state */\nstatic hashmap_t * dumb_symbol_table;\nstatic hashmap_t * glob_dat;\nstatic hashmap_t * objects_map;\nstatic hashmap_t * tls_map;\nstatic size_t current_tls_offset = 16;\n\n/* Used for dlerror */\nstatic char * last_error = NULL;\n\nstatic int _target_is_suid = 0;\n\ntypedef struct elf_object {\n\tFILE * file;\n\n\t/* Full copy of the header. */\n\tElf64_Header header;\n\n\tchar * dyn_string_table;\n\tsize_t dyn_string_table_size;\n\n\tElf64_Sym * dyn_symbol_table;\n\tsize_t dyn_symbol_table_size;\n\n\tElf64_Dyn * dynamic;\n\tElf64_Word * dyn_hash;\n\n\tvoid (*init)(void);\n\tvoid (**init_array)(void);\n\tsize_t init_array_size;\n\n\tuintptr_t base;\n\n\tlist_t * dependencies;\n\n\tint loaded;\n\n} elf_t;\n\nstatic elf_t * _main_obj = NULL;\n\nstatic void clear_cache(uintptr_t start, uintptr_t end) {\n\tchar * data[] = {(char*)start,(char*)end};\n\tsysfunc(42, data);\n}\n\n/* Locate library for LD_LIBRARY PATH */\nstatic char * find_lib(const char * file) {\n\n\t/* If it was an absolute path, there's no need to find it. */\n\tif (strchr(file, '/')) return strdup(file);\n\n\t/* Collect the environment variable. */\n\tchar * path = _target_is_suid ? NULL : getenv(\"LD_LIBRARY_PATH\");\n\tif (!path) {\n\t\t/* Not set - this is the default state. Should probably read from config file? */\n\t\tpath = \"/lib:/usr/lib\";\n\t}\n\n\t/* Duplicate so we can tokenize without editing */\n\tchar * xpath = strdup(path);\n\tchar * p, * last;\n\tfor ((p = strtok_r(xpath, \":\", &last)); p; p = strtok_r(NULL, \":\", &last)) {\n\t\t/* Go through each LD_LIBRARY_PATH entry */\n\t\tint r;\n\t\tstruct stat stat_buf;\n\n\t\t/* Append the requested file to that path */\n\t\tchar * exe = malloc(strlen(p) + strlen(file) + 2);\n\t\t*exe = '\\0';\n\t\tstrcat(exe, p);\n\t\tstrcat(exe, \"/\");\n\t\tstrcat(exe, file);\n\n\t\t/* See if it exists */\n\t\tr = stat(exe, &stat_buf);\n\t\tif (r != 0) {\n\t\t\t/* Nope. */\n\t\t\tfree(exe);\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* It exists, so this is what we want. */\n\t\treturn exe;\n\t}\n\tfree(xpath);\n\n\t/* No match found. */\n\treturn NULL;\n}\n\n/* Open an object file */\nstatic elf_t * open_object(const char * path) {\n\n\t/* If no path (eg. dlopen(NULL)), return the main object (the executable). */\n\tif (!path) {\n\t\treturn _main_obj;\n\t}\n\n\t/* If we've already opened a file with this name, return it - don't load things twice. */\n\tif (hashmap_has(objects_map, (void*)path)) {\n\t\telf_t * object = hashmap_get(objects_map, (void*)path);\n\t\treturn object;\n\t}\n\n\t/* Locate the library */\n\tchar * file = find_lib(path);\n\tif (!file) {\n\t\tlast_error = \"Could not find library.\";\n\t\treturn NULL;\n\t}\n\n\t/* Open the library. */\n\tFILE * f = fopen(file, \"r\");\n\n\t/* Free the expanded path, we don't need it anymore. */\n\tfree(file);\n\n\t/* Failed to open? Unlikely, but could mean permissions problems. */\n\tif (!f) {\n\t\tlast_error = \"Could not open library.\";\n\t\treturn NULL;\n\t}\n\n\t/* Initialize a fresh object object. */\n\telf_t * object = malloc(sizeof(elf_t));\n\n\t/* Really unlikely... */\n\tif (!object) {\n\t\tlast_error = \"Could not allocate space.\";\n\t\treturn NULL;\n\t}\n\tmemset(object, 0, sizeof(elf_t));\n\thashmap_set(objects_map, (void*)path, object);\n\n\tobject->file = f;\n\n\t/* Read the header */\n\tsize_t r = fread(&object->header, sizeof(Elf64_Header), 1, object->file);\n\n\t/* Header failed to read? */\n\tif (!r) {\n\t\tlast_error = \"Failed to read object header.\";\n\t\tfree(object);\n\t\treturn NULL;\n\t}\n\n\t/* Is this actually an ELF object? */\n\tif (object->header.e_ident[0] != ELFMAG0 ||\n\t    object->header.e_ident[1] != ELFMAG1 ||\n\t    object->header.e_ident[2] != ELFMAG2 ||\n\t    object->header.e_ident[3] != ELFMAG3) {\n\n\t\tlast_error = \"Not an ELF object.\";\n\t\tfree(object);\n\t\treturn NULL;\n\t}\n\n\t/* Prepare a list for tracking dependencies. */\n\tobject->dependencies = list_create();\n\n\treturn object;\n}\n\n/* Calculate the size of an object file by examining its phdrs */\nstatic size_t object_calculate_size(elf_t * object) {\n\n\tuintptr_t base_addr = (uintptr_t)-1;\n\tuintptr_t end_addr  = 0x0;\n\tsize_t headers = 0;\n\twhile (headers < object->header.e_phnum) {\n\t\tElf64_Phdr phdr;\n\n\t\t/* Read the phdr */\n\t\tfseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET);\n\t\tfread(&phdr, object->header.e_phentsize, 1, object->file);\n\n\t\tswitch (phdr.p_type) {\n\t\t\tcase PT_LOAD:\n\t\t\t\t{\n\t\t\t\t\t/* If this loads lower than our current base... */\n\t\t\t\t\tif (phdr.p_vaddr < base_addr) {\n\t\t\t\t\t\tbase_addr = phdr.p_vaddr;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* Or higher than our current end address... */\n\t\t\t\t\tif (phdr.p_memsz + phdr.p_vaddr > end_addr) {\n\t\t\t\t\t\tend_addr = phdr.p_memsz + phdr.p_vaddr;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t/* TODO: Do we care about other PHDR types here? */\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\n\t\theaders++;\n\t}\n\n\t/* If base_addr is still -1, then no valid phdrs were found, and the object has no loaded size. */\n\tif (base_addr == (uintptr_t)-1) return 0;\n\treturn end_addr - base_addr;\n}\n\n/* Load an object into memory */\nstatic uintptr_t object_load(elf_t * object, uintptr_t base) {\n\n\tuintptr_t end_addr = 0x0;\n\n\tobject->base = base;\n\n\tsize_t headers = 0;\n\twhile (headers < object->header.e_phnum) {\n\t\tElf64_Phdr phdr;\n\n\t\t/* Read the phdr */\n\t\tfseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET);\n\t\tfread(&phdr, object->header.e_phentsize, 1, object->file);\n\n\t\tswitch (phdr.p_type) {\n\t\t\tcase PT_LOAD:\n\t\t\t\t{\n\t\t\t\t\t/* Request memory to load this PHDR into */\n\t\t\t\t\tchar * args[] = {(char *)(base + phdr.p_vaddr), (char *)phdr.p_memsz};\n\t\t\t\t\tsysfunc(TOARU_SYS_FUNC_MMAP, args);\n\n\t\t\t\t\t/* Copy the code into memory */\n\t\t\t\t\tfseek(object->file, phdr.p_offset, SEEK_SET);\n\t\t\t\t\tfread((void *)(base + phdr.p_vaddr), phdr.p_filesz, 1, object->file);\n\t\t\t\t\tclear_cache(base + phdr.p_vaddr, base + phdr.p_vaddr + phdr.p_filesz);\n\n\t\t\t\t\t/* Zero the remaining area */\n\t\t\t\t\tsize_t r = phdr.p_filesz;\n\t\t\t\t\twhile (r < phdr.p_memsz) {\n\t\t\t\t\t\t*(char *)(phdr.p_vaddr + base + r) = 0;\n\t\t\t\t\t\tr++;\n\t\t\t\t\t}\n\n\t\t\t\t\t/* If this expands our end address, be sure to update it */\n\t\t\t\t\tif (end_addr < phdr.p_vaddr + base + phdr.p_memsz) {\n\t\t\t\t\t\tend_addr = phdr.p_vaddr + base + phdr.p_memsz;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase PT_DYNAMIC:\n\t\t\t\t{\n\t\t\t\t\t/* Keep a reference to the dynamic section, which is actually loaded by a PT_LOAD normally. */\n\t\t\t\t\tobject->dynamic = (Elf64_Dyn *)(base + phdr.p_vaddr);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\n\t\theaders++;\n\t}\n\n\treturn end_addr;\n}\n\n/* Perform cleanup after loading */\nstatic int object_postload(elf_t * object) {\n\n\t/* If there is a dynamic table, parse it. */\n\tif (object->dynamic) {\n\t\tElf64_Dyn * table;\n\n\t\t/* Locate string tables */\n\t\ttable = object->dynamic;\n\t\twhile (table->d_tag) {\n\t\t\tswitch (table->d_tag) {\n\t\t\t\tcase DT_HASH:\n\t\t\t\t\tobject->dyn_hash = (Elf64_Word *)(object->base + table->d_un.d_ptr);\n\t\t\t\t\tobject->dyn_symbol_table_size = object->dyn_hash[1];\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_STRTAB:\n\t\t\t\t\tobject->dyn_string_table = (char *)(object->base + table->d_un.d_ptr);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_SYMTAB:\n\t\t\t\t\tobject->dyn_symbol_table = (Elf64_Sym *)(object->base + table->d_un.d_ptr);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_STRSZ: /* Size of string table */\n\t\t\t\t\tobject->dyn_string_table_size = table->d_un.d_val;\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_INIT: /* DT_INIT - initialization function */\n\t\t\t\t\tobject->init = (void (*)(void))(table->d_un.d_ptr + object->base);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_INIT_ARRAY: /* DT_INIT_ARRAY - array of constructors */\n\t\t\t\t\tobject->init_array = (void (**)(void))(table->d_un.d_ptr + object->base);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DT_INIT_ARRAYSZ: /* DT_INIT_ARRAYSZ - size of the table of constructors */\n\t\t\t\t\tobject->init_array_size = table->d_un.d_val / sizeof(uintptr_t);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttable++;\n\t\t}\n\n\t\t/*\n\t\t * Read through dependencies\n\t\t * We have to do this separately from the above to make sure\n\t\t * we have the dynamic string tables loaded first, as they\n\t\t * are needed for the dependency names.\n\t\t */\n\t\ttable = object->dynamic;\n\t\twhile (table->d_tag) {\n\t\t\tswitch (table->d_tag) {\n\t\t\t\tcase 1:\n\t\t\t\t\tlist_insert(object->dependencies, object->dyn_string_table + table->d_un.d_val);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\ttable++;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Whether symbol addresses is needed for a relocation type */\nstatic int need_symbol_for_type(unsigned int type) {\n#ifdef __x86_64__\n\tswitch(type) {\n\t\tcase R_X86_64_64:\n\t\tcase R_X86_64_PC32:\n\t\tcase R_X86_64_COPY:\n\t\tcase R_X86_64_GLOB_DAT:\n\t\tcase R_X86_64_JUMP_SLOT:\n\t\tcase R_X86_64_8:\n\t\tcase R_X86_64_TPOFF64:\n\t\tcase R_X86_64_DTPMOD64:\n\t\tcase R_X86_64_DTPOFF64:\n\t\t\treturn 1;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n#else\n\tswitch(type) {\n\t\tcase 1024:\n\t\tcase 1025:\n\t\tcase 1026:\n\t\tcase 1030:\n\t\tcase 1031:\n\t\tcase 257:\n\t\t\treturn 1;\n\t\tdefault:\n\t\t\treturn 0;\n\t}\n\n#endif\n}\n\n__attribute__((unused))\nstatic size_t __tlsdesc_static(size_t * a) {\n\treturn a[1];\n}\n\n/* Apply ELF relocations */\nstatic int object_relocate(elf_t * object) {\n\n\t/* If there is a dynamic symbol table, load symbols */\n\tif (object->dyn_symbol_table) {\n\t\tElf64_Sym * table = object->dyn_symbol_table;\n\t\tsize_t i = 0;\n\t\twhile (i < object->dyn_symbol_table_size) {\n\t\t\tchar * symname = (char *)((uintptr_t)object->dyn_string_table + table->st_name);\n\n\t\t\tint is_tls = (table->st_info & 0xF) == 6;\n\n\t\t\t/* If we haven't added this symbol to our symbol table, do so now. */\n\t\t\tif (table->st_shndx) {\n\t\t\t\tif (!hashmap_has(dumb_symbol_table, symname)) {\n\t\t\t\t\thashmap_set(dumb_symbol_table, symname, (void*)(table->st_value + (is_tls ? 0 : object->base)));\n\t\t\t\t\ttable->st_value = table->st_value + (is_tls ? 0 : object->base);\n\t\t\t\t} else {\n\t\t\t\t\ttable->st_value = (uintptr_t)hashmap_get(dumb_symbol_table, symname);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (hashmap_has(dumb_symbol_table, symname)) {\n\t\t\t\t\ttable->st_value = (uintptr_t)hashmap_get(dumb_symbol_table, symname);\n\t\t\t\t} else {\n\t\t\t\t\ttable->st_value = table->st_value + (is_tls ? 0 : object->base);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttable++;\n\t\t\ti++;\n\t\t}\n\t}\n\n\t/* Find relocation table */\n\tfor (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) {\n\t\tElf64_Shdr shdr;\n\t\t/* Load section header */\n\t\tfseek(object->file, object->header.e_shoff + x, SEEK_SET);\n\t\tfread(&shdr, object->header.e_shentsize, 1, object->file);\n\n\t\t/* Relocation table found */\n\t\tif (shdr.sh_type == SHT_REL) {\n\t\t\tTRACE_LD(\"Found a REL section, this is not handled.\");\n\t\t} else if (shdr.sh_type == SHT_RELA) {\n\t\t\tElf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base);\n\t\t\twhile ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {\n\t\t\t\tunsigned int symbol = ELF64_R_SYM(table->r_info);\n\t\t\t\tunsigned int type = ELF64_R_TYPE(table->r_info);\n\t\t\t\tElf64_Sym * sym = &object->dyn_symbol_table[symbol];\n\n\t\t\t\t/* If we need symbol for this, get it. */\n\t\t\t\tchar * symname = NULL;\n\t\t\t\tuintptr_t x = sym->st_value;\n\t\t\t\tif (need_symbol_for_type(type) || (type == 5)) {\n\t\t\t\t\tsymname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);\n\t\t\t\t\tif (symname && hashmap_has(dumb_symbol_table, symname)) {\n\t\t\t\t\t\tx = (uintptr_t)hashmap_get(dumb_symbol_table, symname);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t/* This isn't fatal, but do log a message if debugging is enabled. */\n\t\t\t\t\t\tif ((sym->st_info >> 4) != STB_WEAK) {\n\t\t\t\t\t\t\textern char * _argv_0;\n\t\t\t\t\t\t\tfprintf(stderr, \"%s: undefined symbol %s\\n\",\n\t\t\t\t\t\t\t\t_argv_0, symname);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tTRACE_LD(\"Symbol not found: %s\", symname);\n\t\t\t\t\t\tx = 0x0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/* Relocations, symbol lookups, etc. */\n\t\t\t\tswitch (type) {\n#if defined(__x86_64__)\n\t\t\t\t\tcase R_X86_64_GLOB_DAT: /* 6 */\n\t\t\t\t\t\tif (symname && hashmap_has(glob_dat, symname)) {\n\t\t\t\t\t\t\tx = (uintptr_t)hashmap_get(glob_dat, symname);\n\t\t\t\t\t\t} /* fallthrough */\n\t\t\t\t\tcase R_X86_64_JUMP_SLOT: /* 7 */\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_RELATIVE: /* 8*/\n\t\t\t\t\t\tx = object->base;\n\t\t\t\t\t\tx += table->r_addend;\n\t\t\t\t\t\t//*((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_64: /* 1 */\n\t\t\t\t\t\tx += table->r_addend;\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_COPY: /* 5 */\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_TPOFF64:\n\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) {\n\t\t\t\t\t\t\tif (!sym->st_size) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"Haven't placed %s in static TLS yet but don't know its size?\\n\", symname);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx += current_tls_offset;\n\t\t\t\t\t\t\thashmap_set(tls_map, symname, (void*)(current_tls_offset));\n\t\t\t\t\t\t\tcurrent_tls_offset += sym->st_size; /* TODO alignment restrictions */\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tx += (size_t)hashmap_get(tls_map, symname);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_DTPMOD64:\n\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) { fprintf(stderr, \"tls entry is unallocated?\\n\"); break; }\n\t\t\t\t\t\tx = 0;\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase R_X86_64_DTPOFF64:\n\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) { fprintf(stderr, \"tls entry is unallocated?\\n\"); break; }\n\t\t\t\t\t\tx = table->r_addend;\n\t\t\t\t\t\tx += (size_t)hashmap_get(tls_map, symname);\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n#elif defined(__aarch64__)\n\t\t\t\t\tcase 1024: /* COPY */\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1025: /* GLOB_DAT */\n\t\t\t\t\t\tif (symname && hashmap_has(glob_dat, symname)) {\n\t\t\t\t\t\t\tx = (uintptr_t)hashmap_get(glob_dat, symname);\n\t\t\t\t\t\t} /* fallthrough */\n\t\t\t\t\tcase 1026: /* JUMP_SLOT */\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1027: /* RELATIVE */\n\t\t\t\t\t\tx = object->base;\n\t\t\t\t\t\tx += table->r_addend;\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 257: /* ABS64 */\n\t\t\t\t\t\tx += table->r_addend;\n\t\t\t\t\t\tmemcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1030: /* TLS_TPREL64 TPREL(S+A) */\n\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base)); /* A */\n\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) {\n\t\t\t\t\t\t\tif (!sym->st_size) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"Haven't placed %s in static TLS yet but don't know its size?\\n\", symname);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx += current_tls_offset;\n\t\t\t\t\t\t\thashmap_set(tls_map, symname, (void*)(current_tls_offset));\n\t\t\t\t\t\t\tcurrent_tls_offset += sym->st_size; /* TODO alignment restrictions */\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tsize_t val = (size_t)hashmap_get(tls_map, symname);\n\t\t\t\t\t\t\tTRACE_LD(\"add %#zx to %zx\\n\", val, x);\n\t\t\t\t\t\t\tx += val;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1031: {\n\t\t\t\t\t\tif (symbol) {\n\t\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"Warning: Don't know where to get %s (symbol %d) from TLS\\n\", symname, symbol);\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\t\tx += (size_t)hashmap_get(tls_map, symname);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t/* local tls descriptor? no symbol? idk what to do with this, hope it works */\n\t\t\t\t\t\t\tx = current_tls_offset;\n\t\t\t\t\t\t\tcurrent_tls_offset += 8*4; /* idk, alignment I guess */\n\t\t\t\t\t\t}\n\t\t\t\t\t\tuintptr_t func = (uintptr_t)&__tlsdesc_static;\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &func, sizeof(uintptr_t));\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base + sizeof(uintptr_t)), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n#else\n# error \"unsupported\"\n#endif\n#if 0\n\t\t\t\t\tcase 6: /* GLOB_DAT */\n\t\t\t\t\t\tif (symname && hashmap_has(glob_dat, symname)) {\n\t\t\t\t\t\t\tx = (uintptr_t)hashmap_get(glob_dat, symname);\n\t\t\t\t\t\t}\n\t\t\t\t\tcase 7: /* JUMP_SLOT */\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1: /* 32 */\n\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2: /* PC32 */\n\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tx -= (table->r_offset + object->base);\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 8: /* RELATIVE */\n\t\t\t\t\t\tx = object->base;\n\t\t\t\t\t\tx += *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5: /* COPY */\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 14: /* TLS_TPOFF */\n\t\t\t\t\t\tx = *((ssize_t *)(table->r_offset + object->base));\n\t\t\t\t\t\tif (!hashmap_has(tls_map, symname)) {\n\t\t\t\t\t\t\tif (!sym->st_size) {\n\t\t\t\t\t\t\t\tfprintf(stderr, \"Haven't placed %s in static TLS yet but don't know its size?\\n\", symname);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcurrent_tls_offset += sym->st_size; /* TODO alignment restrictions */\n\t\t\t\t\t\t\thashmap_set(tls_map, symname, (void*)(current_tls_offset));\n\t\t\t\t\t\t\tx -= current_tls_offset;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tx -= (size_t)hashmap_get(tls_map, symname);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmemcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t));\n\t\t\t\t\t\tbreak;\n#endif\n\t\t\t\t\tdefault:\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tchar msg[200];\n\t\t\t\t\t\t\tsnprintf(msg, 200, \"Unimplemented relocation (%d) requested, bailing.\\n\", type);\n\t\t\t\t\t\t\tsysfunc(TOARU_SYS_FUNC_LOGHERE, (char**)msg);\n\t\t\t\t\t\t\texit(1);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tTRACE_LD(\"Unknown relocation type: %d\", type);\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\ttable++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* Copy relocations are special and need to be located before other relocations. */\nstatic void object_find_copy_relocations(elf_t * object) {\n\n\tfor (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) {\n\t\tElf64_Shdr shdr;\n\t\tfseek(object->file, object->header.e_shoff + x, SEEK_SET);\n\t\tfread(&shdr, object->header.e_shentsize, 1, object->file);\n\n\t\t/* Relocation table found */\n\t\tif (shdr.sh_type == SHT_REL) {\n\t\t\tElf64_Rel * table = (Elf64_Rel *)(shdr.sh_addr + object->base);\n\t\t\twhile ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {\n\t\t\t\tunsigned int type = ELF64_R_TYPE(table->r_info);\n#if defined(__x86_64__)\n\t\t\t\tif (type == R_X86_64_COPY) {\n#elif defined(__aarch64__)\n\t\t\t\tif (type == R_AARCH64_COPY) {\n#else\n# error \"Unsupported\"\n#endif\n\t\t\t\t\tunsigned int  symbol = ELF64_R_SYM(table->r_info);\n\t\t\t\t\tElf64_Sym * sym = &object->dyn_symbol_table[symbol];\n\t\t\t\t\tchar * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);\n\t\t\t\t\thashmap_set(glob_dat, symname, (void *)table->r_offset);\n\t\t\t\t}\n\t\t\t\ttable++;\n\t\t\t}\n\t\t}\n\t\tif (shdr.sh_type == SHT_RELA) {\n\t\t\tElf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base);\n\t\t\twhile ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) {\n\t\t\t\tunsigned int type = ELF64_R_TYPE(table->r_info);\n#if defined(__x86_64__)\n\t\t\t\tif (type == R_X86_64_COPY) {\n#elif defined(__aarch64__)\n\t\t\t\tif (type == R_AARCH64_COPY) {\n#else\n# error \"Unsupported\"\n#endif\n\t\t\t\t\tunsigned int  symbol = ELF64_R_SYM(table->r_info);\n\t\t\t\t\tElf64_Sym * sym = &object->dyn_symbol_table[symbol];\n\t\t\t\t\tchar * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name);\n\t\t\t\t\thashmap_set(glob_dat, symname, (void *)table->r_offset);\n\t\t\t\t}\n\t\t\t\ttable++;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/* Find a symbol in a specific object. */\nstatic void * object_find_symbol(elf_t * object, const char * symbol_name) {\n\n\tif (!object->dyn_symbol_table) {\n\t\tlast_error = \"lib does not have a symbol table\";\n\t\treturn NULL;\n\t}\n\n\tElf64_Sym * table = object->dyn_symbol_table;\n\tsize_t i = 0;\n\twhile (i < object->dyn_symbol_table_size) {\n\t\tif (!strcmp(symbol_name, (char *)((uintptr_t)object->dyn_string_table + table->st_name))) {\n\t\t\treturn (void *)(table->st_value);\n\t\t}\n\t\ttable++;\n\t\ti++;\n\t}\n\n\tlast_error = \"symbol not found in library\";\n\treturn NULL;\n}\n\n/* Fully load an object. */\nstatic void * do_actual_load(const char * filename, elf_t * lib, int flags) {\n\t(void)flags;\n\n\tif (!lib) {\n\t\tlast_error = \"could not open library (not found, or other failure)\";\n\t\tTRACE_LD(\"could not open library\");\n\t\treturn NULL;\n\t}\n\n\tsize_t lib_size = object_calculate_size(lib);\n\n\t/* Needs to be at least a page. */\n\tif (lib_size < 4096) {\n\t\tlib_size = 4096;\n\t}\n\n\t/*\n\t * Allocate space to load the library\n\t * This is where we should really be loading things into COW\n\t * but we don't have the functionality available.\n\t */\n\tuintptr_t load_addr = (uintptr_t)valloc(lib_size);\n\tobject_load(lib, load_addr);\n\n\t/* Perform cleanup steps */\n\tobject_postload(lib);\n\n\t/* Ensure dependencies are available */\n\tnode_t * item;\n\twhile ((item = list_pop(lib->dependencies))) {\n\n\t\telf_t * _lib = open_object(item->value);\n\n\t\tif (!_lib) {\n\t\t\t/* Missing dependencies are fatal to this process, but\n\t\t\t * not to the entire application. */\n\t\t\tfree((void *)load_addr);\n\t\t\tlast_error = \"Failed to load a dependency.\";\n\t\t\tlib->loaded = 0;\n\t\t\tTRACE_LD(\"Failed to load object: %s\", item->value);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (!_lib->loaded) {\n\t\t\tdo_actual_load(item->value, _lib, 0);\n\t\t\tTRACE_LD(\"Loaded %s at 0x%x\", item->value, lib->base);\n\t\t}\n\n\t}\n\n\t/* Perform relocations */\n\tTRACE_LD(\"Relocating %s\", filename);\n\tobject_relocate(lib);\n\n\t/* We're done with the file. */\n\tfclose(lib->file);\n\n\t/* If there was an init_array, call everything in it */\n\tif (lib->init_array) {\n\t\tfor (size_t i = 0; i < lib->init_array_size; i++) {\n\t\t\tTRACE_LD(\" 0x%x()\", lib->init_array[i]);\n\t\t\tlib->init_array[i]();\n\t\t}\n\t}\n\n\t/* If the library has an init function, call that last. */\n\tif (lib->init) {\n\t\tlib->init();\n\t}\n\n\tlib->loaded = 1;\n\n\t/* And return an object for the loaded library */\n\treturn (void *)lib;\n}\n\nstatic uintptr_t end_addr = 0;\n/**\n * Half loads an object using the dumb allocator and performs dependency\n * resolution. This is a separate process from do_actual_load and dlopen_ld\n * to avoid problems with malloc and library functions while loading.\n * Preloaded objects will be fully loaded everything is relocated.\n */\nstatic elf_t * preload(hashmap_t * libs, list_t * load_libs, char * lib_name) {\n\t/* Find and open the library */\n\telf_t * lib = open_object(lib_name);\n\n\tif (!lib) {\n\t\tfprintf(stderr, \"Failed to load dependency '%s'.\\n\", lib_name);\n\t\treturn NULL;\n\t}\n\n\t/* Skip already loaded libraries */\n\tif (lib->loaded) return lib;\n\n\t/* Mark this library available */\n\thashmap_set(libs, lib_name, lib);\n\n\t/* Adjust dumb allocator */\n\tend_addr = (end_addr + 0xFFF) & ~(0xFFFUL);\n\n\tTRACE_LD(\"Loading %s at 0x%x\", lib_name, end_addr);\n\n\t/* Load PHDRs */\n\tend_addr = object_load(lib, end_addr);\n\n\t/* Extract information */\n\tobject_postload(lib);\n\n\t/* Mark loaded */\n\tlib->loaded = 1;\n\n\t/* Verify dependencies are loaded before we relocate */\n\tforeach(node, lib->dependencies) {\n\t\tif (!hashmap_has(libs, node->value)) {\n\t\t\tTRACE_LD(\"Need unloaded dependency %s\", node->value);\n\t\t\tpreload(libs, load_libs, node->value);\n\t\t}\n\t}\n\n\t/* Add this to the (forward scan) list of libraries to finish loading */\n\tlist_insert(load_libs, lib);\n\n\treturn lib;\n}\n\n/* exposed dlopen() method */\nstatic void * dlopen_ld(const char * filename, int flags) {\n\tTRACE_LD(\"dlopen(%s,0x%x)\", filename, flags);\n\n\telf_t * lib = open_object(filename);\n\n\tif (!lib) {\n\t\treturn NULL;\n\t}\n\n\tif (lib->loaded) {\n\t\treturn lib;\n\t}\n\n\tvoid * ret = do_actual_load(filename, lib, flags);\n\tif (!ret) {\n\t\t/* Dependency load failure, remove us from hash */\n\t\tTRACE_LD(\"Dependency load failure\");\n\t\thashmap_remove(objects_map, (void*)filename);\n\t}\n\n\tTRACE_LD(\"Loaded %s at 0x%x\", filename, lib->base);\n\treturn ret;\n}\n\n/* exposed dlclose() method - XXX not fully implemented */\nstatic int dlclose_ld(elf_t * lib) {\n\t/* TODO close dependencies? Make sure nothing references this. */\n\tfree((void *)lib->base);\n\treturn 0;\n}\n\n/* exposed dlerror() method */\nstatic char * dlerror_ld(void) {\n\tchar * this_error = last_error;\n\tlast_error = NULL;\n\treturn this_error;\n}\n\n/* Specially used by libc */\nstatic void * _argv_value = NULL;\nstatic char * argv_value(void) {\n\treturn _argv_value;\n}\n\nstatic uintptr_t dl_symbol_table_ptr_addr(void) {\n\treturn (uintptr_t)&dumb_symbol_table;\n}\n\nstatic uintptr_t dl_objects_table_ptr_addr(void) {\n\treturn (uintptr_t)&objects_map;\n}\n\n/* Exported methods (dlfcn) */\ntypedef struct {\n\tchar * name;\n\tvoid * symbol;\n} ld_exports_t;\nld_exports_t ld_builtin_exports[] = {\n\t{\"dlopen\", dlopen_ld},\n\t{\"dlsym\", object_find_symbol},\n\t{\"dlclose\", dlclose_ld},\n\t{\"dlerror\", dlerror_ld},\n\t{\"__get_argv\", argv_value},\n\t{\"__ld_symbol_table\", dl_symbol_table_ptr_addr},\n\t{\"__ld_objects_table\", dl_objects_table_ptr_addr},\n\t{NULL, NULL},\n};\n\nint main(int argc, char * argv[]) {\n\tif (argc < 2) {\n\t\tfprintf(stderr,\n\t\t\t\t\"ld.so - dynamic binary loader\\n\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\"usage: %s [-e] [EXECUTABLE PATH]\\n\"\n\t\t\t\t\"\\n\"\n\t\t\t\t\" -e     \\033[3mAdjust argument offset\\033[0m\\n\"\n\t\t\t\t\"\\n\", argv[0]);\n\t\treturn -1;\n\t}\n\n\tchar * file = argv[1];\n\tsize_t arg_offset = 1;\n\n\tif (!strcmp(argv[1], \"-e\")) {\n\t\targ_offset = 3;\n\t\tfile = argv[2];\n\t}\n\n\t_argv_value = argv+arg_offset;\n\n\t/* Enable tracing if requested */\n\tchar * trace_ld_env = getenv(\"LD_DEBUG\");\n\tif (trace_ld_env && (!strcmp(trace_ld_env,\"1\") || !strcmp(trace_ld_env,\"yes\"))) {\n\t\t__trace_ld = 1;\n\t}\n\n\t/* Initialize hashmaps for symbols, GLOB_DATs, and objects */\n\tdumb_symbol_table = hashmap_create(100);\n\tglob_dat = hashmap_create(10);\n\tobjects_map = hashmap_create(10);\n\ttls_map = hashmap_create(10);\n\n\t/* Setup symbols for built-in exports */\n\tld_exports_t * ex = ld_builtin_exports;\n\twhile (ex->name) {\n\t\thashmap_set(dumb_symbol_table, ex->name, ex->symbol);\n\t\tex++;\n\t}\n\n\t/* Technically there's a potential time-of-use probably if we check like this but\n\t * this is a toy linker for a toy OS so the fact that we even need to check suid\n\t * bits at all is outrageous\n\t */\n\tstruct stat buf;\n\tif (stat(file, &buf)) {\n\t\tfprintf(stderr, \"%s: target binary '%s' not available\\n\", argv[0], file);\n\t}\n\n\t/* Technically there's a way to know we're running suid, but let's check the actual file */\n\tif (buf.st_mode & S_ISUID) {\n\t\t_target_is_suid = 1;\n\t}\n\n\t/* Open the requested main object */\n\telf_t * main_obj = open_object(file);\n\t_main_obj = main_obj;\n\n\tif (!main_obj) {\n\t\tfprintf(stderr, \"%s: error: failed to open object '%s'.\\n\", argv[0], file);\n\t\treturn 1;\n\t}\n\n\t/* Load the main object */\n\tend_addr = object_load(main_obj, 0x0);\n\tobject_postload(main_obj);\n\tobject_find_copy_relocations(main_obj);\n\n\t/* Load library dependencies */\n\thashmap_t * libs = hashmap_create(10);\n\n\twhile (end_addr & 0xFFF) {\n\t\tend_addr++;\n\t}\n\n\t/* Load dependent libraries, recursively. */\n\tTRACE_LD(\"Loading dependencies.\");\n\tlist_t * load_libs = list_create();\n\tnode_t * item;\n\twhile ((item = list_pop(main_obj->dependencies))) {\n\t\tchar * lib_name = item->value;\n\n\t\t/* Skip libg.so which is a fake library that doesn't really exist.\n\t\t * XXX: Only binaries should depend on this I think? */\n\t\tif (!strcmp(lib_name, \"libg.so\")) goto nope;\n\n\t\t/* Preload library */\n\t\telf_t * lib = preload(libs, load_libs, lib_name);\n\n\t\t/* Failed to load */\n\t\tif (!lib) return 1;\n\nnope:\n\t\tfree(item);\n\t}\n\n\tlist_t * ctor_libs = list_create();\n\tlist_t * init_libs = list_create();\n\twhile ((item = list_dequeue(load_libs))) {\n\t\telf_t * lib = item->value;\n\n\t\t/* Complete relocation */\n\t\tobject_relocate(lib);\n\n\t\t/* Close the underlying file */\n\t\tfclose(lib->file);\n\n\t\t/* Store constructors for later execution */\n\t\tif (lib->init_array) {\n\t\t\tlist_insert(ctor_libs, lib);\n\t\t}\n\t\tif (lib->init) {\n\t\t\tlist_insert(init_libs, lib);\n\t\t}\n\n\t\tfree(item);\n\t}\n\n\t/* Relocate the main object */\n\tTRACE_LD(\"Relocating main object\");\n\tobject_relocate(main_obj);\n\tfclose(main_obj->file);\n\tTRACE_LD(\"Placing heap at end\");\n\twhile (end_addr & 0xFFF) {\n\t\tend_addr++;\n\t}\n\n\t/* Move heap start (kind of like a weird sbrk) */\n\t{\n\t\tchar * args[] = {(char*)end_addr};\n\t\tsysfunc(TOARU_SYS_FUNC_SETHEAP, args);\n\t}\n\n\t/* Call constructors for loaded dependencies */\n\tchar * ld_no_ctors = getenv(\"LD_DISABLE_CTORS\");\n\tif (ld_no_ctors && (!strcmp(ld_no_ctors,\"1\") || !strcmp(ld_no_ctors,\"yes\"))) {\n\t\tTRACE_LD(\"skipping ctors because LD_DISABLE_CTORS was set\");\n\t} else {\n\t\tforeach(node, ctor_libs) {\n\t\t\telf_t * lib = node->value;\n\t\t\tif (lib->init_array) {\n\t\t\t\tTRACE_LD(\"Executing init_array...\");\n\t\t\t\tfor (size_t i = 0; i < lib->init_array_size; i++) {\n\t\t\t\t\tTRACE_LD(\" 0x%x()\", lib->init_array[i]);\n\t\t\t\t\tlib->init_array[i]();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tforeach(node, init_libs) {\n\t\telf_t * lib = node->value;\n\t\tlib->init();\n\t}\n\n\t/* If main object had constructors, call them. */\n\tif (main_obj->init_array) {\n\t\tfor (size_t i = 0; i < main_obj->init_array_size; i++) {\n\t\t\tTRACE_LD(\" 0x%x()\", main_obj->init_array[i]);\n\t\t\tmain_obj->init_array[i]();\n\t\t}\n\t}\n\n\tif (main_obj->init) {\n\t\tmain_obj->init();\n\t}\n\n\tmain_obj->loaded = 1;\n\n\t/* Set heap functions for later usage */\n\tif (hashmap_has(dumb_symbol_table, \"malloc\")) _malloc = hashmap_get(dumb_symbol_table, \"malloc\");\n\tif (hashmap_has(dumb_symbol_table, \"free\")) _free = hashmap_get(dumb_symbol_table, \"free\");\n\t_malloc_minimum = 0x40000000;\n\n\t/* Jump to the entry for the main object */\n\tTRACE_LD(\"Jumping to entry point 0x%lx\", main_obj->header.e_entry);\n\tentry_point_t entry = (entry_point_t)main_obj->header.e_entry;\n\tentry(argc-arg_offset,argv+arg_offset,environ);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "modules/ac97.c",
    "content": "/**\n * @file kernel/audio/ac97.c\n * @brief Driver for the Intel AC'97.\n * @package x86_64\n * @package aarch64\n *\n * Simple PCM interface for the AC'97 codec when used with the\n * ICH hardware interface. There are other hardware interfaces\n * that use this codec and this driver could probably be ported\n * to them.\n *\n * Note that the audio subsystem is intended to be non-blocking\n * so that buffer filling can be done directly in interrupt handlers.\n *\n * @see http://www.intel.com/design/chipsets/manuals/29802801.pdf\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2015 Michael Gerow\n * Copyright (C) 2015-2021 K. Lange\n */\n\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n#include <kernel/list.h>\n#include <kernel/module.h>\n#include <kernel/mod/snd.h>\n\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/irq.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/gic.h>\n#endif\n\n/* Utility macros */\n#define N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0]))\n\n/* BARs! */\n#define AC97_NAMBAR  0x10  /* Native Audio Mixer Base Address Register */\n#define AC97_NABMBAR 0x14  /* Native Audio Bus Mastering Base Address Register */\n\n/* Bus mastering IO port offsets */\n#define AC97_PO_BDBAR 0x10  /* PCM out buffer descriptor BAR */\n#define AC97_PO_CIV   0x14  /* PCM out current index value */\n#define AC97_PO_LVI   0x15  /* PCM out last valid index */\n#define AC97_PO_SR    0x16  /* PCM out status register */\n#define AC97_PO_PICB  0x18  /* PCM out position in current buffer register */\n#define AC97_PO_CR    0x1B  /* PCM out control register */\n\n/* Bus mastering misc */\n/* Buffer descriptor list constants */\n#define AC97_BDL_LEN              32                    /* Buffer descriptor list length */\n#define AC97_BDL_BUFFER_LEN       0x1000                /* Length of buffer in BDL */\n#define AC97_CL_GET_LENGTH(cl)    ((cl) & 0xFFFF)       /* Decode length from cl */\n#define AC97_CL_SET_LENGTH(cl, v) ((cl) = (v) & 0xFFFF) /* Encode length to cl */\n#define AC97_CL_BUP               ((uint32_t)1 << 30)             /* Buffer underrun policy in cl */\n#define AC97_CL_IOC               ((uint32_t)1 << 31)             /* Interrupt on completion flag in cl */\n\n/* PCM out control register flags */\n#define AC97_X_CR_RPBM  (1 << 0)  /* Run/pause bus master */\n#define AC97_X_CR_RR    (1 << 1)  /* Reset registers */\n#define AC97_X_CR_LVBIE (1 << 2)  /* Last valid buffer interrupt enable */\n#define AC97_X_CR_FEIE  (1 << 3)  /* FIFO error interrupt enable */\n#define AC97_X_CR_IOCE  (1 << 4)  /* Interrupt on completion enable */\n\n/* Status register flags */\n#define AC97_X_SR_DCH   (1 << 0)  /* DMA controller halted */\n#define AC97_X_SR_CELV  (1 << 1)  /* Current equals last valid */\n#define AC97_X_SR_LVBCI (1 << 2)  /* Last valid buffer completion interrupt */\n#define AC97_X_SR_BCIS  (1 << 3)  /* Buffer completion interrupt status */\n#define AC97_X_SR_FIFOE (1 << 4)  /* FIFO error */\n\n/* Mixer IO port offsets */\n#define AC97_RESET          0x00\n#define AC97_MASTER_VOLUME  0x02\n#define AC97_AUX_OUT_VOLUME 0x04\n#define AC97_MONO_VOLUME    0x06\n#define AC97_PCM_OUT_VOLUME 0x18\n\n/* snd values */\n#define AC97_SND_NAME \"Intel AC'97\"\n#define AC97_PLAYBACK_SPEED 48000\n#define AC97_PLAYBACK_FORMAT SND_FORMAT_L16SLE\n\n/* An entry in a buffer dscriptor list */\ntypedef struct {\n\tuint32_t pointer;  /* Pointer to buffer */\n\tuint32_t cl;       /* Control values and buffer length */\n} __attribute__((packed)) ac97_bdl_entry_t;\n\ntypedef struct {\n\tuint32_t pci_device;\n\tuint16_t nabmbar;               /* Native audio bus mastring BAR */\n\tuint16_t nambar;                /* Native audio mixing BAR */\n\tsize_t irq;                     /* This ac97's irq */\n\tuint8_t lvi;                    /* The currently set last valid index */\n\tuint8_t bits;                   /* How many bits of volume are supported (5 or 6) */\n\tac97_bdl_entry_t * bdl;         /* Buffer descriptor list */\n\tuint16_t * bufs[AC97_BDL_LEN];  /* Virtual addresses for buffers in BDL */\n\tuint32_t bdl_p;\n\tuint32_t mask;\n\tvolatile char *  _iobase;\n\tspin_lock_t lock;\n} ac97_device_t;\n\nstatic ac97_device_t _device;\n\n#if defined(__aarch64__)\nstatic uint8_t inportb(size_t port) {\n\tvolatile uint8_t * _port = (volatile uint8_t*)(_device._iobase + port);\n\treturn *_port;\n}\n\nstatic uint16_t inports(size_t port) {\n\tvolatile uint16_t * _port = (volatile uint16_t*)(_device._iobase + port);\n\treturn *_port;\n}\n\nstatic uint32_t inportl(size_t port) {\n\tvolatile uint32_t * _port = (volatile uint32_t*)(_device._iobase + port);\n\treturn *_port;\n}\n\nstatic void outportb(size_t port, uint8_t val) {\n\tvolatile uint8_t * _port = (volatile uint8_t*)(_device._iobase + port);\n\t*_port = val;\n}\n\nstatic void outports(size_t port, uint16_t val) {\n\tvolatile uint16_t * _port = (volatile uint16_t*)(_device._iobase + port);\n\t*_port = val;\n}\n\nstatic void outportl(size_t port, uint32_t val) {\n\tvolatile uint32_t * _port = (volatile uint32_t*)(_device._iobase + port);\n\t*_port = val;\n}\n#endif\n\n\n#define AC97_KNOB_PCM_OUT (SND_KNOB_VENDOR + 0)\n\nstatic snd_knob_t _knobs[] = {\n\t{\n\t\t\"Master\",\n\t\tSND_KNOB_MASTER\n\t},\n\t{\n\t\t\"PCM Out\",\n\t\tSND_KNOB_VENDOR + 0\n\t}\n};\n\nstatic int ac97_mixer_read(uint32_t knob_id, uint32_t *val);\nstatic int ac97_mixer_write(uint32_t knob_id, uint32_t val);\n\nstatic snd_device_t _snd = {\n\t.name            = AC97_SND_NAME,\n\t.device          = &_device,\n\t.playback_speed  = AC97_PLAYBACK_SPEED,\n\t.playback_format = AC97_PLAYBACK_FORMAT,\n\n\t.knobs     = _knobs,\n\t.num_knobs = N_ELEMENTS(_knobs),\n\n\t.mixer_read  = ac97_mixer_read,\n\t.mixer_write = ac97_mixer_write,\n};\n\n/* \n * This could be unnecessary if we instead allocate just two buffers and make\n * the ac97 think there are more.\n */\n\nstatic void find_ac97(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\n\tac97_device_t * ac97 = extra;\n\n\tif ((vendorid == 0x8086) && (deviceid == 0x2415)) {\n\t\tac97->pci_device = device;\n\t}\n\n}\n\n#define DIVISION 0x1000\n#if defined(__x86_64__)\nstatic int ac97_irq_handler(struct regs * regs) {\n#elif defined(__aarch64__)\nint ac97_irq_handler(process_t * this, int irq, void * data) {\n#endif\n\tspin_lock(_device.lock);\n\tuint16_t sr = inports(_device.nabmbar + AC97_PO_SR);\n\tif (sr & AC97_X_SR_BCIS) {\n\t\toutports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_BCIS);\n\t\tspin_unlock(_device.lock);\n\t\tuint16_t current_buffer = inportb(_device.nabmbar + AC97_PO_CIV);\n\t\tuint16_t last_valid = ((current_buffer+2) & (AC97_BDL_LEN-1));\n\t\tsnd_request_buf(&_snd, 0x1000, (uint8_t *)_device.bufs[last_valid]);\n\t\toutportb(_device.nabmbar + AC97_PO_LVI, last_valid);\n\t\tsnd_request_buf(&_snd, 0x1000, (uint8_t *)_device.bufs[last_valid]+0x1000);\n\t} else if (sr & AC97_X_SR_LVBCI) {\n\t\toutports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_LVBCI);\n\t\tspin_unlock(_device.lock);\n\t} else if (sr & AC97_X_SR_FIFOE) {\n\t\toutports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_FIFOE);\n\t\tspin_unlock(_device.lock);\n\t} else {\n\t\tspin_unlock(_device.lock);\n\t\treturn 0;\n\t}\n#ifdef __x86_64__\n\tirq_ack(_device.irq);\n#endif\n\treturn 1;\n}\n\n/* Currently we just assume right and left are the same */\nstatic int ac97_mixer_read(uint32_t knob_id, uint32_t *val) {\n\tuint16_t tmp;\n\tswitch (knob_id) {\n\t\tcase SND_KNOB_MASTER:\n\t\t\ttmp = inports(_device.nambar + AC97_MASTER_VOLUME);\n\t\t\tif (tmp == 0x8000) {\n\t\t\t\t*val = 0;\n\t\t\t} else {\n\t\t\t\t/* 6 bit value */\n\t\t\t\t*val = (tmp & _device.mask) << (sizeof(*val) * 8 - _device.bits);\n\t\t\t\t*val = ~*val;\n\t\t\t\t*val &= (uint32_t)_device.mask << (sizeof(*val) * 8 - _device.bits);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase AC97_KNOB_PCM_OUT:\n\t\t\ttmp = inports(_device.nambar + AC97_PCM_OUT_VOLUME);\n\t\t\tif (tmp == 0x8000) {\n\t\t\t\t*val = 0;\n\t\t\t} else {\n\t\t\t\t/* 5 bit value */\n\t\t\t\t*val = (tmp & 0x1f) << (sizeof(*val) * 8 - 5);\n\t\t\t\t*val = ~*val;\n\t\t\t\t*val &= 0x1f << (sizeof(*val) * 8 - 5);\n\t\t\t}\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int ac97_mixer_write(uint32_t knob_id, uint32_t val) {\n\tswitch (knob_id) {\n\t\tcase SND_KNOB_MASTER: {\n\t\t\tuint16_t encoded;\n\t\t\tif (val == 0x0) {\n\t\t\t\tencoded = 0x8000;\n\t\t\t} else {\n\t\t\t\t/* 0 is the highest volume */\n\t\t\t\tval = ~val;\n\t\t\t\t/* 6 bit value */\n\t\t\t\tval >>= (sizeof(val) * 8 - _device.bits);\n\t\t\t\tencoded = (val & 0xFF) | (val << 8);\n\t\t\t}\n\t\t\toutports(_device.nambar + AC97_MASTER_VOLUME, encoded);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase AC97_KNOB_PCM_OUT: {\n\t\t\tuint16_t encoded;\n\t\t\tif (val == 0x0) {\n\t\t\t\tencoded = 0x8000;\n\t\t\t} else {\n\t\t\t\t/* 0 is the highest volume */\n\t\t\t\tval = ~val;\n\t\t\t\t/* 5 bit value */\n\t\t\t\tval >>= (sizeof(val) * 8 - 5);\n\t\t\t\tencoded = (val & 0xFF) | (val << 8);\n\t\t\t}\n\t\t\toutports(_device.nambar + AC97_PCM_OUT_VOLUME, encoded);\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n\nstatic int ac97_install(int argc, char * argv[]) {\n\t//debug_print(NOTICE, \"Initializing AC97\");\n\tpci_scan(&find_ac97, -1, &_device);\n\tif (!_device.pci_device) {\n\t\treturn -ENODEV;\n\t}\n\n\n#if defined(__aarch64__)\n\tpci_write_field(_device.pci_device, PCI_COMMAND, 2, 0x5);\n\tpci_write_field(_device.pci_device, AC97_NABMBAR, 2, 0x1001);\n\tpci_write_field(_device.pci_device, PCI_BAR0, 4, 0x2001);\n\t_device._iobase = (volatile char *)mmu_map_mmio_region(0x3eff0000, 0x3000);\n\tasm volatile (\"isb\" ::: \"memory\");\n#endif\n\n\t_device.nabmbar = pci_read_field(_device.pci_device, AC97_NABMBAR, 2) & ((uint32_t) -1) << 1;\n\t_device.nambar = pci_read_field(_device.pci_device, PCI_BAR0, 4) & ((uint32_t) -1) << 1;\n\n\t#if defined(__x86_64__)\n\t_device.irq = pci_get_interrupt(_device.pci_device);\n\t//printf(\"device wants irq %zd\\n\", _device.irq);\n\tirq_install_handler(_device.irq, ac97_irq_handler, \"ac97\");\n\t#elif defined(__aarch64__)\n\tint irq;\n\tgic_map_pci_interrupt(\"ac97\",_device.pci_device,&irq,ac97_irq_handler,&_device);\n\t_device.irq = irq;\n\t#endif\n\n\t/* Enable all matter of interrupts */\n\toutportb(_device.nabmbar + AC97_PO_CR, AC97_X_CR_FEIE | AC97_X_CR_IOCE);\n\n\t/* Enable bus mastering and disable memory mapped space */\n\tpci_write_field(_device.pci_device, PCI_COMMAND, 2, 0x5);\n\t/* Default the PCM output to full volume. */\n\toutports(_device.nambar + AC97_PCM_OUT_VOLUME, 0x0000);\n\n\t/* Allocate our BDL and our buffers */\n\t_device.bdl_p = mmu_allocate_a_frame() << 12;\n\t_device.bdl   = mmu_map_from_physical(_device.bdl_p);\n\tmemset(_device.bdl, 0, AC97_BDL_LEN * sizeof(*_device.bdl));\n\n\tfor (int i = 0; i < AC97_BDL_LEN; i++) {\n\t\t_device.bdl[i].pointer = mmu_allocate_n_frames(2) << 12;\n\t\t_device.bufs[i] = mmu_map_from_physical(_device.bdl[i].pointer);\n\t\tmemset(_device.bufs[i], 0, AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0]));\n\t\tAC97_CL_SET_LENGTH(_device.bdl[i].cl, AC97_BDL_BUFFER_LEN);\n\t\t/* Set all buffers to interrupt */\n\t\t_device.bdl[i].cl |= AC97_CL_IOC;\n\n\t}\n\t/* Tell the ac97 where our BDL is */\n\toutportl(_device.nabmbar + AC97_PO_BDBAR, _device.bdl_p);\n\t/* Set the LVI to be the last index */\n\t_device.lvi = 2;\n\toutportb(_device.nabmbar + AC97_PO_LVI, _device.lvi);\n\n\t/* detect whether device supports MSB */\n\toutports(_device.nambar + AC97_MASTER_VOLUME, 0x2020);\n\tuint16_t t = inports(_device.nambar + AC97_MASTER_VOLUME) & 0x1f;\n\tif (t == 0x1f) {\n\t\t//debug_print(WARNING, \"This device only supports 5 bits of audio volume.\");\n\t\t_device.bits = 5;\n\t\t_device.mask = 0x1f;\n\t} else {\n\t\t_device.bits = 6;\n\t\t_device.mask = 0x3f;\n\t}\n\toutports(_device.nambar + AC97_MASTER_VOLUME, 0x0000);\n\n\tsnd_register(&_snd);\n\n\t/* Start things playing */\n\toutportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RPBM);\n\n\t//debug_print(NOTICE, \"AC97 initialized successfully\");\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\tsnd_unregister(&_snd);\n\n\tfree(_device.bdl);\n\tfor (int i = 0; i < AC97_BDL_LEN; i++) {\n\t\tfree(_device.bufs[i]);\n\t}\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"ac97\",\n\t.init = ac97_install,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/ahci.c",
    "content": "/**\n * @brief AHCI Block Device Driver\n * @file modules/ahci.c\n * @package x86_64\n *\n * @warning This is a stub driver.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/syscall.h>\n#include <kernel/module.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n\nstatic uint32_t mmio_read4(uintptr_t mmiobase, intptr_t offset) {\n\tvolatile uint32_t * data = (volatile uint32_t *)(mmiobase + offset);\n\treturn *data;\n}\n\nstatic void mmio_write4(uintptr_t mmiobase, intptr_t offset, uint32_t value) {\n\tvolatile uint32_t * data = (volatile uint32_t *)(mmiobase + offset);\n\t*data = value;\n}\n\nstatic char * ahci_device_name(uint32_t pcidev, int port) {\n\tstatic char buf[20];\n\tsnprintf(buf,19,\"ahcip%ds%dp%d\",\n\t\t\t(int)pci_extract_bus(pcidev),\n\t\t\t(int)pci_extract_slot(pcidev),\n\t\t\tport);\n\treturn buf;\n}\n\n#define AHCI_PXCMD_ST    (1 << 0UL)\n#define AHCI_PXCMD_SUD   (1 << 1UL)\n#define AHCI_PXCMD_POD   (1 << 2UL)\n#define AHCI_PXCMD_CLO   (1 << 3UL)\n#define AHCI_PXCMD_FRE   (1 << 4UL)\n#define AHCI_PXCMD_MPSS  (1 << 13UL)\n#define AHCI_PXCMD_FR    (1 << 14UL)\n#define AHCI_PXCMD_CR    (1 << 15UL)\n\n#define DPRINT(fmt,...) fprintf(stderr, \"%s: \" fmt, ahci_device_name(pcidev,port), ##__VA_ARGS__)\nstatic void ahci_setup_atapi(fs_node_t * stderr, uint32_t pcidev, uintptr_t mmio_addr, int port) {\n\tintptr_t offset = 0x100 + port * 0x80;\n\tDPRINT(\"setting up ATAPI device\\n\");\n\tuint32_t PxCMD = mmio_read4(mmio_addr, offset + 0x18);\n\tDPRINT(\"device cmd: %#x\\n\", PxCMD);\n\tif (PxCMD & AHCI_PXCMD_ST)  DPRINT(\"  started (not idle!)\\n\");\n\tif (PxCMD & AHCI_PXCMD_FRE) DPRINT(\"  FIS receive enable (not idle!)\\n\");\n\tif (PxCMD & AHCI_PXCMD_FR)  DPRINT(\"  FIS receive running (not idle!)\\n\");\n\tif (PxCMD & AHCI_PXCMD_CR)  DPRINT(\"  command list running (not idle!)\\n\");\n\n\tif (PxCMD & (AHCI_PXCMD_ST | AHCI_PXCMD_FRE | AHCI_PXCMD_FR | AHCI_PXCMD_CR)) {\n\t\tDPRINT(\"Not idle, setting to idle state...\\n\");\n\t\tPxCMD &= ~(AHCI_PXCMD_ST);\n\t\tmmio_write4(mmio_addr, offset + 0x18, PxCMD);\n\t\tDPRINT(\"Waiting for device...\\n\");\n\t\twhile (mmio_read4(mmio_addr, offset + 0x18) & AHCI_PXCMD_CR);\n\t\tDPRINT(\"Device is stopped.\\n\");\n\t}\n}\n\nstatic void find_ahci(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tif (pci_find_type(device) != 0x0106) return; /* Mass Storage, SATA controller */\n\tif (pci_read_field(device, PCI_PROG_IF, 1) != 0x01) return; /* AHCI */\n\tfs_node_t * stderr = extra;\n\n\tfprintf(stderr, \"ahci: located device at %#x\\n\", device);\n\n\tuint16_t command_reg = pci_read_field(device, PCI_COMMAND, 2);\n\tcommand_reg |= (1 << 2);\n\tcommand_reg |= (1 << 1);\n\tcommand_reg ^= (1 << 10);\n\tpci_write_field(device, PCI_COMMAND, 2, command_reg);\n\n\tfprintf(stderr, \"ahci: examining PCI config space...\\n\");\n\tfprintf(stderr, \"ahci: interrupt line = %d\\n\", pci_get_interrupt(device));\n\tfprintf(stderr, \"ahci: BAR5 = %#x\\n\", pci_read_field(device, PCI_BAR5, 4));\n\n\tuintptr_t mmio_addr = (uintptr_t)mmu_map_mmio_region(pci_read_field(device, PCI_BAR5, 4) & 0xFFFFFFF0, 0x2000); /* I have no idea how much space this needs */\n\tfprintf(stderr, \"ahci: mapping mmio to %#zx\\n\", mmio_addr);\n\n\tuint32_t enabledPorts = mmio_read4(mmio_addr, 0x0C);\n\tfprintf(stderr, \"ahci: implemented ports = %#x\\n\", enabledPorts);\n\n\tuint32_t ahciVersion = mmio_read4(mmio_addr, 0x10);\n\tfprintf(stderr, \"ahci: version %d.%d%d\\n\",\n\t\t(ahciVersion >> 16) & 0xFFF,\n\t\t(ahciVersion >> 8) & 0xFF,\n\t\t(ahciVersion) & 0xFF);\n\n\tfprintf(stderr, \"ahci: Telling host controller we are aware of it.\\n\");\n\tmmio_write4(mmio_addr, 0x04, mmio_read4(mmio_addr, 0x04) | (1 << 31UL));\n\n\tint offset = 0x100;\n\tfor (int port = 0; port < 32; ++port) {\n\t\tif (enabledPorts & (1UL << port)) {\n\t\t\t/* Check status */\n\t\t\tuint32_t portSig    = mmio_read4(mmio_addr, offset + 0x24);\n\t\t\tuint32_t portStatus = mmio_read4(mmio_addr, offset + 0x28);\n\t\t\tfprintf(stderr, \"ahci: port %d: status = %#x\\n\", port, portStatus);\n\t\t\tfprintf(stderr, \"ahci: port %d: sig    = %#x\\n\", port, portSig);\n\n\t\t\tswitch (portSig) {\n\t\t\t\tcase 0xeb140101:\n\t\t\t\t\tfprintf(stderr, \"ahci:           ATAPI (CD, DVD)\\n\");\n\t\t\t\t\tahci_setup_atapi(stderr, device, mmio_addr, port);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0x00000101:\n\t\t\t\t\tfprintf(stderr, \"ahci:           hard disk\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase 0xffff0101:\n\t\t\t\t\tfprintf(stderr, \"ahci:           no device\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tfprintf(stderr, \"ahci:           unsupported/unknown\\n\");\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t}\n\t\toffset += 0x80;\n\t}\n\n\n\t\n}\n\nstatic int init(int argc, char * argv[]) {\n\tfs_node_t * node = FD_ENTRY(1); /* Get the stdout for the process that loaded the module */\n\tpci_scan(find_ahci, -1, node);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"ahci\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/ata.c",
    "content": "/**\n * @brief (P)ATA / IDE disk driver\n * @file modules/ata.c\n * @package x86_64\n *\n * @warning This is very buggy.\n *\n * This is a port of the original ATA driver for toaru32.\n * It has a number of issues. It should not be considered\n * stable, or a viable reference.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/syscall.h>\n#include <kernel/module.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/vfs.h>\n#include <kernel/mmu.h>\n#include <kernel/list.h>\n#include <kernel/time.h>\n#include <kernel/misc.h>\n#include <kernel/mutex.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#include <sys/ioctl.h>\n\n#define ATA_SR_BSY     0x80\n#define ATA_SR_DRDY    0x40\n#define ATA_SR_DF      0x20\n#define ATA_SR_DSC     0x10\n#define ATA_SR_DRQ     0x08\n#define ATA_SR_CORR    0x04\n#define ATA_SR_IDX     0x02\n#define ATA_SR_ERR     0x01\n\n#define ATA_ER_BBK      0x80\n#define ATA_ER_UNC      0x40\n#define ATA_ER_MC       0x20\n#define ATA_ER_IDNF     0x10\n#define ATA_ER_MCR      0x08\n#define ATA_ER_ABRT     0x04\n#define ATA_ER_TK0NF    0x02\n#define ATA_ER_AMNF     0x01\n\n#define ATA_CMD_READ_PIO          0x20\n#define ATA_CMD_READ_PIO_EXT      0x24\n#define ATA_CMD_READ_DMA          0xC8\n#define ATA_CMD_READ_DMA_EXT      0x25\n#define ATA_CMD_WRITE_PIO         0x30\n#define ATA_CMD_WRITE_PIO_EXT     0x34\n#define ATA_CMD_WRITE_DMA         0xCA\n#define ATA_CMD_WRITE_DMA_EXT     0x35\n#define ATA_CMD_CACHE_FLUSH       0xE7\n#define ATA_CMD_CACHE_FLUSH_EXT   0xEA\n#define ATA_CMD_PACKET            0xA0\n#define ATA_CMD_IDENTIFY_PACKET   0xA1\n#define ATA_CMD_IDENTIFY          0xEC\n\n#define ATAPI_CMD_READ       0xA8\n#define ATAPI_CMD_EJECT      0x1B\n\n#define ATA_IDENT_DEVICETYPE   0\n#define ATA_IDENT_CYLINDERS    2\n#define ATA_IDENT_HEADS        6\n#define ATA_IDENT_SECTORS      12\n#define ATA_IDENT_SERIAL       20\n#define ATA_IDENT_MODEL        54\n#define ATA_IDENT_CAPABILITIES 98\n#define ATA_IDENT_FIELDVALID   106\n#define ATA_IDENT_MAX_LBA      120\n#define ATA_IDENT_COMMANDSETS  164\n#define ATA_IDENT_MAX_LBA_EXT  200\n\n#define IDE_ATA        0x00\n#define IDE_ATAPI      0x01\n \n#define ATA_MASTER     0x00\n#define ATA_SLAVE      0x01\n\n#define ATA_REG_DATA       0x00\n#define ATA_REG_ERROR      0x01\n#define ATA_REG_FEATURES   0x01\n#define ATA_REG_SECCOUNT0  0x02\n#define ATA_REG_LBA0       0x03\n#define ATA_REG_LBA1       0x04\n#define ATA_REG_LBA2       0x05\n#define ATA_REG_HDDEVSEL   0x06\n#define ATA_REG_COMMAND    0x07\n#define ATA_REG_STATUS     0x07\n#define ATA_REG_SECCOUNT1  0x08\n#define ATA_REG_LBA3       0x09\n#define ATA_REG_LBA4       0x0A\n#define ATA_REG_LBA5       0x0B\n#define ATA_REG_CONTROL    0x0C\n#define ATA_REG_ALTSTATUS  0x0C\n#define ATA_REG_DEVADDRESS 0x0D\n\n// Channels:\n#define ATA_PRIMARY      0x00\n#define ATA_SECONDARY    0x01\n\n// Directions:\n#define ATA_READ      0x00\n#define ATA_WRITE     0x01\n\ntypedef struct {\n\tuint16_t base;\n\tuint16_t ctrl;\n\tuint16_t bmide;\n\tuint16_t nien;\n} ide_channel_regs_t;\n\ntypedef struct {\n\tuint8_t  reserved;\n\tuint8_t  channel;\n\tuint8_t  drive;\n\tuint16_t type;\n\tuint16_t signature;\n\tuint16_t capabilities;\n\tuint32_t command_sets;\n\tuint32_t size;\n\tuint8_t  model[41];\n} ide_device_t;\n\ntypedef struct {\n\tuint16_t flags;\n\tuint16_t unused1[9];\n\tchar     serial[20];\n\tuint16_t unused2[3];\n\tchar     firmware[8];\n\tchar     model[40];\n\tuint16_t sectors_per_int;\n\tuint16_t unused3;\n\tuint16_t capabilities[2];\n\tuint16_t unused4[2];\n\tuint16_t valid_ext_data;\n\tuint16_t unused5[5];\n\tuint16_t size_of_rw_mult;\n\tuint32_t sectors_28;\n\tuint16_t unused6[38];\n\tuint64_t sectors_48;\n\tuint16_t unused7[152];\n} __attribute__((packed)) ata_identify_t;\n\nstatic char ata_drive_char = 'a';\nstatic int  cdrom_number = 0;\nstatic uint32_t ata_pci = 0x00000000;\nstatic list_t * atapi_waiter;\nstatic int  found_something = 0;\n\ntypedef union {\n\tuint8_t command_bytes[12];\n\tuint16_t command_words[6];\n} atapi_command_t;\n\n/* 8086:7010 */\nstatic void find_ata_pci(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tif ((vendorid == 0x8086) && (deviceid == 0x7010 || deviceid == 0x7111)) {\n\t\t*((uint32_t *)extra) = device;\n\t}\n}\n\ntypedef struct {\n\tuintptr_t offset;\n\tuint16_t bytes;\n\tuint16_t last;\n} prdt_t;\n\nstruct ata_device {\n\tint io_base;\n\tint control;\n\tint slave;\n\tint is_atapi;\n\tata_identify_t identity;\n\tprdt_t * dma_prdt;\n\tuintptr_t dma_prdt_phys;\n\tuint8_t * dma_start;\n\tuintptr_t dma_start_phys;\n\tuint32_t bar4;\n\tuint32_t atapi_lba;\n\tuint32_t atapi_sector_size;\n};\n\nstatic struct ata_device ata_primary_master   = {.io_base = 0x1F0, .control = 0x3F6, .slave = 0};\nstatic struct ata_device ata_primary_slave    = {.io_base = 0x1F0, .control = 0x3F6, .slave = 1};\nstatic struct ata_device ata_secondary_master = {.io_base = 0x170, .control = 0x376, .slave = 0};\nstatic struct ata_device ata_secondary_slave  = {.io_base = 0x170, .control = 0x376, .slave = 1};\n\nstatic spin_lock_t atapi_cmd_lock = { 0 };\n\n/* TODO support other sector sizes */\n#define ATA_SECTOR_SIZE 512\n#define ATA_CACHE_SIZE  4096\n#define SECTORS_PER_CACHE_BLOCK 8\n\nstatic void ata_device_read_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf);\nstatic void ata_device_read_sector_atapi(struct ata_device * dev, uint64_t lba, uint8_t * buf);\nstatic void ata_device_write_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf);\nstatic void ata_device_write_sector_actual(struct ata_device * dev, uint64_t lba);\n\nstruct CacheEntry {\n\tstruct ata_device * dev;\n\tuint64_t lba;\n\tuint64_t last_use;\n\tuint64_t flags;\n};\n\n#define CACHE_COUNT 4096\n\nstatic uint64_t hit_count = 0;\nstatic uint64_t miss_count = 0;\nstatic uint64_t eviction_count = 0;\nstatic uint64_t write_count = 0;\nstatic sched_mutex_t * ata_mutex = NULL;\n\nstatic struct CacheEntry * cache_entries = NULL;\nstatic char * cache_blocks = NULL;\nstatic uint64_t counter = 1;\n\nstatic off_t ata_max_offset(struct ata_device * dev) {\n\tuint64_t sectors = dev->identity.sectors_48;\n\t\n\tif (!sectors) {\n\t\t/* Fall back to sectors_28 */\n\t\tsectors = dev->identity.sectors_28;\n\t}\n\n\treturn sectors * ATA_SECTOR_SIZE;\n}\n\nstatic off_t atapi_max_offset(struct ata_device * dev) {\n\tuint64_t max_sector = dev->atapi_lba;\n\n\tif (!max_sector) return 0;\n\n\treturn (max_sector + 1) * dev->atapi_sector_size;\n}\n\nstatic ssize_t read_ata(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct ata_device * dev = (struct ata_device *)node->device;\n\tunsigned int start_block = offset / ATA_CACHE_SIZE;\n\tunsigned int end_block = (offset + size - 1) / ATA_CACHE_SIZE;\n\tunsigned int x_offset = 0;\n\n\n\tif (offset > ata_max_offset(dev)) {\n\t\treturn 0;\n\t}\n\n\tif (offset + (ssize_t)size > ata_max_offset(dev)) {\n\t\tunsigned int i = ata_max_offset(dev) - offset;\n\t\tsize = i;\n\t}\n\n\tif (offset % ATA_CACHE_SIZE || size < ATA_CACHE_SIZE) {\n\t\tunsigned int prefix_size = (ATA_CACHE_SIZE - (offset % ATA_CACHE_SIZE));\n\t\tif (prefix_size > size) prefix_size = size;\n\t\tchar * tmp = malloc(ATA_CACHE_SIZE);\n\t\tata_device_read_sector(dev, start_block, (uint8_t *)tmp);\n\n\t\tmemcpy(buffer, (void *)((uintptr_t)tmp + ((uintptr_t)offset % ATA_CACHE_SIZE)), prefix_size);\n\n\t\tfree(tmp);\n\n\t\tx_offset += prefix_size;\n\t\tstart_block++;\n\t}\n\n\tif ((offset + size)  % ATA_CACHE_SIZE && start_block <= end_block) {\n\t\tunsigned int postfix_size = (offset + size) % ATA_CACHE_SIZE;\n\t\tchar * tmp = malloc(ATA_CACHE_SIZE);\n\t\tata_device_read_sector(dev, end_block, (uint8_t *)tmp);\n\n\t\tmemcpy((void *)((uintptr_t)buffer + size - postfix_size), tmp, postfix_size);\n\n\t\tfree(tmp);\n\n\t\tend_block--;\n\t}\n\n\twhile (start_block <= end_block) {\n\t\tata_device_read_sector(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset));\n\t\tx_offset += ATA_CACHE_SIZE;\n\t\tstart_block++;\n\t}\n\n\treturn size;\n}\n\nstatic ssize_t read_atapi(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\n\tstruct ata_device * dev = (struct ata_device *)node->device;\n\n\tunsigned int start_block = offset / dev->atapi_sector_size;\n\tunsigned int end_block = (offset + size - 1) / dev->atapi_sector_size;\n\n\tunsigned int x_offset = 0;\n\n\tif (offset > atapi_max_offset(dev)) {\n\t\treturn 0;\n\t}\n\n\tif (offset + (ssize_t)size > atapi_max_offset(dev)) {\n\t\tunsigned int i = atapi_max_offset(dev) - offset;\n\t\tsize = i;\n\t}\n\n\tif (offset % dev->atapi_sector_size || size < dev->atapi_sector_size) {\n\t\tunsigned int prefix_size = (dev->atapi_sector_size - (offset % dev->atapi_sector_size));\n\t\tif (prefix_size > size) prefix_size = size;\n\t\tchar * tmp = malloc(dev->atapi_sector_size);\n\t\tata_device_read_sector_atapi(dev, start_block, (uint8_t *)tmp);\n\n\t\tmemcpy(buffer, (void *)((uintptr_t)tmp + ((uintptr_t)offset % dev->atapi_sector_size)), prefix_size);\n\n\t\tfree(tmp);\n\n\t\tx_offset += prefix_size;\n\t\tstart_block++;\n\t}\n\n\tif ((offset + size)  % dev->atapi_sector_size && start_block <= end_block) {\n\t\tunsigned int postfix_size = (offset + size) % dev->atapi_sector_size;\n\t\tchar * tmp = malloc(dev->atapi_sector_size);\n\t\tata_device_read_sector_atapi(dev, end_block, (uint8_t *)tmp);\n\n\t\tmemcpy((void *)((uintptr_t)buffer + size - postfix_size), tmp, postfix_size);\n\n\t\tfree(tmp);\n\n\t\tend_block--;\n\t}\n\n\twhile (start_block <= end_block) {\n\t\tata_device_read_sector_atapi(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset));\n\t\tx_offset += dev->atapi_sector_size;\n\t\tstart_block++;\n\t}\n\n\treturn size;\n}\n\n\nstatic ssize_t write_ata(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct ata_device * dev = (struct ata_device *)node->device;\n\n\tunsigned int start_block = offset / ATA_CACHE_SIZE;\n\tunsigned int end_block = (offset + size - 1) / ATA_CACHE_SIZE;\n\n\tunsigned int x_offset = 0;\n\n\tif (offset > ata_max_offset(dev)) {\n\t\treturn 0;\n\t}\n\n\tif (offset + (ssize_t)size > ata_max_offset(dev)) {\n\t\tunsigned int i = ata_max_offset(dev) - offset;\n\t\tsize = i;\n\t}\n\n\tif (offset % ATA_CACHE_SIZE) {\n\t\tunsigned int prefix_size = (ATA_CACHE_SIZE - (offset % ATA_CACHE_SIZE));\n\n\t\tchar * tmp = malloc(ATA_CACHE_SIZE);\n\t\tata_device_read_sector(dev, start_block, (uint8_t *)tmp);\n\n\t\tmemcpy((void *)((uintptr_t)tmp + ((uintptr_t)offset % ATA_CACHE_SIZE)), buffer, prefix_size);\n\t\tata_device_write_sector(dev, start_block, (uint8_t *)tmp);\n\n\t\tfree(tmp);\n\t\tx_offset += prefix_size;\n\t\tstart_block++;\n\t}\n\n\tif ((offset + size)  % ATA_CACHE_SIZE && start_block <= end_block) {\n\t\tunsigned int postfix_size = (offset + size) % ATA_CACHE_SIZE;\n\n\t\tchar * tmp = malloc(ATA_CACHE_SIZE);\n\t\tata_device_read_sector(dev, end_block, (uint8_t *)tmp);\n\n\t\tmemcpy(tmp, (void *)((uintptr_t)buffer + size - postfix_size), postfix_size);\n\n\t\tata_device_write_sector(dev, end_block, (uint8_t *)tmp);\n\n\t\tfree(tmp);\n\t\tend_block--;\n\t}\n\n\twhile (start_block <= end_block) {\n\t\tata_device_write_sector(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset));\n\t\tx_offset += ATA_CACHE_SIZE;\n\t\tstart_block++;\n\t}\n\n\treturn size;\n}\n\nstatic void open_ata(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void close_ata(fs_node_t * node) {\n\treturn;\n}\n\nstatic int ioctl_ata(fs_node_t * node, unsigned long request, void * argp) {\n\tstruct ata_device * dev = (struct ata_device *)node->device;\n\n\tswitch (request) {\n\t\tcase IOCTLSYNC: {\n\t\t\tmutex_acquire(ata_mutex);\n\t\t\tfor (int i = 0; i < CACHE_COUNT; ++i) {\n\t\t\t\tif (cache_entries[i].dev == dev && cache_entries[i].flags & 1) {\n\t\t\t\t\teviction_count++;\n\t\t\t\t\tmemcpy(cache_entries[i].dev->dma_start, cache_blocks + i * ATA_CACHE_SIZE, ATA_CACHE_SIZE);\n\t\t\t\t\tata_device_write_sector_actual(cache_entries[i].dev, cache_entries[i].lba);\n\t\t\t\t\tcache_entries[i].flags = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t\tmutex_release(ata_mutex);\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase 0x2A01234UL: {\n\t\t\tuint64_t * args = argp;\n\t\t\tmemcpy(&args[0], &hit_count, sizeof(uint64_t));\n\t\t\tmemcpy(&args[1], &miss_count, sizeof(uint64_t));\n\t\t\tmemcpy(&args[2], &eviction_count, sizeof(uint64_t));\n\t\t\tmemcpy(&args[3], &write_count, sizeof(uint64_t));\n\t\t\treturn 0;\n\t\t}\n\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic fs_node_t * atapi_device_create(struct ata_device * device) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tsnprintf(fnode->name, 20, \"cdrom%d\", cdrom_number);\n\tfnode->device  = device;\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask    = 0664;\n\tfnode->length  = atapi_max_offset(device);\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_atapi;\n\tfnode->write   = NULL; /* no write support */\n\tfnode->open    = open_ata;\n\tfnode->close   = close_ata;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL; /* TODO, identify, etc? */\n\treturn fnode;\n}\n\nstatic fs_node_t * ata_device_create(struct ata_device * device) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tsnprintf(fnode->name, 10, \"atadev%d\", ata_drive_char - 'a');\n\tfnode->device  = device;\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask    = 0660;\n\tfnode->length  = ata_max_offset(device); /* TODO */\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_ata;\n\tfnode->write   = write_ata;\n\tfnode->open    = open_ata;\n\tfnode->close   = close_ata;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = ioctl_ata; /* TODO, identify, etc? */\n\treturn fnode;\n}\n\nstatic void ata_io_wait(struct ata_device * dev) {\n\tinportb(dev->io_base + ATA_REG_ALTSTATUS);\n\tinportb(dev->io_base + ATA_REG_ALTSTATUS);\n\tinportb(dev->io_base + ATA_REG_ALTSTATUS);\n\tinportb(dev->io_base + ATA_REG_ALTSTATUS);\n}\n\nstatic int ata_status_wait(struct ata_device * dev, int timeout) {\n\tint status;\n\tif (timeout > 0) {\n\t\tint i = 0;\n\t\twhile ((status = inportb(dev->io_base + ATA_REG_STATUS)) & ATA_SR_BSY && (i < timeout)) i++;\n\t} else {\n\t\twhile ((status = inportb(dev->io_base + ATA_REG_STATUS)) & ATA_SR_BSY);\n\t}\n\treturn status;\n}\n\nstatic int ata_wait(struct ata_device * dev, int advanced) {\n\tuint8_t status = 0;\n\n\tata_io_wait(dev);\n\n\tstatus = ata_status_wait(dev, -1);\n\n\tif (advanced) {\n\t\tstatus = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (status   & ATA_SR_ERR)  return 1;\n\t\tif (status   & ATA_SR_DF)   return 1;\n\t\tif (!(status & ATA_SR_DRQ)) return 1;\n\t}\n\n\treturn 0;\n}\n\nstatic void ata_soft_reset(struct ata_device * dev) {\n\toutportb(dev->control, 0x04);\n\tata_io_wait(dev);\n\toutportb(dev->control, 0x00);\n}\n\nstatic int ata_irq_handler(struct regs *r) {\n\tstruct ata_device * dev = r->int_no == 14 ? &ata_primary_master : &ata_secondary_master;\n\tinportb(dev->io_base + ATA_REG_STATUS);\n\n\tspin_lock(atapi_cmd_lock);\n\twakeup_queue(atapi_waiter);\n\tspin_unlock(atapi_cmd_lock);\n\tirq_ack(r->int_no);\n\n\treturn 1;\n}\n\nstatic void * kvmalloc_p(size_t size, uintptr_t * outphys) {\n\tuintptr_t index = mmu_allocate_n_frames(size / 0x1000) << 12;\n\t*outphys = index;\n\treturn mmu_map_from_physical(index);\n}\n\nstatic void ata_device_init(struct ata_device * dev) {\n\toutportb(dev->io_base + 1, 1);\n\toutportb(dev->control, 0);\n\n\toutportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4);\n\tata_io_wait(dev);\n\n\toutportb(dev->io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY);\n\tata_io_wait(dev);\n\n\tinportb(dev->io_base + ATA_REG_COMMAND);\n\n\tata_wait(dev, 0);\n\n\tuint16_t * buf = (uint16_t *)&dev->identity;\n\n\tfor (int i = 0; i < 256; ++i) {\n\t\tbuf[i] = inports(dev->io_base);\n\t}\n\n\tuint8_t * ptr = (uint8_t *)&dev->identity.model;\n\tfor (int i = 0; i < 39; i+=2) {\n\t\tuint8_t tmp = ptr[i+1];\n\t\tptr[i+1] = ptr[i];\n\t\tptr[i] = tmp;\n\t}\n\n\tdev->is_atapi = 0;\n\tdev->dma_prdt  = (void *)kvmalloc_p(4096, &dev->dma_prdt_phys);\n\tdev->dma_start = (void *)kvmalloc_p(4096, &dev->dma_start_phys);\n\tdev->dma_prdt[0].offset = dev->dma_start_phys;\n\tdev->dma_prdt[0].bytes = ATA_CACHE_SIZE;\n\tdev->dma_prdt[0].last = 0x8000;\n\n\tuint16_t command_reg = pci_read_field(ata_pci, PCI_COMMAND, 4);\n\tif (!(command_reg & (1 << 2))) {\n\t\tcommand_reg |= (1 << 2); /* bit 2 */\n\t\tpci_write_field(ata_pci, PCI_COMMAND, 4, command_reg);\n\t\tcommand_reg = pci_read_field(ata_pci, PCI_COMMAND, 4);\n\t}\n\n\tdev->bar4 = pci_read_field(ata_pci, PCI_BAR4, 4);\n\n\tif (dev->bar4 & 0x00000001) {\n\t\tdev->bar4 = dev->bar4 & 0xFFFFFFFC;\n\t} else {\n\t\treturn; /* No DMA because we're not sure what to do here */\n\t}\n}\n\nstatic int atapi_device_init(struct ata_device * dev) {\n\n\tdev->is_atapi = 1;\n\n\toutportb(dev->io_base + 1, 1);\n\toutportb(dev->control, 0);\n\n\toutportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4);\n\tata_io_wait(dev);\n\n\toutportb(dev->io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET);\n\tata_io_wait(dev);\n\n\tinportb(dev->io_base + ATA_REG_COMMAND);\n\n\tata_wait(dev, 0);\n\n\tuint16_t * buf = (uint16_t *)&dev->identity;\n\n\tfor (int i = 0; i < 256; ++i) {\n\t\tbuf[i] = inports(dev->io_base);\n\t}\n\n\tuint8_t * ptr = (uint8_t *)&dev->identity.model;\n\tfor (int i = 0; i < 39; i+=2) {\n\t\tuint8_t tmp = ptr[i+1];\n\t\tptr[i+1] = ptr[i];\n\t\tptr[i] = tmp;\n\t}\n\n\t/* Detect medium */\n\tatapi_command_t command;\n\tcommand.command_bytes[0] = 0x25;\n\tcommand.command_bytes[1] = 0;\n\tcommand.command_bytes[2] = 0;\n\tcommand.command_bytes[3] = 0;\n\tcommand.command_bytes[4] = 0;\n\tcommand.command_bytes[5] = 0;\n\tcommand.command_bytes[6] = 0;\n\tcommand.command_bytes[7] = 0;\n\tcommand.command_bytes[8] = 0; /* bit 0 = PMI (0, last sector) */\n\tcommand.command_bytes[9] = 0; /* control */\n\tcommand.command_bytes[10] = 0;\n\tcommand.command_bytes[11] = 0;\n\n\tuint16_t bus = dev->io_base;\n\n\toutportb(bus + ATA_REG_FEATURES, 0x00);\n\toutportb(bus + ATA_REG_LBA1, 0x08);\n\toutportb(bus + ATA_REG_LBA2, 0x08);\n\toutportb(bus + ATA_REG_COMMAND, ATA_CMD_PACKET);\n\n\t/* poll */\n\tint timeout = 100;\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif ((status & ATA_SR_ERR)) goto atapi_error;\n\t\tif (timeout-- < 0) goto atapi_timeout;\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;\n\t}\n\n\tfor (int i = 0; i < 6; ++i) {\n\t\toutports(bus, command.command_words[i]);\n\t}\n\n\t/* poll */\n\ttimeout = 100;\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif ((status & ATA_SR_ERR)) goto atapi_error_read;\n\t\tif (timeout-- < 0) goto atapi_timeout;\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;\n\t\tif ((status & ATA_SR_DRQ)) break;\n\t}\n\n\tuint16_t data[4];\n\n\tfor (int i = 0; i < 4; ++i) {\n\t\tdata[i] = inports(bus);\n\t}\n\n#define htonl(l)  ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24))\n\tuint32_t lba, blocks;;\n\tmemcpy(&lba, &data[0], sizeof(uint32_t));\n\tlba = htonl(lba);\n\tmemcpy(&blocks, &data[2], sizeof(uint32_t));\n\tblocks = htonl(blocks);\n\n\tdev->atapi_lba = lba;\n\tdev->atapi_sector_size = blocks;\n\n\tif (!lba) return 1;\n\n\treturn 0;\n\natapi_error_read:\n\treturn 1;\n\natapi_error:\n\treturn 1;\n\natapi_timeout:\n\treturn 1;\n}\n\nstatic int ata_device_detect(struct ata_device * dev) {\n\tata_soft_reset(dev);\n\tata_io_wait(dev);\n\toutportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4);\n\tata_io_wait(dev);\n\tata_status_wait(dev, 10000);\n\n\tunsigned char cl = inportb(dev->io_base + ATA_REG_LBA1); /* CYL_LO */\n\tunsigned char ch = inportb(dev->io_base + ATA_REG_LBA2); /* CYL_HI */\n\n\tif (cl == 0xFF && ch == 0xFF) {\n\t\t/* Nothing here */\n\t\treturn 0;\n\t}\n\tif ((cl == 0x00 && ch == 0x00) ||\n\t    (cl == 0x3C && ch == 0xC3)) {\n\t\t/* Parallel ATA device, or emulated SATA */\n\t\tata_device_init(dev);\n\n\t\toff_t sectors = ata_max_offset(dev);\n\t\tif (sectors == 0) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tchar devname[64];\n\t\tsnprintf((char *)&devname, 20, \"/dev/hd%c\", ata_drive_char);\n\t\tfs_node_t * node = ata_device_create(dev);\n\t\tchar options[21];\n\t\tsnprintf(options, 20, \"%c\", ata_drive_char);\n\t\tvfs_mount(devname, node, \"ata-hd\", options);\n\t\tnode->length  = sectors;\n\n\t\tata_drive_char++;\n\t\tfound_something = 1;\n\t\treturn 1;\n\t} else if ((cl == 0x14 && ch == 0xEB) ||\n\t           (cl == 0x69 && ch == 0x96)) {\n\n\t\tchar devname[64];\n\t\tsnprintf((char *)&devname, 20, \"/dev/cdrom%d\", cdrom_number);\n\n\t\tif (atapi_device_init(dev)) {\n\t\t\treturn 0;\n\t\t}\n\t\tfs_node_t * node = atapi_device_create(dev);\n\t\tchar options[21];\n\t\tsnprintf(options, 20, \"%d\", cdrom_number);\n\t\tvfs_mount(devname, node, \"atapi-cdrom\", options);\n\n\t\tcdrom_number++;\n\t\tfound_something = 1;\n\t\treturn 2;\n\t}\n\n\t/* TODO: ATAPI, SATA, SATAPI */\n\treturn 0;\n}\n\nstatic void ata_device_read_sector_actual(struct ata_device * dev, uint64_t lba) {\n\tuint16_t bus = dev->io_base;\n\tuint8_t slave = dev->slave;\n\n\tif (dev->is_atapi) return;\n\n\tata_wait(dev, 0);\n\n\t/* Stop */\n\toutportb(dev->bar4, 0x00);\n\n\t/* Set the PRDT */\n\toutportl(dev->bar4 + 0x04, dev->dma_prdt_phys);\n\n\t/* Enable error, irq status */\n\toutportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);\n\n\t/* set read */\n\toutportb(dev->bar4, 0x08);\n\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (!(status & ATA_SR_BSY)) break;\n\t}\n\n\toutportb(bus + ATA_REG_CONTROL, 0x00);\n\toutportb(bus + ATA_REG_HDDEVSEL, 0xe0 | slave << 4);\n\tata_io_wait(dev);\n\toutportb(bus + ATA_REG_FEATURES, 0x00);\n\n\toutportb(bus + ATA_REG_SECCOUNT0, 0);\n\toutportb(bus + ATA_REG_LBA0, (lba & 0xff000000) >> 24);\n\toutportb(bus + ATA_REG_LBA1, (lba & 0xff00000000) >> 32);\n\toutportb(bus + ATA_REG_LBA2, (lba & 0xff0000000000) >> 40);\n\n\toutportb(bus + ATA_REG_SECCOUNT0, SECTORS_PER_CACHE_BLOCK);\n\toutportb(bus + ATA_REG_LBA0, (lba & 0x000000ff) >>  0);\n\toutportb(bus + ATA_REG_LBA1, (lba & 0x0000ff00) >>  8);\n\toutportb(bus + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16);\n\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;\n\t}\n\tspin_lock(atapi_cmd_lock);\n\toutportb(bus + ATA_REG_COMMAND, ATA_CMD_READ_DMA_EXT);\n\n\tata_io_wait(dev);\n\n\toutportb(dev->bar4, 0x08 | 0x01);\n\tsleep_on_unlocking(atapi_waiter, &atapi_cmd_lock);\n\n\twhile (1) {\n\t\tint status = inportb(dev->bar4 + 0x02);\n\t\tint dstatus = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (!(status & 0x04)) {\n\t\t\tcontinue;\n\t\t}\n\t\tif (!(dstatus & ATA_SR_BSY)) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Inform device we are done. */\n\toutportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);\n}\n\nstatic void ata_device_read_sector_atapi_actual(struct ata_device * dev, uint64_t lba, uint8_t * buf) {\n\n\tif (!dev->is_atapi) return;\n\n\tuint16_t bus = dev->io_base;\n\n\toutportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4);\n\tata_io_wait(dev);\n\n\toutportb(bus + ATA_REG_FEATURES, 0x00);\n\toutportb(bus + ATA_REG_LBA1, dev->atapi_sector_size & 0xFF);\n\toutportb(bus + ATA_REG_LBA2, dev->atapi_sector_size >> 8);\n\toutportb(bus + ATA_REG_COMMAND, ATA_CMD_PACKET);\n\n\t/* poll */\n\tint timeout = 100;\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup;\n\t\tif (timeout-- < 0) { dprintf(\"atapi: timeout while waiting for controller to be ready\\n\"); goto atapi_timeout; }\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break;\n\t}\n\n\tatapi_command_t command;\n\tcommand.command_bytes[0] = 0xA8;\n\tcommand.command_bytes[1] = 0;\n\tcommand.command_bytes[2] = (lba >> 0x18) & 0xFF;\n\tcommand.command_bytes[3] = (lba >> 0x10) & 0xFF;\n\tcommand.command_bytes[4] = (lba >> 0x08) & 0xFF;\n\tcommand.command_bytes[5] = (lba >> 0x00) & 0xFF;\n\tcommand.command_bytes[6] = 0;\n\tcommand.command_bytes[7] = 0;\n\tcommand.command_bytes[8] = 0; /* bit 0 = PMI (0, last sector) */\n\tcommand.command_bytes[9] = 1; /* control */\n\tcommand.command_bytes[10] = 0;\n\tcommand.command_bytes[11] = 0;\n\n\tspin_lock(atapi_cmd_lock);\n\tfor (int i = 0; i < 6; ++i) {\n\t\toutports(bus, command.command_words[i]);\n\t}\n\n\tsleep_on_unlocking(atapi_waiter, &atapi_cmd_lock);\n\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup;\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break;\n\t}\n\n\tuint16_t size_to_read = inportb(bus + ATA_REG_LBA2) << 8;\n\tsize_to_read = size_to_read | inportb(bus + ATA_REG_LBA1);\n\n\tinportsm(bus,buf,size_to_read/2);\n\n\ttimeout = 1000;\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup;\n\t\tif (timeout-- < 0) { dprintf(\"atapi: timeout while waiting for controller to be ready after transaction\\n\"); goto atapi_timeout; }\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;\n\t}\n\natapi_error_on_read_setup:\n\treturn;\n\natapi_timeout:\n\treturn;\n}\n\nstatic void ata_device_write_sector_actual(struct ata_device * dev, uint64_t lba) {\n\tuint16_t bus = dev->io_base;\n\tuint8_t slave = dev->slave;\n\n\tata_wait(dev, 0);\n\toutportb(dev->bar4, 0x00);\n\toutportl(dev->bar4 + 0x04, dev->dma_prdt_phys);\n\toutportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);\n\n\t/* set write */\n\toutportb(dev->bar4, 0x00);\n\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (!(status & ATA_SR_BSY)) break;\n\t}\n\n\toutportb(bus + ATA_REG_CONTROL, 0x02);\n\toutportb(bus + ATA_REG_HDDEVSEL, 0xe0 | slave << 4);\n\tata_wait(dev, 0);\n\toutportb(bus + ATA_REG_FEATURES, 0x00);\n\n\toutportb(bus + ATA_REG_SECCOUNT0, 0);\n\toutportb(bus + ATA_REG_LBA0, (lba & 0xff000000) >> 24);\n\toutportb(bus + ATA_REG_LBA1, (lba & 0xff00000000) >> 32);\n\toutportb(bus + ATA_REG_LBA2, (lba & 0xff0000000000) >> 40);\n\n\toutportb(bus + ATA_REG_SECCOUNT0, SECTORS_PER_CACHE_BLOCK);\n\toutportb(bus + ATA_REG_LBA0, (lba & 0x000000ff) >>  0);\n\toutportb(bus + ATA_REG_LBA1, (lba & 0x0000ff00) >>  8);\n\toutportb(bus + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16);\n\n\twhile (1) {\n\t\tuint8_t status = inportb(dev->io_base + ATA_REG_STATUS);\n\t\tif (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break;\n\t}\n\tspin_lock(atapi_cmd_lock);\n\toutportb(bus + ATA_REG_COMMAND, ATA_CMD_WRITE_DMA_EXT);\n\n\tata_io_wait(dev);\n\n\toutportb(dev->bar4, 0x00 | 0x01);\n\tsleep_on_unlocking(atapi_waiter, &atapi_cmd_lock);\n\n\t#if 0\n\tata_wait(dev, 0);\n\tint size = ATA_SECTOR_SIZE / 2;\n\toutportsm(bus,buf,size);\n\t#endif\n\n\toutportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02);\n\n#if 0\n\toutportb(bus + 0x07, ATA_CMD_CACHE_FLUSH);\n\tata_wait(dev, 0);\n#endif\n}\n\nstatic void ata_device_read_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf) {\n\tlba *= SECTORS_PER_CACHE_BLOCK;\n\t/* Is it in the cache? */\n\tmutex_acquire(ata_mutex);\n\tint found = 0;\n\tint oldest = 0;\n\tuint64_t lu = -1;\n\tfor (int i = 0; i < CACHE_COUNT; ++i) {\n\t\tif (cache_entries[i].dev == dev && cache_entries[i].lba == lba) {\n\t\t\t//dprintf(\"ata: cache hit\\n\");\n\t\t\thit_count++;\n\t\t\toldest = i;\n\t\t\tfound = 1;\n\t\t\tbreak;\n\t\t} else if (cache_entries[i].dev == NULL) {\n\t\t\toldest = i;\n\t\t\tbreak;\n\t\t} else if (cache_entries[i].last_use < lu) {\n\t\t\toldest = i;\n\t\t\tlu = cache_entries[oldest].last_use;\n\t\t}\n\t}\n\tif (!found) {\n\t\tmiss_count++;\n\t\tif (cache_entries[oldest].dev && cache_entries[oldest].flags & 1) {\n\t\t\teviction_count++;\n\t\t\tmemcpy(cache_entries[oldest].dev->dma_start, cache_blocks + oldest * ATA_CACHE_SIZE, ATA_CACHE_SIZE);\n\t\t\tata_device_write_sector_actual(cache_entries[oldest].dev, cache_entries[oldest].lba);\n\t\t}\n\t\tata_device_read_sector_actual(dev, lba);\n\t\tcache_entries[oldest].dev = dev;\n\t\tcache_entries[oldest].lba = lba;\n\t\tcache_entries[oldest].flags = 0;\n\t\tmemcpy(cache_blocks + oldest * ATA_CACHE_SIZE, dev->dma_start, ATA_CACHE_SIZE);\n\t}\n\tcache_entries[oldest].last_use = counter++;\n\tmemcpy(buf, cache_blocks + ATA_CACHE_SIZE * oldest, ATA_CACHE_SIZE);\n\tmutex_release(ata_mutex);\n}\n\nstatic void ata_device_write_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf) {\n\tlba *= SECTORS_PER_CACHE_BLOCK;\n\tmutex_acquire(ata_mutex);\n\tint found = 0;\n\tint oldest = 0;\n\tuint64_t lu = -1;\n\tfor (int i = 0; i < CACHE_COUNT; ++i) {\n\t\tif (cache_entries[i].dev == dev && cache_entries[i].lba == lba) {\n\t\t\t//dprintf(\"ata: cache hit\\n\");\n\t\t\thit_count++;\n\t\t\toldest = i;\n\t\t\tfound = 1;\n\t\t\tbreak;\n\t\t} else if (cache_entries[i].dev == NULL) {\n\t\t\toldest = i;\n\t\t\tbreak;\n\t\t} else if (cache_entries[i].last_use < lu) {\n\t\t\toldest = i;\n\t\t\tlu = cache_entries[oldest].last_use;\n\t\t}\n\t}\n\tif (!found) {\n\t\tmiss_count++;\n\t\tif (cache_entries[oldest].dev && cache_entries[oldest].flags & 1) {\n\t\t\teviction_count++;\n\t\t\tmemcpy(cache_entries[oldest].dev->dma_start, cache_blocks + oldest * ATA_CACHE_SIZE, ATA_CACHE_SIZE);\n\t\t\tata_device_write_sector_actual(cache_entries[oldest].dev, cache_entries[oldest].lba);\n\t\t}\n\t\tcache_entries[oldest].dev = dev;\n\t\tcache_entries[oldest].lba = lba;\n\t}\n\twrite_count++;\n\tcache_entries[oldest].last_use = counter++;\n\tmemcpy(cache_blocks + oldest * ATA_CACHE_SIZE, buf, ATA_CACHE_SIZE);\n\tcache_entries[oldest].flags = 1;\n\tmutex_release(ata_mutex);\n}\nstatic void ata_device_read_sector_atapi(struct ata_device * dev, uint64_t lba, uint8_t * buf) {\n\tmutex_acquire(ata_mutex);\n\tata_device_read_sector_atapi_actual(dev, lba, buf);\n\tmutex_release(ata_mutex);\n}\n\nstatic int ata_initialize(int argc, char * argv[]) {\n\t/* Detect drives and mount them */\n\n\t/* Locate ATA device via PCI */\n\tpci_scan(&find_ata_pci, -1, &ata_pci);\n\n\tirq_install_handler(14, ata_irq_handler, \"ide master\");\n\tirq_install_handler(15, ata_irq_handler, \"ide slave\");\n\n\tatapi_waiter = list_create(\"atapi waiter\", NULL);\n\n\tcache_entries = malloc(sizeof(struct CacheEntry) * CACHE_COUNT);\n\tmemset(cache_entries, 0, sizeof(struct CacheEntry) * CACHE_COUNT);\n\tcache_blocks  = mmu_map_module(CACHE_COUNT * ATA_CACHE_SIZE);\n\tata_mutex = mutex_init(\"ata lock\");\n\n\tata_device_detect(&ata_primary_master);\n\tata_device_detect(&ata_primary_slave);\n\tata_device_detect(&ata_secondary_master);\n\tata_device_detect(&ata_secondary_slave);\n\n\treturn 0;\n}\n\nstatic int ata_finalize(void) {\n\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"ata\",\n\t.init = ata_initialize,\n\t.fini = ata_finalize,\n};\n\n"
  },
  {
    "path": "modules/dospart.c",
    "content": "/**\n * @file modules/dospart.c\n * @brief DOS MBR partition table mapper\n * @package x86_64\n * @package aarch64\n *\n * Provides partition entries for disks.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/module.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/tokenize.h>\n\n#define SECTORSIZE      512\n\ntypedef struct {\n\tuint8_t  status;\n\tuint8_t  chs_first_sector[3];\n\tuint8_t  type;\n\tuint8_t  chs_last_sector[3];\n\tuint32_t lba_first_sector;\n\tuint32_t sector_count;\n} partition_t;\n\ntypedef struct {\n\tuint8_t     boostrap[446];\n\tpartition_t partitions[4];\n\tuint8_t     signature[2];\n} __attribute__((packed)) mbr_t;\n\nstruct dos_partition_entry {\n\tfs_node_t * device;\n\tpartition_t partition;\n};\n\nstatic ssize_t read_part(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct dos_partition_entry * device = (struct dos_partition_entry *)node->device;\n\n\tif ((size_t)offset > device->partition.sector_count * SECTORSIZE) {\n\t\treturn 0;\n\t}\n\n\tif (offset + size > device->partition.sector_count * SECTORSIZE) {\n\t\tsize = device->partition.sector_count * SECTORSIZE - offset;\n\t}\n\n\treturn read_fs(device->device, offset + device->partition.lba_first_sector * SECTORSIZE, size, buffer);\n}\n\nstatic ssize_t write_part(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct dos_partition_entry * device = (struct dos_partition_entry *)node->device;\n\n\tif ((size_t)offset > device->partition.sector_count * SECTORSIZE) {\n\t\treturn 0;\n\t}\n\n\tif (offset + size > device->partition.sector_count * SECTORSIZE) {\n\t\tsize = device->partition.sector_count * SECTORSIZE - offset;\n\t}\n\n\treturn write_fs(device->device, offset + device->partition.lba_first_sector * SECTORSIZE, size, buffer);\n}\n\nstatic void open_part(fs_node_t * node, unsigned int flags) {\n\treturn;\n}\n\nstatic void close_part(fs_node_t * node) {\n\treturn;\n}\n\nstatic fs_node_t * dospart_device_create(int i, fs_node_t * dev, mbr_t * mbr, int id) {\n\n\tvfs_lock(dev);\n\n\tstruct dos_partition_entry * device = malloc(sizeof(struct dos_partition_entry));\n\tmemcpy(&device->partition, &mbr->partitions[id], sizeof(partition_t));\n\tdevice->device = dev;\n\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tfnode->inode = 0;\n\tsnprintf(fnode->name, 20, \"dospart%d\", i);\n\tfnode->device  = device;\n\tfnode->uid = 0;\n\tfnode->gid = 0;\n\tfnode->mask    = 0660;\n\tfnode->length  = device->partition.sector_count * SECTORSIZE; /* TODO */\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->read    = read_part;\n\tfnode->write   = write_part;\n\tfnode->open    = open_part;\n\tfnode->close   = close_part;\n\tfnode->readdir = NULL;\n\tfnode->finddir = NULL;\n\tfnode->ioctl   = NULL; /* TODO, identify, etc? */\n\treturn fnode;\n}\n\nstatic fs_node_t * dospart_map(const char * device, const char * mount_path) {\n\tchar * arg = strdup(device);\n\tchar * argv[10];\n\ttokenize(arg, \",\", argv);\n\n\tfs_node_t * dev = kopen(argv[0], 0);\n\tif (!dev) {\n\t\treturn NULL;\n\t}\n\n\tmbr_t mbr;\n\tread_fs(dev, 0, SECTORSIZE, (uint8_t *)&mbr);\n\n\tif (mbr.signature[0] == 0x55 && mbr.signature[1] == 0xAA) {\n\t\tfor (int i = 0; i < 4; ++i) {\n\t\t\tif (mbr.partitions[i].status & 0x80) {\n\t\t\t\tfs_node_t * node = dospart_device_create(i, dev, &mbr, i);\n\t\t\t\tchar tmp[64];\n\t\t\t\tsnprintf(tmp, 20, \"%s%d\", device, i);\n\t\t\t\tvfs_mount(tmp, node, \"dospart\", tmp);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* VFS_MOUNT_PARTITION_MAPPER_SUCCESS? */\n\treturn (fs_node_t*)1;\n}\n\nstatic int dospart_initialize(int argc, char * argv[]) {\n\tvfs_register(\"mbr\", dospart_map);\n\treturn 0;\n}\n\nstatic int dospart_finalize(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"dospart\",\n\t.init = dospart_initialize,\n\t.fini = dospart_finalize,\n};\n\n"
  },
  {
    "path": "modules/e1000.c",
    "content": "/**\n * @file kernel/net/e1000.c\n * @brief Intel Gigabit Ethernet device driver\n * @package x86_64\n * @package aarch64\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2017-2021 K. Lange\n *\n * @ref https://www.intel.com/content/dam/www/public/us/en/documents/manuals/pcie-gbe-controllers-open-source-manual.pdf\n */\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/pci.h>\n#include <kernel/mmu.h>\n#include <kernel/pipe.h>\n#include <kernel/list.h>\n#include <kernel/spinlock.h>\n#include <kernel/time.h>\n#include <kernel/vfs.h>\n#include <kernel/net/netif.h>\n#include <kernel/net/eth.h>\n#include <kernel/module.h>\n#include <errno.h>\n\n#if defined(__x86_64__)\n#include <kernel/arch/x86_64/irq.h>\n#elif defined(__aarch64__)\n#include <kernel/arch/aarch64/gic.h>\n#endif\n#include <kernel/net/e1000.h>\n\n#include <sys/socket.h>\n#include <net/if.h>\n\n#define INTS (ICR_LSC | ICR_RXO | ICR_RXT0 | ICR_TXQE | ICR_TXDW | ICR_ACK | ICR_RXDMT0 | ICR_SRPD)\n\nstruct e1000_nic {\n\tstruct EthernetDevice eth;\n\tuint32_t pci_device;\n\tuint16_t deviceid;\n\tuintptr_t mmio_addr;\n\tint irq_number;\n\n\tint has_eeprom;\n\tint rx_index;\n\tint tx_index;\n\tint link_status;\n\n\tspin_lock_t tx_lock;\n\n\tuint8_t * rx_virt[E1000_NUM_RX_DESC];\n\tuint8_t * tx_virt[E1000_NUM_TX_DESC];\n\tvolatile struct e1000_rx_desc * rx;\n\tvolatile struct e1000_tx_desc * tx;\n\tuintptr_t rx_phys;\n\tuintptr_t tx_phys;\n\n\tint configured;\n\tprocess_t * queuer;\n\tprocess_t * processor;\n\n\tnetif_counters_t counts;\n};\n\nstatic int device_count = 0;\nstatic struct e1000_nic * devices[32] = {NULL};\n\n#ifdef __aarch64__\nstatic uint32_t mmio_read32(uintptr_t addr) {\n\tasm volatile (\"dc ivac, %0\\ndsb sy\\nisb\\n\" :: \"r\"(addr) : \"memory\");\n\tuint32_t res = *((volatile uint32_t*)(addr));\n\tasm volatile (\"dmb ish\" ::: \"memory\");\n\treturn res;\n}\nstatic void mmio_write32(uintptr_t addr, uint32_t val) {\n\t(*((volatile uint32_t*)(addr))) = val;\n\tasm volatile (\"dsb ishst\\nisb\\ndc cvac, %0\\n\" :: \"r\"(addr) : \"memory\");\n}\nstatic void cache_invalidate(void *addr) {\n\tuintptr_t a = (uintptr_t)addr;\n\tfor (uintptr_t x = 0; x < 4096; x += 64) {\n\t\tasm volatile (\"dc ivac, %0\\n\" :: \"r\"(a + x) : \"memory\");\n\t}\n\tasm volatile (\"dsb sy\\nisb\":::\"memory\");\n}\n\nstatic void cache_clean(void *addr) {\n\tuintptr_t a = (uintptr_t)addr;\n\tasm volatile (\"dmb ish\" ::: \"memory\");\n\tfor (uintptr_t x = 0; x < 4096; x += 64) {\n\t\tasm volatile (\"dc cvac, %0\" :: \"r\"(a + x) : \"memory\");\n\t}\n\tasm volatile (\"dsb sy\\nisb\":::\"memory\");\n}\n\n#else\nstatic uint32_t mmio_read32(uintptr_t addr) {\n\treturn *((volatile uint32_t*)(addr));\n}\nstatic void mmio_write32(uintptr_t addr, uint32_t val) {\n\t(*((volatile uint32_t*)(addr))) = val;\n}\n#endif\n\nstatic void write_command(struct e1000_nic * device, uint16_t addr, uint32_t val) {\n\tmmio_write32(device->mmio_addr + addr, val);\n}\n\nstatic uint32_t read_command(struct e1000_nic * device, uint16_t addr) {\n\treturn mmio_read32(device->mmio_addr + addr);\n}\n\nstatic void delay_yield(size_t subticks) {\n#ifdef __aarch64__\n\tasm volatile (\"isb\" ::: \"memory\");\n#endif\n\tunsigned long s, ss;\n\trelative_time(0, subticks, &s, &ss);\n\tsleep_until((process_t *)this_core->current_process, s, ss);\n\tswitch_task(0);\n}\n\nstatic int eeprom_detect(struct e1000_nic * device) {\n\n\t/* Definitely not */\n\tif (device->deviceid == 0x10d3) return 0;\n\n\twrite_command(device, E1000_REG_EEPROM, 1);\n\n\tfor (int i = 0; i < 10000 && !device->has_eeprom; ++i) {\n\t\tuint32_t val = read_command(device, E1000_REG_EEPROM);\n\t\tif (val & 0x10) device->has_eeprom = 1;\n\t}\n\n\treturn 0;\n}\n\nstatic uint16_t eeprom_read(struct e1000_nic * device, uint8_t addr) {\n\tuint32_t temp = 0;\n\twrite_command(device, E1000_REG_EEPROM, 1 | ((uint32_t)(addr) << 8));\n\twhile (!((temp = read_command(device, E1000_REG_EEPROM)) & (1 << 4)));\n\treturn (uint16_t)((temp >> 16) & 0xFFFF);\n}\n\nstatic void write_mac(struct e1000_nic * device) {\n\tuint32_t low, high;\n\tmemcpy(&low, &device->eth.mac[0], 4);\n\tmemcpy(&high,&device->eth.mac[4], 2);\n\tmemset((uint8_t *)&high + 2, 0, 2);\n\thigh |= 0x80000000;\n\twrite_command(device, E1000_REG_RXADDR + 0, low);\n\twrite_command(device, E1000_REG_RXADDR + 4, high);\n}\n\nstatic void read_mac(struct e1000_nic * device) {\n\tif (device->has_eeprom) {\n\t\tuint32_t t;\n\t\tt = eeprom_read(device, 0);\n\t\tdevice->eth.mac[0] = t & 0xFF;\n\t\tdevice->eth.mac[1] = t >> 8;\n\t\tt = eeprom_read(device, 1);\n\t\tdevice->eth.mac[2] = t & 0xFF;\n\t\tdevice->eth.mac[3] = t >> 8;\n\t\tt = eeprom_read(device, 2);\n\t\tdevice->eth.mac[4] = t & 0xFF;\n\t\tdevice->eth.mac[5] = t >> 8;\n\t} else {\n\t\tuint32_t mac_addr_low  = *(uint32_t *)(device->mmio_addr + E1000_REG_RXADDR);\n\t\tuint32_t mac_addr_high = *(uint32_t *)(device->mmio_addr + E1000_REG_RXADDR + 4);\n\t\tdevice->eth.mac[0] = (mac_addr_low >> 0 ) & 0xFF;\n\t\tdevice->eth.mac[1] = (mac_addr_low >> 8 ) & 0xFF;\n\t\tdevice->eth.mac[2] = (mac_addr_low >> 16) & 0xFF;\n\t\tdevice->eth.mac[3] = (mac_addr_low >> 24) & 0xFF;\n\t\tdevice->eth.mac[4] = (mac_addr_high>> 0 ) & 0xFF;\n\t\tdevice->eth.mac[5] = (mac_addr_high>> 8 ) & 0xFF;\n\t}\n}\n\nstatic void e1000_handle(struct e1000_nic * nic, uint32_t status) {\n\twrite_command(nic, E1000_REG_ICR, status);\n\n\tif (!nic->configured) {\n\t\treturn;\n\t}\n\n\tif (status & ICR_LSC) {\n\t\tnic->link_status= (read_command(nic, E1000_REG_STATUS) & (1 << 1));\n\t}\n\n\tmake_process_ready(nic->queuer);\n}\n\nstatic void e1000_queuer(void * data) {\n\tstruct e1000_nic * nic = data;\n\n\tint head = read_command(nic, E1000_REG_RXDESCHEAD);\n\tint budget = 8;\n\n\twhile (1) {\n\t\tint processed = 0;\n\t\tif (head == nic->rx_index) {\n\t\t\thead = read_command(nic, E1000_REG_RXDESCHEAD);\n\t\t}\n\t\tif (head != nic->rx_index) {\n#ifdef __aarch64__\n\t\t\t__sync_synchronize();\n#endif\n\t\t\twhile ((nic->rx[nic->rx_index].status & 0x01) && (processed < budget)) {\n\t\t\t\tint i = nic->rx_index;\n\t\t\t\tif (!(nic->rx[i].errors & (0x97))) {\n\t\t\t\t\tnic->counts.rx_count++;\n\t\t\t\t\tnic->counts.rx_bytes += nic->rx[i].length;\n#ifdef __aarch64__\n\t\t\t\t\tcache_invalidate(nic->rx_virt[i]);\n#endif\n\t\t\t\t\tnet_eth_handle((void*)nic->rx_virt[i], nic->eth.device_node, nic->rx[i].length);\n\t\t\t\t} else {\n\t\t\t\t\tprintf(\"error bits set in packet: %x\\n\", nic->rx[i].errors);\n\t\t\t\t}\n\t\t\t\tprocessed++;\n#ifdef __aarch64__\n\t\t\t\t__sync_synchronize();\n#endif\n\t\t\t\tnic->rx[i].status = 0;\n\t\t\t\tif (++nic->rx_index == E1000_NUM_RX_DESC) {\n\t\t\t\t\tnic->rx_index = 0;\n\t\t\t\t}\n\t\t\t\tif (nic->rx_index == head) {\n\t\t\t\t\thead = read_command(nic, E1000_REG_RXDESCHEAD);\n\t\t\t\t\tif (nic->rx_index == head) break;\n\t\t\t\t}\n\t\t\t\twrite_command(nic, E1000_REG_RXDESCTAIL, nic->rx_index);\n\t\t\t\tread_command(nic, E1000_REG_STATUS);\n#ifdef __aarch64__\n\t\t\t\t__sync_synchronize();\n#endif\n\t\t\t}\n\t\t}\n\t\tif (processed == 0) {\n\t\t\tdelay_yield(100000);\n\t\t} else {\n\t\t\tif (this_core->cpu_id == 0) switch_task(1);\n\t\t}\n\t}\n}\n\n#if defined(__x86_64__)\nstatic int irq_handler(struct regs *r) {\n\tint irq = r->int_no - 32;\n#elif defined(__aarch64__)\nstatic int e1000_irq_handler(process_t * this, int irq, void * data) {\n#endif\n\tint handled = 0;\n\n\tfor (int i = 0; i < device_count; ++i) {\n\t\tif (devices[i]->irq_number == irq) {\n\t\t\tuint32_t status = read_command(devices[i], E1000_REG_ICR);\n\t\t\tif (status) {\n\t\t\t\te1000_handle(devices[i], status);\n\t\t\t\tif (!handled) {\n\t\t\t\t\thandled = 1;\n#if defined(__x86_64__)\n\t\t\t\t\tirq_ack(irq);\n#endif\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn handled;\n}\n\nstatic int tx_full(struct e1000_nic * device, int tx_tail, int tx_head) {\n\tif (tx_tail == tx_head) return 0;\n\tif (device->tx_index == tx_head) return 1;\n\tif (((device->tx_index + 1) & E1000_NUM_TX_DESC) == tx_head) return 1;\n\treturn 0;\n}\n\nstatic void send_packet(struct e1000_nic * device, uint8_t* payload, size_t payload_size) {\n\tspin_lock(device->tx_lock);\n\tint tx_tail = read_command(device, E1000_REG_TXDESCTAIL);\n\tint tx_head = read_command(device, E1000_REG_TXDESCHEAD);\n\n\tif (tx_full(device, tx_tail, tx_head)) {\n\t\tint timeout = 1000;\n\t\tdo {\n\t\t\tspin_unlock(device->tx_lock);\n\t\t\tdelay_yield(10000);\n\t\t\ttimeout--;\n\t\t\tif (timeout == 0) {\n\t\t\t\tprintf(\"e1000: wait for tx timed out, giving up\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tspin_lock(device->tx_lock);\n\t\t\ttx_tail = read_command(device, E1000_REG_TXDESCTAIL);\n\t\t\ttx_head = read_command(device, E1000_REG_TXDESCHEAD);\n\t\t} while (tx_full(device, tx_tail, tx_head));\n\t}\n\n\tint sent = device->tx_index;\n\n\tmemcpy(device->tx_virt[device->tx_index], payload, payload_size);\n#if defined(__aarch64__)\n\tasm volatile (\"dmb ish\\nisb\" ::: \"memory\");\n\tcache_clean(device->tx_virt[device->tx_index]);\n#endif\n\n\tdevice->tx[device->tx_index].length = payload_size;\n\tdevice->tx[device->tx_index].cmd = CMD_EOP | CMD_IFCS | CMD_RS | CMD_RPS;\n\tdevice->tx[device->tx_index].status = 0;\n#if defined(__aarch64__)\n\tasm volatile (\"dmb ish\\nisb\" ::: \"memory\");\n#endif\n\n\tdevice->counts.tx_count++;\n\tdevice->counts.tx_bytes += payload_size;\n\n\tif (++device->tx_index == E1000_NUM_TX_DESC) {\n\t\tdevice->tx_index = 0;\n\t}\n\n\twrite_command(device, E1000_REG_TXDESCTAIL, device->tx_index);\n\tint st = read_command(device, E1000_REG_STATUS);\n\t(void)st;\n\n#if defined(__aarch64__)\n\tasm volatile (\"dc ivac, %0\\ndsb sy\\n\" :: \"r\"(&device->tx[sent]) : \"memory\");\n#else\n\t(void)sent;\n#endif\n\n\tspin_unlock(device->tx_lock);\n}\n\nstatic void init_rx(struct e1000_nic * device) {\n\twrite_command(device, E1000_REG_RXDESCLO, device->rx_phys);\n\twrite_command(device, E1000_REG_RXDESCHI, 0);\n\twrite_command(device, E1000_REG_RXDESCLEN, E1000_NUM_RX_DESC * sizeof(struct e1000_rx_desc));\n\twrite_command(device, E1000_REG_RXDESCHEAD, 0);\n\twrite_command(device, E1000_REG_RXDESCTAIL, E1000_NUM_RX_DESC - 1);\n\n\tdevice->rx_index = 0;\n\n\twrite_command(device, E1000_REG_RCTRL,\n\t\tRCTL_EN  |\n\t\t(1 << 2) | /* store bad packets */\n\t\t(1 << 4) | /* multicast promiscuous */\n\t\t(1 << 15) | /* broadcast accept */\n\t\t(1 << 25) | /* Extended size... */\n\t\t(3 << 16) | /*   4096 */\n\t\t(1 << 26) /* strip CRC */\n\t);\n}\n\nstatic void init_tx(struct e1000_nic * device) {\n\twrite_command(device, E1000_REG_TXDESCLO, device->tx_phys);\n\twrite_command(device, E1000_REG_TXDESCHI, 0);\n\twrite_command(device, E1000_REG_TXDESCLEN, E1000_NUM_TX_DESC * sizeof(struct e1000_tx_desc));\n\twrite_command(device, E1000_REG_TXDESCHEAD, 0);\n\twrite_command(device, E1000_REG_TXDESCTAIL, 0);\n\n\tdevice->tx_index = 0;\n\n\tuint32_t tctl = read_command(device, E1000_REG_TCTRL);\n\n\t/* Collision threshold */\n\ttctl &= ~(0xFF << 4);\n\ttctl |= (15 << 4);\n\n\t/* Turn it on */\n\ttctl |= TCTL_EN;\n\ttctl |= TCTL_PSP;\n\ttctl |= (1 << 24); /* retransmit on late collision */\n\n\twrite_command(device, E1000_REG_TCTRL, tctl);\n}\n\n#define privileged() do { if (this_core->current_process->user != USER_ROOT_UID) { return -EPERM; } } while (0)\n\nstatic int ioctl_e1000(fs_node_t * node, unsigned long request, void * argp) {\n\tstruct e1000_nic * nic = node->device;\n\n\tswitch (request) {\n\t\tcase SIOCGIFHWADDR:\n\t\t\t/* fill argp with mac */\n\t\t\tmemcpy(argp, nic->eth.mac, 6);\n\t\t\treturn 0;\n\n\t\tcase SIOCGIFADDR:\n\t\t\tif (nic->eth.ipv4_addr == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_addr, sizeof(nic->eth.ipv4_addr));\n\t\t\treturn 0;\n\t\tcase SIOCSIFADDR:\n\t\t\tprivileged();\n\t\t\tmemcpy(&nic->eth.ipv4_addr, argp, sizeof(nic->eth.ipv4_addr));\n\t\t\treturn 0;\n\t\tcase SIOCGIFNETMASK:\n\t\t\tif (nic->eth.ipv4_subnet == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_subnet, sizeof(nic->eth.ipv4_subnet));\n\t\t\treturn 0;\n\t\tcase SIOCSIFNETMASK:\n\t\t\tprivileged();\n\t\t\tmemcpy(&nic->eth.ipv4_subnet, argp, sizeof(nic->eth.ipv4_subnet));\n\t\t\treturn 0;\n\t\tcase SIOCGIFGATEWAY:\n\t\t\tif (nic->eth.ipv4_subnet == 0) return -ENOENT;\n\t\t\tmemcpy(argp, &nic->eth.ipv4_gateway, sizeof(nic->eth.ipv4_gateway));\n\t\t\treturn 0;\n\t\tcase SIOCSIFGATEWAY:\n\t\t\tprivileged();\n\t\t\tmemcpy(&nic->eth.ipv4_gateway, argp, sizeof(nic->eth.ipv4_gateway));\n\t\t\tnet_arp_ask(nic->eth.ipv4_gateway, node);\n\t\t\treturn 0;\n\n\t\tcase SIOCGIFADDR6:\n\t\t\treturn -ENOENT;\n\t\tcase SIOCSIFADDR6:\n\t\t\tprivileged();\n\t\t\tmemcpy(&nic->eth.ipv6_addr, argp, sizeof(nic->eth.ipv6_addr));\n\t\t\treturn 0;\n\n\t\tcase SIOCGIFFLAGS: {\n\t\t\tuint32_t * flags = argp;\n\t\t\t*flags = IFF_RUNNING;\n\t\t\tif (nic->link_status) *flags |= IFF_UP;\n\t\t\t/* We turn these on in our init_tx */\n\t\t\t*flags |= IFF_BROADCAST;\n\t\t\t*flags |= IFF_MULTICAST;\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase SIOCGIFMTU: {\n\t\t\tuint32_t * mtu = argp;\n\t\t\t*mtu = nic->eth.mtu;\n\t\t\treturn 0;\n\t\t}\n\n\t\tcase SIOCGIFCOUNTS: {\n\t\t\tmemcpy(argp, &nic->counts, sizeof(netif_counters_t));\n\t\t\treturn 0;\n\t\t}\n\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic ssize_t write_e1000(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tstruct e1000_nic * nic = node->device;\n\t/* write packet */\n\tsend_packet(nic, buffer, size);\n\treturn size;\n}\n\nstatic void ints_off(struct e1000_nic * nic) {\n\twrite_command(nic, E1000_REG_IMC, 0xFFFFFFFF);\n\twrite_command(nic, E1000_REG_ICR, 0xFFFFFFFF);\n\tread_command(nic, E1000_REG_STATUS);\n}\n\nstatic void e1000_init(struct e1000_nic * nic) {\n\tuint32_t e1000_device_pci = nic->pci_device;\n\n\tnic->rx_phys = mmu_allocate_n_frames(2) << 12;\n\tnic->rx = mmu_map_mmio_region(nic->rx_phys, 8192);\n\n\tnic->tx_phys = mmu_allocate_n_frames(2) << 12;\n\tnic->tx = mmu_map_mmio_region(nic->tx_phys, 8192);\n\n\tmemset((void*)nic->rx, 0, sizeof(struct e1000_rx_desc) * E1000_NUM_RX_DESC);\n\tmemset((void*)nic->tx, 0, sizeof(struct e1000_tx_desc) * E1000_NUM_TX_DESC);\n\n\t/* Allocate buffers */\n\tfor (int i = 0; i < E1000_NUM_RX_DESC; ++i) {\n\t\tnic->rx[i].addr = mmu_allocate_a_frame() << 12;\n\t\tnic->rx_virt[i] = mmu_map_mmio_region(nic->rx[i].addr, 4096);\n\t\tmmu_frame_map_address(mmu_get_page((uintptr_t)nic->rx_virt[i],0),MMU_FLAG_KERNEL|MMU_FLAG_WRITABLE,nic->rx[i].addr);\n\t\tnic->rx[i].status = 0;\n\t}\n\n\tfor (int i = 0; i < E1000_NUM_TX_DESC; ++i) {\n\t\tnic->tx[i].addr = mmu_allocate_a_frame() << 12;\n\t\tnic->tx_virt[i] = mmu_map_mmio_region(nic->tx[i].addr, 4096);\n\t\tmmu_frame_allocate(mmu_get_page((uintptr_t)nic->tx_virt[i],0),MMU_FLAG_KERNEL|MMU_FLAG_WRITABLE);\n\t\tmemset(nic->tx_virt[i], 0, 4096);\n\t\tnic->tx[i].status = 0;\n\t\tnic->tx[i].cmd = (1 << 0);\n\t}\n\n\tuint16_t command_reg = pci_read_field(e1000_device_pci, PCI_COMMAND, 2);\n\tcommand_reg = (1 << 1) | (1 << 2);\n\tpci_write_field(e1000_device_pci, PCI_COMMAND, 2, command_reg);\n\n#if defined(__aarch64__)\n\tpci_write_field(e1000_device_pci, PCI_BAR0, 4, 0x12200000);\n\tasm volatile (\"isb\" ::: \"memory\");\n#endif\n\n\tdelay_yield(10000);\n\n\t/* Is this size enough? */\n\tuint32_t initial_bar = pci_read_field(e1000_device_pci, PCI_BAR0, 4);\n\tnic->mmio_addr = (uintptr_t)mmu_map_mmio_region(initial_bar, 0x20000);\n#if defined(__aarch64__)\n\tasm volatile (\"isb\" ::: \"memory\");\n#endif\n\n\teeprom_detect(nic);\n\tread_mac(nic);\n\twrite_mac(nic);\n\n\tnic->queuer = (process_t*)this_core->current_process;\n\n\t#define CTRL_PHY_RST (1UL << 31UL)\n\t#define CTRL_RST     (1UL << 26UL)\n\t#define CTRL_SLU     (1UL << 6UL)\n\t#define CTRL_LRST    (1UL << 3UL)\n\n#if defined(__x86_64__)\n\tnic->irq_number = pci_get_interrupt(e1000_device_pci);\n\tirq_install_handler(nic->irq_number, irq_handler, nic->eth.if_name);\n#elif defined(__aarch64__)\n\tint irq;\n\tgic_map_pci_interrupt(nic->eth.if_name,e1000_device_pci,&irq,e1000_irq_handler,nic);\n\tnic->irq_number = irq;\n#endif\n\n\t/* Disable interrupts */\n\tints_off(nic);\n\n\t/* Turn off receive + transmit */\n\twrite_command(nic, E1000_REG_RCTRL, 0);\n\twrite_command(nic, E1000_REG_TCTRL, TCTL_PSP);\n\tread_command(nic, E1000_REG_STATUS);\n\tdelay_yield(10000);\n\n\t/* Reset everything */\n\tuint32_t ctrl = read_command(nic, E1000_REG_CTRL);\n\tctrl |= CTRL_RST;\n\twrite_command(nic, E1000_REG_CTRL, ctrl);\n\tdelay_yield(20000);\n\n\t/* Turn off interrupts _again_ */\n\tints_off(nic);\n\n\t/* Recommended flow control settings? */\n\twrite_command(nic, 0x0028, 0x002C8001);\n\twrite_command(nic, 0x002c, 0x0100);\n\twrite_command(nic, 0x0030, 0x8808);\n\twrite_command(nic, 0x0170, 0xFFFF);\n\n\t/* Link up */\n\tuint32_t status = read_command(nic, E1000_REG_CTRL);\n\tstatus |= CTRL_SLU;\n\tstatus |= (2 << 8); /* Speed to gigabit... */\n\tstatus &= ~CTRL_LRST;\n\tstatus &= ~CTRL_PHY_RST;\n\twrite_command(nic, E1000_REG_CTRL, status);\n\n\t/* Clear statistical counters */\n#ifndef __aarch64__\n\tfor (int i = 0; i < 128; ++i) {\n\t\twrite_command(nic, 0x5200 + i * 4, 0);\n\t}\n\n\tfor (int i = 0; i < 64; ++i) {\n\t\tread_command(nic, 0x4000 + i * 4);\n\t}\n#endif\n\n\tinit_rx(nic);\n\tinit_tx(nic);\n\n\twrite_command(nic, E1000_REG_RDTR, 0);\n\twrite_command(nic, E1000_REG_ITR, 500);\n\tread_command(nic, E1000_REG_STATUS);\n\n\tnic->link_status = (read_command(nic, E1000_REG_STATUS) & (1 << 1));\n\n\tnic->eth.device_node = calloc(sizeof(fs_node_t),1);\n\tsnprintf(nic->eth.device_node->name, 100, \"%s\", nic->eth.if_name);\n\tnic->eth.device_node->flags = FS_BLOCKDEVICE; /* NETDEVICE? */\n\tnic->eth.device_node->mask  = 0644; /* temporary; shouldn't be doing this with these device files */\n\tnic->eth.device_node->ioctl = ioctl_e1000;\n\tnic->eth.device_node->write = write_e1000;\n\tnic->eth.device_node->device = nic;\n\n\tnic->eth.mtu = 1500; /* guess */\n\n\tnet_add_interface(nic->eth.if_name, nic->eth.device_node);\n\n\tchar worker_name[34];\n\tsnprintf(worker_name, 33, \"[%s]\", nic->eth.if_name);\n\tnic->queuer = spawn_worker_thread(e1000_queuer, worker_name, nic);\n\n\tnic->configured = 1;\n\n\t/* Twiddle interrupts */\n\twrite_command(nic, E1000_REG_IMS, INTS);\n\tdelay_yield(10000);\n}\n\nstatic void find_e1000(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * found) {\n\tif ((vendorid == 0x8086) && (deviceid == 0x100e || deviceid == 0x1004 || deviceid == 0x100f || deviceid == 0x10ea || deviceid == 0x10d3)) {\n\t\t/* Allocate a device */\n\t\tstruct e1000_nic * nic = calloc(1,sizeof(struct e1000_nic));\n\t\tnic->pci_device = device;\n\t\tnic->deviceid   = deviceid;\n\t\tdevices[device_count++] = nic;\n\n\t\tsnprintf(nic->eth.if_name, 31,\n\t\t\t\"enp%ds%d\",\n\t\t\t(int)pci_extract_bus(device),\n\t\t\t(int)pci_extract_slot(device));\n\n\t\te1000_init(nic);\n\t\t*(int*)found = 1;\n\t}\n}\n\nstatic int e1000_install(int argc, char * argv[]) {\n\tuint32_t found = 0;\n\tpci_scan(&find_e1000, -1, &found);\n\n\tif (!found) {\n\t\t/* TODO: Clean up? Remove ourselves? */\n\t\treturn -ENODEV;\n\t}\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\t/* TODO: Uninstall device */\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"e1000\",\n\t.init = e1000_install,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/es1371.c",
    "content": "/**\n * @file kernel/audio/es1371.c\n * @brief Driver for the Ensoniq ES1371.\n * @package x86_64\n *\n * @ref http://www.vogons.org/download/file.php?id=13036&sid=30df81e15e2521deb842a79f451b1161\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n#include <kernel/list.h>\n#include <kernel/module.h>\n#include <kernel/time.h>\n#include <kernel/mod/snd.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#define ES_PORT_CONTROL      0x00\n#define ES_PORT_STATUS       0x04\n#define ES_PORT_UART_DATA    0x08\n#define ES_PORT_UART_STS     0x09\n#define ES_PORT_UART_TEST    0x0a\n#define ES_PORT_MEMORY_PAGE  0x0c\n#define ES_PORT_SRC_RW       0x10\n#define ES_PORT_CODEC_RW     0x14\n#define ES_PORT_LEGACY       0x18\n#define ES_PORT_SERIAL       0x20\n#define ES_PORT_P1_FRAME_CNT 0x24\n#define ES_PORT_P2_FRAME_CNT 0x28\n#define ES_PORT_R_FRAME_CNT  0x2c\n#define ES_PORT_P1_BUF_ADDR  0x30\n#define ES_PORT_P1_BUF_DEF   0x34\n#define ES_PORT_P2_BUF_ADDR  0x38\n#define ES_PORT_P2_BUF_DEF   0x3c\n\n/**\n * Control bits\n */\n#define ES_CTRL_SYNC_RES (1 << 14)\n#define ES_CTRL_DAC2_EN  (1 << 5)\n\n/**\n * Status bits\n */\n#define ES_STATUS_INTR (1 << 31)\n#define ES_STATUS_DAC2 (1 << 1)\n\n/**\n * Serial control bits\n */\n#define ES_SERIAL_P2_END_INC_MASK  (0x7 << 19)\n#define ES_SERIAL_P2_END_INC_TWO   (2 << 19)\n#define ES_SERIAL_P2_ST_INC_MASK   (0x7 << 16)\n#define ES_SERIAL_P2_LOOP_MASK   (1 << 14)\n#define ES_SERIAL_P2_PAUSE       (1 << 12)\n#define ES_SERIAL_P2_INTR_EN     (1 << 9)\n#define ES_SERIAL_P2_DAC_SEN     (1 << 6)\n#define ES_SERIAL_P2_MODE_MASK   (0x3 << 2)\n#define ES_SERIAL_P2_MODE_16BIT  (1 << 3)\n#define ES_SERIAL_P2_MODE_STEREO (1 << 2)\n\n/**\n * SRC RW register bits\n */\n#define ES_SRC_REG_MASK (0xF << 19) /* SRC, DAC1, DAC2, ADC disable... */\n#define ES_SRC_REG_WE   (1 << 24)\n#define ES_SRC_REG_BUSY (1 << 23)\n#define ES_SRC_REG(x)   ((x & 0x7F) << 25)\n\n/**\n * 16-bit registers for the samplerate converter\n */\n#define ES_SRC_P2_TRUNCN     0x74\n#define ES_SRC_P2_INTREGS    0x75\n#define ES_SRC_P2_ACCUMFRAC  0x76\n#define ES_SRC_P2_VFREQFRAC  0x77\n\n/**\n * Volume is specified with a sign bit, 3 integer bits, and the rest as fractional.\n * I am not sure what this actually gets used for - a multiplier?\n */\n#define ES_SRC_P2_VOL_L      0x7E\n#define ES_SRC_P2_VOL_R      0x7F\n\nstruct es1371_device {\n\tuint32_t pci_device;\n\tint portbase;\n\tint irq;\n\tint bits;\n\tint mask;\n\tuint32_t serial;\n\tint16_t * buf;\n};\n\nstatic struct es1371_device _device;\n\nstatic snd_knob_t _knobs[] = {\n\t{\n\t\t\"Master\",\n\t\tSND_KNOB_MASTER\n\t},\n};\n\nstatic int es1371_mixer_read(uint32_t knob_id, uint32_t *val);\nstatic int es1371_mixer_write(uint32_t knob_id, uint32_t val);\n\nstatic snd_device_t _snd = {\n\t.name            = \"Ensoniq ES1371\",\n\t.device          = &_device,\n\t.playback_speed  = 48000,\n\t.playback_format = SND_FORMAT_L16SLE,\n\n\t.knobs     = _knobs,\n\t.num_knobs = 1,\n\n\t.mixer_read  = es1371_mixer_read,\n\t.mixer_write = es1371_mixer_write,\n};\n\nstatic void find_es1371(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tstruct es1371_device * es1371 = extra;\n\n\tif ((vendorid == 0x1274) && (deviceid == 0x1371)) {\n\t\tes1371->pci_device = device;\n\t}\n\n}\n\nstatic int es1371_irq_handler(struct regs * regs) {\n\tuint32_t status = inportl(_device.portbase + ES_PORT_STATUS);\n\tif (!(status & ES_STATUS_INTR)) return 0;\n\tif (status & ES_STATUS_DAC2) {\n\t\t/* Reset the interrupt-waiting bit by toggling interrupts off and on for this DAC */\n\t\toutportl(_device.portbase + ES_PORT_SERIAL, _device.serial & ~ES_SERIAL_P2_INTR_EN);\n\t\toutportl(_device.portbase + ES_PORT_SERIAL, _device.serial);\n\t\toutportl(_device.portbase + ES_PORT_MEMORY_PAGE, 0x0c);\n\t\tuint32_t def = inportl(_device.portbase + ES_PORT_P2_BUF_DEF);\n\t\tsnd_request_buf(&_snd, 0x1000, (uint8_t*)_device.buf + ((def & 0xFFFF0000) ? 0 : 0x1000));\n\t}\n\tirq_ack(_device.irq);\n\treturn 1;\n}\n\nstatic int es1371_mixer_read(uint32_t knob_id, uint32_t *val) {\n\tswitch (knob_id) {\n\t\t/* This is essentially the same as how we get the volume from the ICH AC'97, but\n\t\t * we have to get the AC97 codec through this port access thing... */\n\t\tcase SND_KNOB_MASTER: {\n\t\t\toutportl(_device.portbase + ES_PORT_CODEC_RW, (0x02 << 16) | (1 << 23));\n\t\t\tuint32_t tmp = inportl(_device.portbase + ES_PORT_CODEC_RW) & 0xFFFF;\n\t\t\tif (tmp == 0x8000) {\n\t\t\t\t*val = 0;\n\t\t\t} else {\n\t\t\t\t/* 6 bit value */\n\t\t\t\t*val = (tmp & _device.mask) << (sizeof(*val) * 8 - _device.bits);\n\t\t\t\t*val = ~*val;\n\t\t\t\t*val &= (uint32_t)_device.mask << (sizeof(*val) * 8 - _device.bits);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic int es1371_mixer_write(uint32_t knob_id, uint32_t val) {\n\tswitch (knob_id) {\n\t\tcase SND_KNOB_MASTER: {\n\t\t\tuint16_t encoded;\n\t\t\tif (val == 0x0) {\n\t\t\t\tencoded = 0x8000;\n\t\t\t} else {\n\t\t\t\tval = ~val;\n\t\t\t\tval >>= (sizeof(val) * 8 - _device.bits);\n\t\t\t\tencoded = (val & 0xFF) | (val << 8);\n\t\t\t}\n\t\t\toutportl(_device.portbase + ES_PORT_CODEC_RW,\n\t\t\t\t(0x02 << 16) | encoded);\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\treturn -1;\n\t}\n\treturn 0;\n}\n\nstatic void delay_yield(size_t subticks) {\n\tunsigned long s, ss;\n\trelative_time(0, subticks, &s, &ss);\n\tsleep_until((process_t *)this_core->current_process, s, ss);\n\tswitch_task(0);\n}\n\n/**\n * @brief Write to the samplerate converter RAM\n */\nstatic void src_write(int port, uint16_t value) {\n\tuint32_t x;\n\twhile ((x = inportl(_device.portbase + ES_PORT_SRC_RW)) & ES_SRC_REG_BUSY);\n\tx &= ES_SRC_REG_MASK;\n\tx |= ES_SRC_REG_WE | ES_SRC_REG(port) | (value & 0xFFFF);\n\toutportl(_device.portbase + ES_PORT_SRC_RW, x);\n}\n\nstatic int es1371_install(int argc, char * argv[]) {\n\tpci_scan(&find_es1371, -1, &_device);\n\tif (!_device.pci_device) {\n\t\treturn -ENODEV;\n\t}\n\n\t/* Get I/O port from PCI */\n\t_device.portbase = pci_read_field(_device.pci_device, PCI_BAR0, 4) & ((uint32_t)-1) << 1;\n\n\t/* Enable port-IO, bus mastering, interrupts */\n\tuint16_t command_reg = pci_read_field(_device.pci_device, PCI_COMMAND, 2);\n\tcommand_reg |= (1 << 2) | (1 << 0);\n\tcommand_reg &= ~(1 << 10);\n\tpci_write_field(_device.pci_device,\n\t\tPCI_COMMAND, 2, command_reg);\n\n\t/* Reset the controller */\n\tuint32_t ctrl   = 0;\n\tuint32_t serial = 0;\n\toutportl(_device.portbase + ES_PORT_CONTROL, ctrl);\n\toutportl(_device.portbase + ES_PORT_SERIAL,  serial);\n\toutportl(_device.portbase + ES_PORT_LEGACY,  0);\n\toutportl(_device.portbase + ES_PORT_CONTROL, ctrl | ES_CTRL_SYNC_RES);\n\tinportl(_device.portbase + ES_PORT_CONTROL);\n\tdelay_yield(2000);\n\toutportl(_device.portbase + ES_PORT_CONTROL, ctrl);\n\n\t/* Get 8192 of audio buffer space */\n\tuintptr_t addr = mmu_allocate_n_frames(2) << 12;\n\tif (!addr) {\n\t\treturn -ENODEV;\n\t}\n\tif (addr > 0xFFFFffff) {\n\t\t/* This thing only supports 32-bit physical addresses, so if we got something\n\t\t * too high (unlikely at early boot) we need to bail. */\n\t\tprintf(\"es1371: Allocated buffer is beyond the reach of 32-bit DMA engine.\\n\");\n\t\treturn -ENODEV;\n\t}\n\n\t/* Map interrupt handler */\n\t_device.irq = pci_get_interrupt(_device.pci_device);\n\tirq_install_handler(_device.irq, es1371_irq_handler, \"es1371\");\n\n\t/* Zero out the audio buffer */\n\t_device.buf = mmu_map_from_physical(addr);\n\tmemset(_device.buf, 0, 0x2000);\n\n\t/* Disable sound rate converter while we program it */\n\toutportl(_device.portbase + ES_PORT_SRC_RW,\n\t\t(1 << 22));\n\n\t/* Turn everything off? */\n\tfor (int i = 0; i < 0x80; ++i) {\n\t\tsrc_write(i, 0);\n\t}\n\n\t/* Set for 48KHz */\n\tsrc_write(ES_SRC_P2_TRUNCN,  16 <<  4);\n\tsrc_write(ES_SRC_P2_INTREGS, 16 << 10);\n\n\t/* This seems to be the sample rate converter's left/right audio volume? */\n\tsrc_write(ES_SRC_P2_VOL_L, 0x1 << 12);\n\tsrc_write(ES_SRC_P2_VOL_R, 0x1 << 12);\n\n\toutportl(_device.portbase + ES_PORT_SRC_RW, 0);\n\n\toutportl(_device.portbase + ES_PORT_CODEC_RW, 0);\n\tdelay_yield(2000);\n\n\t/* Set AC'97 codec volumes, which are inverted (-dB) */\n\t_device.bits = 5;\n\t_device.mask = 0x1f;\n\toutportl(_device.portbase + ES_PORT_CODEC_RW,\n\t\t(0x02 << 16)); /* Master */\n\toutportl(_device.portbase + ES_PORT_CODEC_RW,\n\t\t(0x18 << 16)); /* PCM OUT */\n\n\toutportb(ES_PORT_UART_STS, 0);\n\toutportb(ES_PORT_UART_TEST, 0);\n\toutportb(ES_PORT_STATUS, 0);\n\n\t/* Set up buffer for two pages we can swap between,\n\t * which gives us good latency for immediate playback */\n\toutportl(_device.portbase + ES_PORT_MEMORY_PAGE, 0x0c);\n\toutportl(_device.portbase + ES_PORT_P2_BUF_ADDR, addr);\n\toutportl(_device.portbase + ES_PORT_P2_BUF_DEF, 0x7FF);\n\toutportl(_device.portbase + ES_PORT_P2_FRAME_CNT, 0x400);\n\n\t/* Configure playback mode */\n\tserial = inportl(_device.portbase + ES_PORT_SERIAL);\n\tserial &=\n\t\t~(ES_SERIAL_P2_LOOP_MASK | ES_SERIAL_P2_END_INC_MASK | ES_SERIAL_P2_DAC_SEN |\n\t\t\tES_SERIAL_P2_PAUSE | ES_SERIAL_P2_ST_INC_MASK | ES_SERIAL_P2_MODE_MASK);\n\tserial |=\n\t\tES_SERIAL_P2_INTR_EN |\n\t\tES_SERIAL_P2_MODE_STEREO | ES_SERIAL_P2_MODE_16BIT | /* 16-bit sterio */\n\t\tES_SERIAL_P2_END_INC_TWO;\n\toutportl(_device.portbase + ES_PORT_SERIAL, serial);\n\t_device.serial = serial;\n\n\t/* Turn it on! */\n\tctrl = inportl(_device.portbase + ES_PORT_CONTROL);\n\toutportl(_device.portbase + ES_PORT_CONTROL, ES_CTRL_DAC2_EN | ctrl);\n\n\tsnd_register(&_snd);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\tsnd_unregister(&_snd);\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"es1371\",\n\t.init = es1371_install,\n\t.fini = fini,\n};\n\n\n"
  },
  {
    "path": "modules/ext2.c",
    "content": "/**\n * @file modules/ext2.c\n * @brief Implementation of the Ext2 filesystem.\n * @package x86_64\n * @package aarch64\n *\n * @warning There are many known bugs in this implementation.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange <klange@toaruos.org>\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/time.h>\n#include <kernel/string.h>\n#include <kernel/spinlock.h>\n#include <kernel/tokenize.h>\n#include <kernel/module.h>\n#include <kernel/mutex.h>\n\n#include <sys/ioctl.h>\n\n#define debug_print(lvl, str, ...) do { if (this->flags & EXT2_FLAG_LOUD) { printf(\"ext2: %s: \" str \"\\n\", #lvl __VA_OPT__(,) __VA_ARGS__); } } while (0)\n\n#define EXT2_SUPER_MAGIC 0xEF53\n#define EXT2_DIRECT_BLOCKS 12\n\n/* Super block struct. */\nstruct ext2_superblock {\n\tuint32_t inodes_count;\n\tuint32_t blocks_count;\n\tuint32_t r_blocks_count;\n\tuint32_t free_blocks_count;\n\tuint32_t free_inodes_count;\n\tuint32_t first_data_block;\n\tuint32_t log_block_size;\n\tuint32_t log_frag_size;\n\tuint32_t blocks_per_group;\n\tuint32_t frags_per_group;\n\tuint32_t inodes_per_group;\n\tuint32_t mtime;\n\tuint32_t wtime;\n\n\tuint16_t mnt_count;\n\tuint16_t max_mnt_count;\n\tuint16_t magic;\n\tuint16_t state;\n\tuint16_t errors;\n\tuint16_t minor_rev_level;\n\n\tuint32_t lastcheck;\n\tuint32_t checkinterval;\n\tuint32_t creator_os;\n\tuint32_t rev_level;\n\n\tuint16_t def_resuid;\n\tuint16_t def_resgid;\n\n\t/* EXT2_DYNAMIC_REV */\n\tuint32_t first_ino;\n\tuint16_t inode_size;\n\tuint16_t block_group_nr;\n\tuint32_t feature_compat;\n\tuint32_t feature_incompat;\n\tuint32_t feature_ro_compat;\n\n\tuint8_t uuid[16];\n\tuint8_t volume_name[16];\n\n\tuint8_t last_mounted[64];\n\n\tuint32_t algo_bitmap;\n\n\t/* Performance Hints */\n\tuint8_t prealloc_blocks;\n\tuint8_t prealloc_dir_blocks;\n\tuint16_t _padding;\n\n\t/* Journaling Support */\n\tuint8_t journal_uuid[16];\n\tuint32_t journal_inum;\n\tuint32_t jounral_dev;\n\tuint32_t last_orphan;\n\n\t/* Directory Indexing Support */\n\tuint32_t hash_seed[4];\n\tuint8_t def_hash_version;\n\tuint16_t _padding_a;\n\tuint8_t _padding_b;\n\n\t/* Other Options */\n\tuint32_t default_mount_options;\n\tuint32_t first_meta_bg;\n\tuint8_t _unused[760];\n\n} __attribute__ ((packed));\n\ntypedef struct ext2_superblock ext2_superblock_t;\n\n/* Block group descriptor. */\nstruct ext2_bgdescriptor {\n\tuint32_t block_bitmap;\n\tuint32_t inode_bitmap;\t\t// block no. of inode bitmap\n\tuint32_t inode_table;\n\tuint16_t free_blocks_count;\n\tuint16_t free_inodes_count;\n\tuint16_t used_dirs_count;\n\tuint16_t pad;\n\tuint8_t reserved[12];\n} __attribute__ ((packed));\n\ntypedef struct ext2_bgdescriptor ext2_bgdescriptor_t;\n\n/* File Types */\n#define EXT2_S_IFSOCK\t0xC000\n#define EXT2_S_IFLNK\t0xA000\n#define EXT2_S_IFREG\t0x8000\n#define EXT2_S_IFBLK\t0x6000\n#define EXT2_S_IFDIR\t0x4000\n#define EXT2_S_IFCHR\t0x2000\n#define EXT2_S_IFIFO\t0x1000\n\n/* setuid, etc. */\n#define EXT2_S_ISUID\t0x0800\n#define EXT2_S_ISGID\t0x0400\n#define EXT2_S_ISVTX\t0x0200\n\n/* rights */\n#define EXT2_S_IRUSR\t0x0100\n#define EXT2_S_IWUSR\t0x0080\n#define EXT2_S_IXUSR\t0x0040\n#define EXT2_S_IRGRP\t0x0020\n#define EXT2_S_IWGRP\t0x0010\n#define EXT2_S_IXGRP\t0x0008\n#define EXT2_S_IROTH\t0x0004\n#define EXT2_S_IWOTH\t0x0002\n#define EXT2_S_IXOTH\t0x0001\n\n/* This is not actually the inode table.\n * It represents an inode in an inode table on disk. */\nstruct ext2_inodetable {\n\tuint16_t mode;\n\tuint16_t uid;\n\tuint32_t size;\t\t\t// file length in byte.\n\tuint32_t atime;\n\tuint32_t ctime;\n\tuint32_t mtime;\n\tuint32_t dtime;\n\tuint16_t gid;\n\tuint16_t links_count;\n\tuint32_t blocks;\n\tuint32_t flags;\n\tuint32_t osd1;\n\tuint32_t block[15];\n\tuint32_t generation;\n\tuint32_t file_acl;\n\tuint32_t dir_acl;\n\tuint32_t faddr;\n\tuint8_t osd2[12];\n} __attribute__ ((packed));\n\ntypedef struct ext2_inodetable ext2_inodetable_t;\n\n/* Represents directory entry on disk. */\nstruct ext2_dir {\n\tuint32_t inode;\n\tuint16_t rec_len;\n\tuint8_t name_len;\n\tuint8_t file_type;\n\tchar name[];\t\t/* Actually a set of characters, at most 255 bytes */\n} __attribute__ ((packed));\n\ntypedef struct ext2_dir ext2_dir_t;\n\n#define EXT2_BGD_BLOCK 2\n\n#define E_SUCCESS   0\n#define E_BADBLOCK  1\n#define E_NOSPACE   2\n#define E_BADPARENT 3\n\n#undef _symlink\n#define _symlink(inode) ((char *)(inode)->block)\n\n/*\n * EXT2 filesystem object\n */\ntypedef struct {\n\text2_superblock_t       * superblock;          /* Device superblock, contains important information */\n\text2_bgdescriptor_t     * block_groups;        /* Block Group Descriptor / Block groups */\n\tfs_node_t               * root_node;           /* Root FS node (attached to mountpoint) */\n\n\tfs_node_t               * block_device;        /* Block device node XXX unused */\n\n\tunsigned int              block_size;          /* Size of one block */\n\tunsigned int              pointers_per_block;  /* Number of pointers that fit in a block */\n\tunsigned int              inodes_per_group;    /* Number of inodes in a \"group\" */\n\tunsigned int              block_group_count;   /* Number of blocks groups */\n\n\tuint8_t                   bgd_block_span;\n\tuint8_t                   bgd_offset;\n\tunsigned int              inode_size;\n\n\tuint8_t *                 cache_data;\n\n\tint flags;\n\n\tsched_mutex_t *           mutex;\n} ext2_fs_t;\n\n#define EXT2_FLAG_READWRITE 0x0002\n#define EXT2_FLAG_LOUD      0x0004\n\n/*\n * These macros were used in the original toaru ext2 driver.\n * They make referring to some of the core parts of the drive a bit easier.\n */\n#define BGDS (this->block_group_count)\n#define SB   (this->superblock)\n#define BGD  (this->block_groups)\n#define RN   (this->root_node)\n\n/*\n * These macros deal with the block group descriptor bitmap\n */\n#define BLOCKBIT(n)  (bg_buffer[((n) >> 3)] & (1 << (((n) % 8))))\n#define BLOCKBYTE(n) (bg_buffer[((n) >> 3)])\n#define SETBIT(n)    (1 << (((n) % 8)))\n\nstatic int node_from_file(ext2_fs_t * this, ext2_inodetable_t *inode, ext2_dir_t *direntry,  fs_node_t *fnode);\nstatic int ext2_root(ext2_fs_t * this, ext2_inodetable_t *inode, fs_node_t *fnode);\nstatic ext2_inodetable_t * read_inode(ext2_fs_t * this, size_t inode);\nstatic void refresh_inode(ext2_fs_t * this, ext2_inodetable_t * inodet,  size_t inode);\nstatic int write_inode(ext2_fs_t * this, ext2_inodetable_t *inode, size_t index);\nstatic fs_node_t * finddir_ext2(fs_node_t *node, char *name);\nstatic size_t allocate_block(ext2_fs_t * this);\n\n/**\n * ext2->rewrite_superblock Rewrite the superblock.\n *\n * Superblocks are a bit different from other blocks, as they are always in the same place,\n * regardless of what the filesystem block size is. This doesn't work well with our setup,\n * so we need to special-case it.\n */\nstatic int rewrite_superblock(ext2_fs_t * this) {\n\twrite_fs(this->block_device, 1024, sizeof(ext2_superblock_t), (uint8_t *)SB);\n\treturn E_SUCCESS;\n}\n\n/**\n * ext2->read_block Read a block from the block device associated with this filesystem.\n *\n * The read block will be copied into the buffer pointed to by `buf`.\n *\n * @param block_no Number of block to read.\n * @param buf      Where to put the data read.\n * @returns Error code or E_SUCCESS\n */\nstatic int read_block(ext2_fs_t * this, unsigned int block_no, uint8_t * buf) {\n\t/* 0 is an invalid block number. So is anything beyond the total block count, but we can't check that. */\n\tif (!block_no) {\n\t\treturn E_BADBLOCK;\n\t}\n\n\t/* In such cases, we read directly from the block device */\n\tread_fs(this->block_device, block_no * this->block_size, this->block_size, (uint8_t *)buf);\n\n\t/* And return SUCCESS */\n\treturn E_SUCCESS;\n}\n\n/**\n * ext2->write_block Write a block to the block device.\n *\n * @param block_no Block to write\n * @param buf      Data in the block\n * @returns Error code or E_SUCCESSS\n */\nstatic int write_block(ext2_fs_t * this, unsigned int block_no, uint8_t *buf) {\n\tif (!block_no) {\n\t\tdebug_print(ERROR, \"Attempted to write to block #0. Enable tracing and retry this operation.\");\n\t\tdebug_print(ERROR, \"Your file system is most likely corrupted now.\");\n\t\treturn E_BADBLOCK;\n\t}\n\n\t/* This operation requires the filesystem lock */\n\twrite_fs(this->block_device, block_no * this->block_size, this->block_size, buf);\n\n\t/* We're done. */\n\treturn E_SUCCESS;\n}\n\n/**\n * ext2->set_block_number Set the \"real\" block number for a given \"inode\" block number.\n *\n * @param inode   Inode to operate on\n * @param iblock  Block offset within the inode\n * @param rblock  Real block number\n * @returns Error code or E_SUCCESS\n */\nstatic unsigned int set_block_number(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int iblock, unsigned int rblock) {\n\n\tunsigned int p = this->pointers_per_block;\n\n\t/* We're going to do some crazy math in a bit... */\n\tunsigned int a, b, c, d, e, f, g;\n\n\tuint8_t * tmp;\n\n\tif (iblock < EXT2_DIRECT_BLOCKS) {\n\t\tinode->block[iblock] = rblock;\n\t\treturn E_SUCCESS;\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p) {\n\t\t/* XXX what if inode->block[EXT2_DIRECT_BLOCKS] isn't set? */\n\t\tif (!inode->block[EXT2_DIRECT_BLOCKS]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) return E_NOSPACE;\n\t\t\tinode->block[EXT2_DIRECT_BLOCKS] = block_no;\n\t\t\twrite_inode(this, inode, inode_no);\n\t\t}\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp);\n\n\t\t((uint32_t *)tmp)[iblock - EXT2_DIRECT_BLOCKS] = rblock;\n\t\twrite_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp);\n\n\t\tfree(tmp);\n\t\treturn E_SUCCESS;\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p) {\n\t\ta = iblock - EXT2_DIRECT_BLOCKS;\n\t\tb = a - p;\n\t\tc = b / p;\n\t\td = b - c * p;\n\n\t\tif (!inode->block[EXT2_DIRECT_BLOCKS+1]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) return E_NOSPACE;\n\t\t\tinode->block[EXT2_DIRECT_BLOCKS+1] = block_no;\n\t\t\twrite_inode(this, inode, inode_no);\n\t\t}\n\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp);\n\n\t\tif (!((uint32_t *)tmp)[c]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) goto no_space_free;\n\t\t\t((uint32_t *)tmp)[c] = block_no;\n\t\t\twrite_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp);\n\t\t}\n\n\t\tuint32_t nblock = ((uint32_t *)tmp)[c];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\t((uint32_t  *)tmp)[d] = rblock;\n\t\twrite_block(this, nblock, (uint8_t *)tmp);\n\n\t\tfree(tmp);\n\t\treturn E_SUCCESS;\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p + p) {\n\t\ta = iblock - EXT2_DIRECT_BLOCKS;\n\t\tb = a - p;\n\t\tc = b - p * p;\n\t\td = c / (p * p);\n\t\te = c - d * p * p;\n\t\tf = e / p;\n\t\tg = e - f * p;\n\n\t\tif (!inode->block[EXT2_DIRECT_BLOCKS+2]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) return E_NOSPACE;\n\t\t\tinode->block[EXT2_DIRECT_BLOCKS+2] = block_no;\n\t\t\twrite_inode(this, inode, inode_no);\n\t\t}\n\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp);\n\n\t\tif (!((uint32_t *)tmp)[d]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) goto no_space_free;\n\t\t\t((uint32_t *)tmp)[d] = block_no;\n\t\t\twrite_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp);\n\t\t}\n\n\t\tuint32_t nblock = ((uint32_t *)tmp)[d];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\tif (!((uint32_t *)tmp)[f]) {\n\t\t\tunsigned int block_no = allocate_block(this);\n\t\t\tif (!block_no) goto no_space_free;\n\t\t\t((uint32_t *)tmp)[f] = block_no;\n\t\t\twrite_block(this, nblock, (uint8_t *)tmp);\n\t\t}\n\n\t\tnblock = ((uint32_t *)tmp)[f];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\t((uint32_t *)tmp)[g] = nblock;\n\t\twrite_block(this, nblock, (uint8_t *)tmp);\n\n\t\tfree(tmp);\n\t\treturn E_SUCCESS;\n\t}\n\n\tdebug_print(CRITICAL, \"EXT2 driver tried to write to a block number that was too high (%d)\", rblock);\n\treturn E_BADBLOCK;\nno_space_free:\n\tfree(tmp);\n\treturn E_NOSPACE;\n}\n\n/**\n * ext2->get_block_number Given an inode block number, get the real block number.\n *\n * @param inode   Inode to operate on\n * @param iblock  Block offset within the inode\n * @returns Real block number\n */\nstatic unsigned int get_block_number(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int iblock) {\n\n\tunsigned int p = this->pointers_per_block;\n\n\t/* We're going to do some crazy math in a bit... */\n\tunsigned int a, b, c, d, e, f, g;\n\n\tuint8_t * tmp;\n\n\tif (iblock < EXT2_DIRECT_BLOCKS) {\n\t\treturn inode->block[iblock];\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p) {\n\t\t/* XXX what if inode->block[EXT2_DIRECT_BLOCKS] isn't set? */\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp);\n\n\t\tunsigned int out = ((uint32_t *)tmp)[iblock - EXT2_DIRECT_BLOCKS];\n\t\tfree(tmp);\n\t\treturn out;\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p) {\n\t\ta = iblock - EXT2_DIRECT_BLOCKS;\n\t\tb = a - p;\n\t\tc = b / p;\n\t\td = b - c * p;\n\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp);\n\n\t\tuint32_t nblock = ((uint32_t *)tmp)[c];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\tunsigned int out = ((uint32_t  *)tmp)[d];\n\t\tfree(tmp);\n\t\treturn out;\n\t} else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p + p) {\n\t\ta = iblock - EXT2_DIRECT_BLOCKS;\n\t\tb = a - p;\n\t\tc = b - p * p;\n\t\td = c / (p * p);\n\t\te = c - d * p * p;\n\t\tf = e / p;\n\t\tg = e - f * p;\n\n\t\ttmp = malloc(this->block_size);\n\t\tread_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp);\n\n\t\tuint32_t nblock = ((uint32_t *)tmp)[d];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\tnblock = ((uint32_t *)tmp)[f];\n\t\tread_block(this, nblock, (uint8_t *)tmp);\n\n\t\tunsigned int out = ((uint32_t  *)tmp)[g];\n\t\tfree(tmp);\n\t\treturn out;\n\t}\n\n\tdebug_print(CRITICAL, \"EXT2 driver tried to read to a block number that was too high (%d)\", iblock);\n\n\treturn 0;\n}\n\nstatic int write_inode(ext2_fs_t * this, ext2_inodetable_t *inode, size_t index) {\n\tif (!index) {\n\t\tdprintf(\"ext2: Attempt to write inode 0\\n\");\n\t\treturn E_BADBLOCK;\n\t}\n\tindex--;\n\n\tsize_t group = index / this->inodes_per_group;\n\tif (group > BGDS) {\n\t\treturn E_BADBLOCK;\n\t}\n\n\tsize_t inode_table_block = BGD[group].inode_table;\n\tindex -= group * this->inodes_per_group;\n\tsize_t block_offset = (index * this->inode_size) / this->block_size;\n\tsize_t offset_in_block = index - block_offset * (this->block_size / this->inode_size);\n\n\text2_inodetable_t *inodet = malloc(this->block_size);\n\t/* Read the current table block */\n\tread_block(this, inode_table_block + block_offset, (uint8_t *)inodet);\n\tmemcpy((uint8_t *)((uintptr_t)inodet + offset_in_block * this->inode_size), inode, this->inode_size);\n\twrite_block(this, inode_table_block + block_offset, (uint8_t *)inodet);\n\tfree(inodet);\n\n\treturn E_SUCCESS;\n}\n\nstatic size_t allocate_block(ext2_fs_t * this) {\n\tsize_t block_no     = 0;\n\tsize_t block_offset = 0;\n\tsize_t group        = 0;\n\tuint8_t * bg_buffer = malloc(this->block_size);\n\n\tmutex_acquire(this->mutex);\n\n\tfor (unsigned int i = 0; i < BGDS; ++i) {\n\t\tif (BGD[i].free_blocks_count > 0) {\n\t\t\tread_block(this, BGD[i].block_bitmap, (uint8_t *)bg_buffer);\n\t\t\twhile (BLOCKBIT(block_offset)) {\n\t\t\t\t++block_offset;\n\t\t\t}\n\t\t\tblock_no = block_offset + SB->blocks_per_group * i;\n\t\t\tgroup = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!block_no) {\n\t\tmutex_release(this->mutex);\n\t\tdebug_print(CRITICAL, \"No available blocks, disk is out of space!\");\n\t\tfree(bg_buffer);\n\t\treturn 0;\n\t}\n\n\tdebug_print(WARNING, \"allocating block #%zu (group %zu)\", block_no, group);\n\n\tBLOCKBYTE(block_offset) |= SETBIT(block_offset);\n\twrite_block(this, BGD[group].block_bitmap, (uint8_t *)bg_buffer);\n\n\tBGD[group].free_blocks_count--;\n\tfor (int i = 0; i < this->bgd_block_span; ++i) {\n\t\twrite_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i));\n\t}\n\n\tSB->free_blocks_count--;\n\trewrite_superblock(this);\n\n\tmemset(bg_buffer, 0x00, this->block_size);\n\twrite_block(this, block_no, bg_buffer);\n\n\tmutex_release(this->mutex);\n\n\tfree(bg_buffer);\n\n\treturn block_no;\n\n}\n\n\n/**\n * ext2->allocate_inode_block Allocate a block in an inode.\n *\n * @param inode Inode to operate on\n * @param inode_no Number of the inode (this is not part of the struct)\n * @param block Block within inode to allocate\n * @returns Error code or E_SUCCESS\n */\nstatic int allocate_inode_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int block) {\n\tdebug_print(NOTICE, \"Allocating block #%d for inode #%d\", block, inode_no);\n\tunsigned int block_no = allocate_block(this);\n\n\tif (!block_no) return E_NOSPACE;\n\n\tset_block_number(this, inode, inode_no, block, block_no);\n\n\tunsigned int t = (block + 1) * (this->block_size / 512);\n\tif (inode->blocks < t) {\n\t\tdebug_print(NOTICE, \"Setting inode->blocks to %d = (%d fs blocks)\", t, t / (this->block_size / 512));\n\t\tinode->blocks = t;\n\t}\n\twrite_inode(this, inode, inode_no);\n\n\treturn E_SUCCESS;\n}\n\n/**\n * ext2->inode_read_block\n *\n * @param inode\n * @param no\n * @param block\n * @parma buf\n * @returns Real block number for reference.\n */\nstatic unsigned int inode_read_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int block, uint8_t * buf) {\n\n\tif (block >= inode->blocks / (this->block_size / 512)) {\n\t\tmemset(buf, 0x00, this->block_size);\n\t\tdebug_print(WARNING, \"Tried to read an invalid block. Asked for %d (0-indexed), but inode only has %d!\", block, inode->blocks / (this->block_size / 512));\n\t\treturn 0;\n\t}\n\n\tunsigned int real_block = get_block_number(this, inode, block);\n\tread_block(this, real_block, buf);\n\n\treturn real_block;\n}\n\n/**\n * ext2->inode_write_block\n */\nstatic unsigned int inode_write_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int block, uint8_t * buf) {\n\tif (block >= inode->blocks / (this->block_size / 512)) {\n\t\tdebug_print(WARNING, \"Attempting to write beyond the existing allocated blocks for this inode.\");\n\t\tdebug_print(WARNING, \"Inode %d, Block %d\", inode_no, block);\n\t}\n\n\tdebug_print(WARNING, \"clearing and allocating up to required blocks (block=%d, %d)\", block, inode->blocks);\n\tchar * empty = NULL;\n\n\twhile (block >= inode->blocks / (this->block_size / 512)) {\n\t\tallocate_inode_block(this, inode, inode_no, inode->blocks / (this->block_size / 512));\n\t\trefresh_inode(this, inode, inode_no);\n\t}\n\tif (empty) free(empty);\n\tdebug_print(WARNING, \"... done\");\n\n\tunsigned int real_block = get_block_number(this, inode, block);\n\tdebug_print(WARNING, \"Writing virtual block %d for inode %d maps to real block %d\", block, inode_no, real_block);\n\n\twrite_block(this, real_block, buf);\n\treturn real_block;\n}\n\n/**\n * ext2->create_entry\n *\n * @returns Error code or E_SUCCESS\n */\nstatic int create_entry(fs_node_t * parent, char * name, uint32_t inode) {\n\text2_fs_t * this = (ext2_fs_t *)parent->device;\n\n\text2_inodetable_t * pinode = read_inode(this,parent->inode);\n\tif (((pinode->mode & EXT2_S_IFDIR) == 0) || (name == NULL)) {\n\t\tdebug_print(WARNING, \"Attempted to allocate an inode in a parent that was not a directory.\");\n\t\treturn E_BADPARENT;\n\t}\n\n\tdebug_print(WARNING, \"Creating a directory entry for %s pointing to inode %d.\", name, inode);\n\n\t/* okay, how big is it... */\n\n\tdebug_print(WARNING, \"We need to append %zd bytes to the direcotry.\", sizeof(ext2_dir_t) + strlen(name));\n\n\tunsigned int rec_len = sizeof(ext2_dir_t) + strlen(name);\n\trec_len += (rec_len % 4) ? (4 - (rec_len % 4)) : 0;\n\n\tdebug_print(WARNING, \"Our directory entry looks like this:\");\n\tdebug_print(WARNING, \"  inode     = %d\", inode);\n\tdebug_print(WARNING, \"  rec_len   = %d\", rec_len);\n\tdebug_print(WARNING, \"  name_len  = %zd\", strlen(name));\n\tdebug_print(WARNING, \"  file_type = %d\", 0);\n\tdebug_print(WARNING, \"  name      = %s\", name);\n\n\tdebug_print(WARNING, \"The inode size is marked as: %d\", pinode->size);\n\tdebug_print(WARNING, \"Block size is %d\", this->block_size);\n\n\tuint8_t * block = malloc(this->block_size);\n\tuint8_t block_nr = 0;\n\tuint32_t dir_offset = 0;\n\tuint32_t total_offset = 0;\n\tint modify_or_replace = 0;\n\text2_dir_t *previous;\n\n\tinode_read_block(this, pinode, block_nr, block);\n\twhile (total_offset < pinode->size) {\n\t\tif (dir_offset >= this->block_size) {\n\t\t\tblock_nr++;\n\t\t\tdir_offset -= this->block_size;\n\t\t\tinode_read_block(this, pinode, block_nr, block);\n\t\t}\n\t\text2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset);\n\n\t\tunsigned int sreclen = d_ent->name_len + sizeof(ext2_dir_t);\n\t\tsreclen += (sreclen % 4) ? (4 - (sreclen % 4)) : 0;\n\n\t\t{\n\t\t\tchar f[d_ent->name_len+1];\n\t\t\tmemcpy(f, d_ent->name, d_ent->name_len);\n\t\t\tf[d_ent->name_len] = 0;\n\t\t\tdebug_print(WARNING, \" * file: %s\", f);\n\t\t}\n\t\tdebug_print(WARNING, \"   rec_len: %d\", d_ent->rec_len);\n\t\tdebug_print(WARNING, \"   type: %d\", d_ent->file_type);\n\t\tdebug_print(WARNING, \"   namel: %d\", d_ent->name_len);\n\t\tdebug_print(WARNING, \"   inode: %d\", d_ent->inode);\n\n\t\tif (d_ent->rec_len != sreclen && total_offset + d_ent->rec_len == pinode->size) {\n\t\t\tdebug_print(WARNING, \"  - should be %d, but instead points to end of block\", sreclen);\n\t\t\tdebug_print(WARNING, \"  - we've hit the end, should change this pointer\");\n\n\t\t\tdir_offset += sreclen;\n\t\t\ttotal_offset += sreclen;\n\n\t\t\tmodify_or_replace = 1; /* Modify */\n\t\t\tprevious = d_ent;\n\n\t\t\tbreak;\n\t\t}\n\n\t\tif (d_ent->inode == 0) {\n\t\t\tmodify_or_replace = 2; /* Replace */\n\t\t}\n\n\t\tdir_offset += d_ent->rec_len;\n\t\ttotal_offset += d_ent->rec_len;\n\t}\n\n\tif (!modify_or_replace) {\n\t\tdebug_print(WARNING, \"That's odd, this shouldn't have happened, we made it all the way here without hitting our two end conditions?\");\n\t}\n\n\tif (modify_or_replace == 1) {\n\t\tdebug_print(WARNING, \"The last node in the list is a real node, we need to modify it.\");\n\n\t\tif (dir_offset + rec_len >= this->block_size) {\n\t\t\tblock_nr++;\n\t\t\tallocate_inode_block(this, pinode, parent->inode, block_nr);\n\t\t\tmemset(block, 0, this->block_size);\n\t\t\tdir_offset = 0;\n\t\t\tpinode->size += this->block_size;\n\t\t\twrite_inode(this, pinode, parent->inode);\n\t\t} else {\n\t\t\tunsigned int sreclen = previous->name_len + sizeof(ext2_dir_t);\n\t\t\tsreclen += (sreclen % 4) ? (4 - (sreclen % 4)) : 0;\n\t\t\tprevious->rec_len = sreclen;\n\t\t\tdebug_print(WARNING, \"Set previous node rec_len to %d\", sreclen);\n\t\t}\n\n\t} else if (modify_or_replace == 2) {\n\t\tdebug_print(WARNING, \"The last node in the list is a fake node, we'll replace it.\");\n\t}\n\n\tdebug_print(WARNING, \" total_offset = 0x%x\", total_offset);\n\tdebug_print(WARNING, \"   dir_offset = 0x%x\", dir_offset);\n\text2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset);\n\n\td_ent->inode     = inode;\n\td_ent->rec_len   = this->block_size - dir_offset;\n\td_ent->name_len  = strlen(name);\n\td_ent->file_type = 0; /* This is unused */\n\tmemcpy(d_ent->name, name, strlen(name));\n\n\tinode_write_block(this, pinode, parent->inode, block_nr, block);\n\n\tfree(block);\n\tfree(pinode);\n\n\n\treturn E_NOSPACE;\n}\n\nstatic unsigned int allocate_inode(ext2_fs_t * this) {\n\tuint32_t node_no     = 0;\n\tuint32_t node_offset = 0;\n\tuint32_t group       = 0;\n\tuint8_t * bg_buffer  = malloc(this->block_size);\n\n\tmutex_acquire(this->mutex);\n\n\tfor (unsigned int i = 0; i < BGDS; ++i) {\n\t\tif (BGD[i].free_inodes_count > 0) {\n\t\t\tdebug_print(NOTICE, \"Group %d has %d free inodes.\", i, BGD[i].free_inodes_count);\n\t\t\tread_block(this, BGD[i].inode_bitmap, (uint8_t *)bg_buffer);\n\n\t\t\t/* Sorry for the weird loops */\n\t\t\twhile (1) {\n\t\t\t\twhile (BLOCKBIT(node_offset)) {\n\t\t\t\t\tnode_offset++;\n\t\t\t\t\tif (node_offset == this->inodes_per_group) {\n\t\t\t\t\t\tgoto _next_block;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tnode_no = node_offset + i * this->inodes_per_group + 1;\n\t\t\t\t/* Is this a reserved inode? */\n\t\t\t\tif (node_no <= 10) {\n\t\t\t\t\tnode_offset++;\n\t\t\t\t\tif (node_offset == this->inodes_per_group) {\n\t\t\t\t\t\tgoto _next_block;\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (node_offset == this->inodes_per_group) {\n\t\t\t\t_next_block:\n\t\t\t\tnode_offset = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tgroup = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (!node_no) {\n\t\tmutex_release(this->mutex);\n\t\tdprintf(\"ext2: Out of inodes? node_no = 0\\n\");\n\t\treturn 0;\n\t}\n\n\tBLOCKBYTE(node_offset) |= SETBIT(node_offset);\n\n\twrite_block(this, BGD[group].inode_bitmap, (uint8_t *)bg_buffer);\n\tfree(bg_buffer);\n\n\tBGD[group].free_inodes_count--;\n\tfor (int i = 0; i < this->bgd_block_span; ++i) {\n\t\twrite_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i));\n\t}\n\n\tSB->free_inodes_count--;\n\trewrite_superblock(this);\n\n\tmutex_release(this->mutex);\n\n\treturn node_no;\n}\n\nstatic int mkdir_ext2(fs_node_t * parent, char * name, mode_t permission) {\n\tif (!name) return -EINVAL;\n\n\text2_fs_t * this = parent->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\t/* first off, check if it exists */\n\tfs_node_t * check = finddir_ext2(parent, name);\n\tif (check) {\n\t\tdebug_print(WARNING, \"A file by this name already exists: %s\", name);\n\t\tfree(check);\n\t\treturn -EEXIST;\n\t}\n\n\tif (!has_permission(parent, 02) || !has_permission(parent, 01)) {\n\t\treturn -EACCES;\n\t}\n\n\t/* Allocate an inode for it */\n\tunsigned int inode_no = allocate_inode(this);\n\text2_inodetable_t * inode = read_inode(this,inode_no);\n\n\t/* Set the access and creation times to now */\n\tinode->atime = now();\n\tinode->ctime = inode->atime;\n\tinode->mtime = inode->atime;\n\tinode->dtime = 0; /* This inode was never deleted */\n\n\t/* Empty the file */\n\tmemset(inode->block, 0x00, sizeof(inode->block));\n\tinode->blocks = 0;\n\tinode->size = 0; /* empty */\n\n\t/* Assign it to root */\n\tinode->uid = this_core->current_process->user;\n\tinode->gid = this_core->current_process->user_group;\n\n\t/* misc */\n\tinode->faddr = 0;\n\tinode->links_count = 2; /* There's the parent's pointer to us, and our pointer to us. */\n\tinode->flags = 0;\n\tinode->osd1 = 0;\n\tinode->generation = 0;\n\tinode->file_acl = 0;\n\tinode->dir_acl = 0;\n\n\t/* File mode */\n\tinode->mode = EXT2_S_IFDIR;\n\tinode->mode |= 0xFFF & permission;\n\n\t/* Write the osd blocks to 0 */\n\tmemset(inode->osd2, 0x00, sizeof(inode->osd2));\n\n\t/* Write out inode changes */\n\twrite_inode(this, inode, inode_no);\n\n\t/* Now append the entry to the parent */\n\tcreate_entry(parent, name, inode_no);\n\n\tinode->size = this->block_size;\n\twrite_inode(this, inode, inode_no);\n\n\tuint8_t * tmp = malloc(this->block_size);\n\text2_dir_t * t = malloc(12);\n\tmemset(t, 0, 12);\n\tt->inode = inode_no;\n\tt->rec_len = 12;\n\tt->name_len = 1;\n\tt->name[0] = '.';\n\tmemcpy(&tmp[0], t, 12);\n\tt->inode = parent->inode;\n\tt->name_len = 2;\n\tt->name[1] = '.';\n\tt->rec_len = this->block_size - 12;\n\tmemcpy(&tmp[12], t, 12);\n\tfree(t);\n\n\tinode_write_block(this, inode, inode_no, 0, tmp);\n\n\tfree(inode);\n\tfree(tmp);\n\n\t/* Update parent link count */\n\text2_inodetable_t * pinode = read_inode(this, parent->inode);\n\tpinode->links_count++;\n\twrite_inode(this, pinode, parent->inode);\n\tfree(pinode);\n\n\t/* Update directory count in block group descriptor */\n\tuint32_t group = inode_no / this->inodes_per_group;\n\tBGD[group].used_dirs_count++;\n\tfor (int i = 0; i < this->bgd_block_span; ++i) {\n\t\twrite_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i));\n\t}\n\n\treturn 0;\n}\n\nstatic int create_ext2(fs_node_t * parent, char * name, mode_t permission) {\n\tif (!name) return -EINVAL;\n\n\text2_fs_t * this = parent->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\t/* first off, check if it exists */\n\tfs_node_t * check = finddir_ext2(parent, name);\n\tif (check) {\n\t\tdebug_print(WARNING, \"A file by this name already exists: %s\", name);\n\t\tfree(check);\n\t\treturn -EEXIST;\n\t}\n\n\t/* Allocate an inode for it */\n\tunsigned int inode_no = allocate_inode(this);\n\text2_inodetable_t * inode = read_inode(this,inode_no);\n\n\t/* Set the access and creation times to now */\n\tinode->atime = now();\n\tinode->ctime = inode->atime;\n\tinode->mtime = inode->atime;\n\tinode->dtime = 0; /* This inode was never deleted */\n\n\t/* Empty the file */\n\tmemset(inode->block, 0x00, sizeof(inode->block));\n\tinode->blocks = 0;\n\tinode->size = 0; /* empty */\n\n\tinode->uid = this_core->current_process->user;\n\tinode->gid = this_core->current_process->user_group;\n\n\t/* misc */\n\tinode->faddr = 0;\n\tinode->links_count = 1; /* The one we're about to create. */\n\tinode->flags = 0;\n\tinode->osd1 = 0;\n\tinode->generation = 0;\n\tinode->file_acl = 0;\n\tinode->dir_acl = 0;\n\n\t/* File mode */\n\t/* TODO: Use the mask from `permission` */\n\tinode->mode = EXT2_S_IFREG;\n\tinode->mode |= 0xFFF & permission;\n\n\t/* Write the osd blocks to 0 */\n\tmemset(inode->osd2, 0x00, sizeof(inode->osd2));\n\n\t/* Write out inode changes */\n\twrite_inode(this, inode, inode_no);\n\n\t/* Now append the entry to the parent */\n\tcreate_entry(parent, name, inode_no);\n\n\tfree(inode);\n\n\treturn 0;\n}\n\nstatic int chmod_ext2(fs_node_t * node, mode_t mode) {\n\text2_fs_t * this = node->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\text2_inodetable_t * inode = read_inode(this,node->inode);\n\n\tinode->mode = (inode->mode & 0xFFFFF000) | mode;\n\n\twrite_inode(this, inode, node->inode);\n\n\treturn 0;\n}\n\n/**\n * direntry_ext2\n */\nstatic ext2_dir_t * direntry_ext2(ext2_fs_t * this, ext2_inodetable_t * inode, uint32_t no, uint32_t index) {\n\tuint8_t *block = malloc(this->block_size);\n\tuint8_t block_nr = 0;\n\tinode_read_block(this, inode, block_nr, block);\n\tuint32_t dir_offset = 0;\n\tuint32_t total_offset = 0;\n\tuint32_t dir_index = 0;\n\n\twhile (total_offset < inode->size && dir_index <= index) {\n\t\text2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset);\n\n\t\tif (d_ent->inode != 0 && dir_index == index) {\n\t\t\text2_dir_t *out = malloc(d_ent->rec_len);\n\t\t\tmemcpy(out, d_ent, d_ent->rec_len);\n\t\t\tfree(block);\n\t\t\treturn out;\n\t\t}\n\n\t\tdir_offset += d_ent->rec_len;\n\t\ttotal_offset += d_ent->rec_len;\n\n\t\tif (d_ent->inode) {\n\t\t\tdir_index++;\n\t\t}\n\n\t\tif (dir_offset >= this->block_size) {\n\t\t\tblock_nr++;\n\t\t\tdir_offset -= this->block_size;\n\t\t\tinode_read_block(this, inode, block_nr, block);\n\t\t}\n\t}\n\n\tfree(block);\n\treturn NULL;\n}\n\n/**\n * finddir_ext2\n */\nstatic fs_node_t * finddir_ext2(fs_node_t *node, char *name) {\n\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\n\text2_inodetable_t *inode = read_inode(this,node->inode);\n\t//assert(inode->mode & EXT2_S_IFDIR);\n\tuint8_t * block = malloc(this->block_size);\n\text2_dir_t *direntry = NULL;\n\tuint8_t block_nr = 0;\n\tinode_read_block(this, inode, block_nr, block);\n\tuint32_t dir_offset = 0;\n\tuint32_t total_offset = 0;\n\n\twhile (total_offset < inode->size) {\n\t\tif (dir_offset >= this->block_size) {\n\t\t\tblock_nr++;\n\t\t\tdir_offset -= this->block_size;\n\t\t\tinode_read_block(this, inode, block_nr, block);\n\t\t}\n\t\text2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset);\n\n\t\tif (d_ent->inode == 0 || strlen(name) != d_ent->name_len) {\n\t\t\tdir_offset += d_ent->rec_len;\n\t\t\ttotal_offset += d_ent->rec_len;\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tchar *dname = malloc(sizeof(char) * (d_ent->name_len + 1));\n\t\tmemcpy(dname, &(d_ent->name), d_ent->name_len);\n\t\tdname[d_ent->name_len] = '\\0';\n\t\tif (!strcmp(dname, name)) {\n\t\t\tfree(dname);\n\t\t\tdirentry = malloc(d_ent->rec_len);\n\t\t\tmemcpy(direntry, d_ent, d_ent->rec_len);\n\t\t\tbreak;\n\t\t}\n\t\tfree(dname);\n\n\t\tdir_offset += d_ent->rec_len;\n\t\ttotal_offset += d_ent->rec_len;\n\t}\n\tfree(inode);\n\tif (!direntry) {\n\t\tfree(block);\n\t\treturn NULL;\n\t}\n\tfs_node_t *outnode = malloc(sizeof(fs_node_t));\n\tmemset(outnode, 0, sizeof(fs_node_t));\n\n\tinode = read_inode(this, direntry->inode);\n\n\tif (!node_from_file(this, inode, direntry, outnode)) {\n\t\tdebug_print(CRITICAL, \"Oh dear. Couldn't allocate the outnode?\");\n\t}\n\n\tfree(direntry);\n\tfree(inode);\n\tfree(block);\n\treturn outnode;\n}\n\nstatic int unlink_ext2(fs_node_t * node, char * name) {\n\t/* XXX this is a very bad implementation */\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\text2_inodetable_t *inode = read_inode(this,node->inode);\n\t//assert(inode->mode & EXT2_S_IFDIR);\n\tuint8_t * block = malloc(this->block_size);\n\text2_dir_t *direntry = NULL;\n\tuint8_t block_nr = 0;\n\tinode_read_block(this, inode, block_nr, block);\n\tuint32_t dir_offset = 0;\n\tuint32_t total_offset = 0;\n\n\twhile (total_offset < inode->size) {\n\t\tif (dir_offset >= this->block_size) {\n\t\t\tblock_nr++;\n\t\t\tdir_offset -= this->block_size;\n\t\t\tinode_read_block(this, inode, block_nr, block);\n\t\t}\n\t\text2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset);\n\n\t\tif (d_ent->inode == 0 || strlen(name) != d_ent->name_len) {\n\t\t\tdir_offset += d_ent->rec_len;\n\t\t\ttotal_offset += d_ent->rec_len;\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tchar *dname = malloc(sizeof(char) * (d_ent->name_len + 1));\n\t\tmemcpy(dname, &(d_ent->name), d_ent->name_len);\n\t\tdname[d_ent->name_len] = '\\0';\n\t\tif (!strcmp(dname, name)) {\n\t\t\tfree(dname);\n\t\t\tdirentry = d_ent;\n\t\t\tbreak;\n\t\t}\n\t\tfree(dname);\n\n\t\tdir_offset += d_ent->rec_len;\n\t\ttotal_offset += d_ent->rec_len;\n\t}\n\tif (!direntry) {\n\t\tfree(inode);\n\t\tfree(block);\n\t\treturn -ENOENT;\n\t}\n\n\tunsigned int new_inode = direntry->inode;\n\tdirentry->inode = 0;\n\tinode_write_block(this, inode, node->inode, block_nr, block);\n\tfree(inode);\n\tfree(block);\n\n\tinode = read_inode(this, new_inode);\n\n\tif (inode->links_count == 1) {\n\t\tdprintf(\"ext2: TODO: unlinking '%s' (inode=%u) which now has no links; should delete\\n\",\n\t\t\tname, new_inode);\n\t}\n\n\tif (inode->links_count > 0) {\n\t\tinode->links_count--;\n\t\twrite_inode(this, inode, new_inode);\n\t}\n\n\tfree(inode);\n\n\treturn 0;\n}\n\n\nstatic void refresh_inode(ext2_fs_t * this, ext2_inodetable_t * inodet,  size_t inode) {\n\tif (!inode) {\n\t\tdprintf(\"ext2: Attempt to read inode 0\\n\");\n\t\treturn;\n\t}\n\tinode--;\n\n\tuint32_t group = inode / this->inodes_per_group;\n\tif (group > BGDS) {\n\t\treturn;\n\t}\n\tuint32_t inode_table_block = BGD[group].inode_table;\n\tinode -= group * this->inodes_per_group;\t// adjust index within group\n\tuint32_t block_offset\t\t= (inode * this->inode_size) / this->block_size;\n\tuint32_t offset_in_block    = inode - block_offset * (this->block_size / this->inode_size);\n\n\tuint8_t * buf = malloc(this->block_size);\n\n\tread_block(this, inode_table_block + block_offset, buf);\n\n\text2_inodetable_t *inodes = (ext2_inodetable_t *)buf;\n\n\tmemcpy(inodet, (uint8_t *)((uintptr_t)inodes + offset_in_block * this->inode_size), this->inode_size);\n\n\tfree(buf);\n}\n\n/**\n * read_inode\n */\nstatic ext2_inodetable_t * read_inode(ext2_fs_t * this, size_t inode) {\n\text2_inodetable_t *inodet   = malloc(this->inode_size);\n\trefresh_inode(this, inodet, inode);\n\treturn inodet;\n}\n\nstatic ssize_t read_ext2(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\text2_inodetable_t * inode = read_inode(this, node->inode);\n\tuint32_t end;\n\tif (inode->size == 0) return 0;\n\tif (offset + size > inode->size) {\n\t\tend = inode->size;\n\t} else {\n\t\tend = offset + size;\n\t}\n\tuint32_t start_block  = offset / this->block_size;\n\tuint32_t end_block    = end / this->block_size;\n\tuint32_t end_size     = end - end_block * this->block_size;\n\tuint32_t size_to_read = end - offset;\n\n\tuint8_t * buf = malloc(this->block_size);\n\tif (start_block == end_block) {\n\t\tinode_read_block(this, inode, start_block, buf);\n\t\tmemcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), size_to_read);\n\t} else {\n\t\tuint32_t block_offset;\n\t\tuint32_t blocks_read = 0;\n\t\tfor (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) {\n\t\t\tif (block_offset == start_block) {\n\t\t\t\tinode_read_block(this, inode, block_offset, buf);\n\t\t\t\tmemcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), this->block_size - (offset % this->block_size));\n\t\t\t} else {\n\t\t\t\tinode_read_block(this, inode, block_offset, buf);\n\t\t\t\tmemcpy(buffer + this->block_size * blocks_read - (offset % this->block_size), buf, this->block_size);\n\t\t\t}\n\t\t}\n\t\tif (end_size) {\n\t\t\tinode_read_block(this, inode, end_block, buf);\n\t\t\tmemcpy(buffer + this->block_size * blocks_read - (offset % this->block_size), buf, end_size);\n\t\t}\n\t}\n\tfree(inode);\n\tfree(buf);\n\treturn size_to_read;\n}\n\nstatic ssize_t write_inode_buffer(ext2_fs_t * this, ext2_inodetable_t * inode, uint32_t inode_number, off_t offset, size_t size, uint8_t *buffer) {\n\tuint32_t end = offset + size;\n\tif (end > inode->size) {\n\t\tinode->size = end;\n\t\twrite_inode(this, inode, inode_number);\n\t}\n\n\tuint32_t start_block  = offset / this->block_size;\n\tuint32_t end_block    = end / this->block_size;\n\tuint32_t end_size     = end - end_block * this->block_size;\n\tuint32_t size_to_read = end - offset;\n\tuint8_t * buf = malloc(this->block_size);\n\tif (start_block == end_block) {\n\t\tinode_read_block(this, inode, start_block, buf);\n\t\tmemcpy((uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), buffer, size_to_read);\n\t\tinode_write_block(this, inode, inode_number, start_block, buf);\n\t} else {\n\t\tuint32_t block_offset;\n\t\tuint32_t blocks_read = 0;\n\t\tfor (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) {\n\t\t\tif (block_offset == start_block) {\n\t\t\t\tint b = inode_read_block(this, inode, block_offset, buf);\n\t\t\t\tmemcpy((uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), buffer, this->block_size - (offset % this->block_size));\n\t\t\t\tinode_write_block(this, inode, inode_number, block_offset, buf);\n\t\t\t\tif (!b) {\n\t\t\t\t\trefresh_inode(this, inode, inode_number);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tint b = inode_read_block(this, inode, block_offset, buf);\n\t\t\t\tmemcpy(buf, buffer + this->block_size * blocks_read - (offset % this->block_size), this->block_size);\n\t\t\t\tinode_write_block(this, inode, inode_number, block_offset, buf);\n\t\t\t\tif (!b) {\n\t\t\t\t\trefresh_inode(this, inode, inode_number);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (end_size) {\n\t\t\tinode_read_block(this, inode, end_block, buf);\n\t\t\tmemcpy(buf, buffer + this->block_size * blocks_read - (offset % this->block_size), end_size);\n\t\t\tinode_write_block(this, inode, inode_number, end_block, buf);\n\t\t}\n\t}\n\tfree(buf);\n\treturn size_to_read;\n}\n\nstatic ssize_t write_ext2(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\text2_inodetable_t * inode = read_inode(this, node->inode);\n\n\tssize_t rv = write_inode_buffer(this, inode, node->inode, offset, size, buffer);\n\tfree(inode);\n\treturn rv;\n}\n\nstatic int truncate_ext2(fs_node_t * node, size_t size) {\n\text2_fs_t * this = node->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\tif (size != 0) return -ENOTSUP;\n\n\text2_inodetable_t * inode = read_inode(this,node->inode);\n\tinode->size = 0;\n\twrite_inode(this, inode, node->inode);\n\treturn 0;\n}\n\nstatic void open_ext2(fs_node_t *node, unsigned int flags) {\n\t/* Nothing to do here */\n}\n\nstatic void close_ext2(fs_node_t *node) {\n\t/* Nothing to do here */\n}\n\n\n/**\n * readdir_ext2\n */\nstatic struct dirent * readdir_ext2(fs_node_t *node, unsigned long index) {\n\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\n\text2_inodetable_t *inode = read_inode(this, node->inode);\n\t//assert(inode->mode & EXT2_S_IFDIR);\n\text2_dir_t *direntry = direntry_ext2(this, inode, node->inode, index);\n\tif (!direntry) {\n\t\tfree(inode);\n\t\treturn NULL;\n\t}\n\tstruct dirent *dirent = malloc(sizeof(struct dirent));\n\tmemcpy(&dirent->d_name, &direntry->name, direntry->name_len);\n\tdirent->d_name[direntry->name_len] = '\\0';\n\tdirent->d_ino = direntry->inode;\n\tfree(direntry);\n\tfree(inode);\n\treturn dirent;\n}\n\nstatic int symlink_ext2(fs_node_t * parent, char * target, char * name) {\n\tif (!name) return -EINVAL;\n\n\text2_fs_t * this = parent->device;\n\tif (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS;\n\n\t/* first off, check if it exists */\n\tfs_node_t * check = finddir_ext2(parent, name);\n\tif (check) {\n\t\tdebug_print(WARNING, \"A file by this name already exists: %s\", name);\n\t\tfree(check);\n\t\treturn -EEXIST; /* this should probably have a return value... */\n\t}\n\n\t/* Allocate an inode for it */\n\tunsigned int inode_no = allocate_inode(this);\n\text2_inodetable_t * inode = read_inode(this,inode_no);\n\n\t/* Set the access and creation times to now */\n\tinode->atime = now();\n\tinode->ctime = inode->atime;\n\tinode->mtime = inode->atime;\n\tinode->dtime = 0; /* This inode was never deleted */\n\n\t/* Empty the file */\n\tmemset(inode->block, 0x00, sizeof(inode->block));\n\tinode->blocks = 0;\n\tinode->size = 0; /* empty */\n\n\t/* Assign it to current user */\n\tinode->uid = this_core->current_process->user;\n\tinode->gid = this_core->current_process->user_group;\n\n\t/* misc */\n\tinode->faddr = 0;\n\tinode->links_count = 1; /* The one we're about to create. */\n\tinode->flags = 0;\n\tinode->osd1 = 0;\n\tinode->generation = 0;\n\tinode->file_acl = 0;\n\tinode->dir_acl = 0;\n\n\tinode->mode = EXT2_S_IFLNK;\n\n\t/* I *think* this is what you're supposed to do with symlinks */\n\tinode->mode |= 0777;\n\n\t/* Write the osd blocks to 0 */\n\tmemset(inode->osd2, 0x00, sizeof(inode->osd2));\n\n\tsize_t target_len = strlen(target);\n\tint embedded = target_len <= 60; // sizeof(_symlink(inode));\n\tif (embedded) {\n\t\tmemcpy(_symlink(inode), target, target_len);\n\t\tinode->size = target_len;\n\t}\n\n\t/* Write out inode changes */\n\twrite_inode(this, inode, inode_no);\n\n\t/* Now append the entry to the parent */\n\tcreate_entry(parent, name, inode_no);\n\n\n\t/* If we didn't embed it in the inode just use write_inode_buffer to finish the job */\n\tif (!embedded) {\n\t\twrite_inode_buffer(parent->device, inode, inode_no, 0, target_len, (uint8_t *)target);\n\t}\n\tfree(inode);\n\n\treturn 0;\n}\n\nstatic ssize_t readlink_ext2(fs_node_t * node, char * buf, size_t size) {\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\text2_inodetable_t * inode = read_inode(this, node->inode);\n\tsize_t read_size = inode->size < size ? inode->size : size;\n\tif (inode->size > 60) { //sizeof(_symlink(inode))) {\n\t\tread_ext2(node, 0, read_size, (uint8_t *)buf);\n\t} else {\n\t\tmemcpy(buf, _symlink(inode), read_size);\n\t}\n\n\t/* Believe it or not, we actually aren't supposed to include the nul in the length. */\n\tif (read_size < size) {\n\t\tbuf[read_size] = '\\0';\n\t}\n\n\tfree(inode);\n\treturn read_size;\n}\n\nstatic int ioctl_ext2(fs_node_t * node, unsigned long request, void * argp) {\n\text2_fs_t * this = (ext2_fs_t *)node->device;\n\n\tswitch (request) {\n\t\tcase IOCTLSYNC:\n\t\t\treturn ioctl_fs(this->block_device, IOCTLSYNC, NULL);\n\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic int node_from_file(ext2_fs_t * this, ext2_inodetable_t *inode, ext2_dir_t *direntry,  fs_node_t *fnode) {\n\tif (!fnode) {\n\t\t/* You didn't give me a node to write into, go **** yourself */\n\t\treturn 0;\n\t}\n\t/* Information from the direntry */\n\tfnode->device = (void *)this;\n\tfnode->inode = direntry->inode;\n\tmemcpy(&fnode->name, &direntry->name, direntry->name_len);\n\tfnode->name[direntry->name_len] = '\\0';\n\t/* Information from the inode */\n\tfnode->uid = inode->uid;\n\tfnode->gid = inode->gid;\n\tfnode->length = inode->size;\n\tfnode->mask = inode->mode & 0xFFF;\n\tfnode->nlink = inode->links_count;\n\t/* File Flags */\n\tfnode->flags = 0;\n\tif ((inode->mode & EXT2_S_IFREG) == EXT2_S_IFREG) {\n\t\tfnode->flags   |= FS_FILE;\n\t\tfnode->read     = read_ext2;\n\t\tfnode->write    = write_ext2;\n\t\tfnode->truncate = truncate_ext2;\n\t\tfnode->create   = NULL;\n\t\tfnode->mkdir    = NULL;\n\t\tfnode->readdir  = NULL;\n\t\tfnode->finddir  = NULL;\n\t\tfnode->symlink  = NULL;\n\t\tfnode->readlink = NULL;\n\t}\n\tif ((inode->mode & EXT2_S_IFDIR) == EXT2_S_IFDIR) {\n\t\tfnode->flags   |= FS_DIRECTORY;\n\t\tfnode->create   = create_ext2;\n\t\tfnode->mkdir    = mkdir_ext2;\n\t\tfnode->unlink   = unlink_ext2;\n\t\tfnode->symlink  = symlink_ext2;\n\t\tfnode->readdir  = readdir_ext2;\n\t\tfnode->finddir  = finddir_ext2;\n\t\tfnode->write    = NULL;\n\t\tfnode->readlink = NULL;\n\t}\n\tif ((inode->mode & EXT2_S_IFBLK) == EXT2_S_IFBLK) {\n\t\tfnode->flags |= FS_BLOCKDEVICE;\n\t}\n\tif ((inode->mode & EXT2_S_IFCHR) == EXT2_S_IFCHR) {\n\t\tfnode->flags |= FS_CHARDEVICE;\n\t}\n\tif ((inode->mode & EXT2_S_IFIFO) == EXT2_S_IFIFO) {\n\t\tfnode->flags |= FS_PIPE;\n\t}\n\tif ((inode->mode & EXT2_S_IFLNK) == EXT2_S_IFLNK) {\n\t\tfnode->flags   |= FS_SYMLINK;\n\t\tfnode->read     = NULL;\n\t\tfnode->write    = NULL;\n\t\tfnode->create   = NULL;\n\t\tfnode->mkdir    = NULL;\n\t\tfnode->readdir  = NULL;\n\t\tfnode->finddir  = NULL;\n\t\tfnode->readlink = readlink_ext2;\n\t}\n\n\tfnode->atime   = inode->atime;\n\tfnode->mtime   = inode->mtime;\n\tfnode->ctime   = inode->ctime;\n\n\tfnode->chmod   = chmod_ext2;\n\tfnode->open    = open_ext2;\n\tfnode->close   = close_ext2;\n\tfnode->ioctl = ioctl_ext2;\n\treturn 1;\n}\n\nstatic int ext2_root(ext2_fs_t * this, ext2_inodetable_t *inode, fs_node_t *fnode) {\n\tif (!fnode) {\n\t\treturn 0;\n\t}\n\t/* Information for root dir */\n\tfnode->device = (void *)this;\n\tfnode->inode = 2;\n\tfnode->name[0] = '/';\n\tfnode->name[1] = '\\0';\n\t/* Information from the inode */\n\tfnode->uid = inode->uid;\n\tfnode->gid = inode->gid;\n\tfnode->length = inode->size;\n\tfnode->mask = inode->mode & 0xFFF;\n\tfnode->nlink = inode->links_count;\n\t/* File Flags */\n\tfnode->flags = 0;\n\tif ((inode->mode & EXT2_S_IFREG) == EXT2_S_IFREG) {\n\t\tdebug_print(CRITICAL, \"Root appears to be a regular file.\");\n\t\tdebug_print(CRITICAL, \"This is probably very, very wrong.\");\n\t\treturn 0;\n\t}\n\tif ((inode->mode & EXT2_S_IFDIR) == EXT2_S_IFDIR) {\n\t} else {\n\t\tdebug_print(CRITICAL, \"Root doesn't appear to be a directory.\");\n\t\tdebug_print(CRITICAL, \"This is probably very, very wrong.\");\n\n\t\tdebug_print(ERROR, \"Other useful information:\");\n\t\tdebug_print(ERROR, \"%d\", inode->uid);\n\t\tdebug_print(ERROR, \"%d\", inode->gid);\n\t\tdebug_print(ERROR, \"%d\", inode->size);\n\t\tdebug_print(ERROR, \"%d\", inode->mode);\n\t\tdebug_print(ERROR, \"%d\", inode->links_count);\n\n\t\treturn 0;\n\t}\n\tif ((inode->mode & EXT2_S_IFBLK) == EXT2_S_IFBLK) {\n\t\tfnode->flags |= FS_BLOCKDEVICE;\n\t}\n\tif ((inode->mode & EXT2_S_IFCHR) == EXT2_S_IFCHR) {\n\t\tfnode->flags |= FS_CHARDEVICE;\n\t}\n\tif ((inode->mode & EXT2_S_IFIFO) == EXT2_S_IFIFO) {\n\t\tfnode->flags |= FS_PIPE;\n\t}\n\tif ((inode->mode & EXT2_S_IFLNK) == EXT2_S_IFLNK) {\n\t\tfnode->flags |= FS_SYMLINK;\n\t}\n\n\tfnode->atime   = inode->atime;\n\tfnode->mtime   = inode->mtime;\n\tfnode->ctime   = inode->ctime;\n\n\tfnode->flags |= FS_DIRECTORY;\n\tfnode->read    = NULL;\n\tfnode->write   = NULL;\n\tfnode->chmod   = chmod_ext2;\n\tfnode->open    = open_ext2;\n\tfnode->close   = close_ext2;\n\tfnode->readdir = readdir_ext2;\n\tfnode->finddir = finddir_ext2;\n\tfnode->ioctl   = NULL;\n\tfnode->create  = create_ext2;\n\tfnode->mkdir   = mkdir_ext2;\n\tfnode->unlink  = unlink_ext2;\n\treturn 1;\n}\n\nstatic fs_node_t * mount_ext2(fs_node_t * block_device, int flags) {\n\n\text2_fs_t * this = malloc(sizeof(ext2_fs_t));\n\n\tmemset(this, 0x00, sizeof(ext2_fs_t));\n\n\tthis->flags = flags;\n\n\tthis->block_device = block_device;\n\tthis->block_size = 1024;\n\t/* We need to keep an owned refcount to this device if it was something we opened... */\n\t//vfs_lock(this->block_device);\n\n\tthis->mutex = mutex_init(\"ext2 fs\");\n\n\tSB = malloc(this->block_size);\n\n\tdebug_print(INFO, \"Reading superblock...\");\n\tread_block(this, 1, (uint8_t *)SB);\n\tif (SB->magic != EXT2_SUPER_MAGIC) {\n\t\tdebug_print(ERROR, \"... not an EXT2 filesystem? (magic didn't match, got 0x%x)\", SB->magic);\n\t\treturn NULL;\n\t}\n\tthis->inode_size = SB->inode_size;\n\tif (SB->inode_size == 0) {\n\t\tthis->inode_size = 128;\n\t}\n\tthis->block_size = 1024 << SB->log_block_size;\n\tthis->pointers_per_block = this->block_size / 4;\n\tdebug_print(INFO, \"Log block size = %d -> %d\", SB->log_block_size, this->block_size);\n\tBGDS = SB->blocks_count / SB->blocks_per_group;\n\tif (SB->blocks_per_group * BGDS < SB->blocks_count) {\n\t\tBGDS += 1;\n\t}\n\tthis->inodes_per_group = SB->inodes_count / BGDS;\n\n\t// load the block group descriptors\n\tthis->bgd_block_span = sizeof(ext2_bgdescriptor_t) * BGDS / this->block_size + 1;\n\tBGD = malloc(this->block_size * this->bgd_block_span);\n\n\tdebug_print(INFO, \"bgd_block_span = %d\", this->bgd_block_span);\n\n\tthis->bgd_offset = 2;\n\n\tif (this->block_size > 1024) {\n\t\tthis->bgd_offset = 1;\n\t}\n\n\tfor (int i = 0; i < this->bgd_block_span; ++i) {\n\t\tread_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i));\n\t}\n\n\tdprintf(\"ext2: %u BGDs, %u inodes, %u inodes per group\\n\",\n\t\tBGDS, SB->inodes_count, this->inodes_per_group);\n\n#if 1 // DEBUG_BLOCK_DESCRIPTORS\n\tchar * bg_buffer = malloc(this->block_size * sizeof(char));\n\tfor (uint32_t i = 0; i < BGDS; ++i) {\n\t\tdebug_print(INFO, \"Block Group Descriptor #%d @ %d\", i, this->bgd_offset + i * SB->blocks_per_group);\n\t\tdebug_print(INFO, \"\\tBlock Bitmap @ %d\", BGD[i].block_bitmap); {\n\t\t\tdebug_print(INFO, \"\\t\\tExamining block bitmap at %d\", BGD[i].block_bitmap);\n\t\t\tread_block(this, BGD[i].block_bitmap, (uint8_t *)bg_buffer);\n\t\t\tuint32_t j = 0;\n\t\t\twhile (BLOCKBIT(j)) {\n\t\t\t\t++j;\n\t\t\t}\n\t\t\tdebug_print(INFO, \"\\t\\tFirst free block in group is %d\", j + BGD[i].block_bitmap - 2);\n\t\t}\n\t\tdebug_print(INFO, \"\\tInode Bitmap @ %d\", BGD[i].inode_bitmap); {\n\t\t\tdebug_print(INFO, \"\\t\\tExamining inode bitmap at %d\", BGD[i].inode_bitmap);\n\t\t\tread_block(this, BGD[i].inode_bitmap, (uint8_t *)bg_buffer);\n\t\t\tuint32_t j = 0;\n\t\t\twhile (BLOCKBIT(j)) {\n\t\t\t\t++j;\n\t\t\t}\n\t\t\tdebug_print(INFO, \"\\t\\tFirst free inode in group is %d\", j + this->inodes_per_group * i + 1);\n\t\t}\n\t\tdebug_print(INFO, \"\\tInode Table  @ %d\", BGD[i].inode_table);\n\t\tdebug_print(INFO, \"\\tFree Blocks =  %d\", BGD[i].free_blocks_count);\n\t\tdebug_print(INFO, \"\\tFree Inodes =  %d\", BGD[i].free_inodes_count);\n\t}\n\tfree(bg_buffer);\n#endif\n\n\text2_inodetable_t *root_inode = read_inode(this, 2);\n\tRN = (fs_node_t *)malloc(sizeof(fs_node_t));\n\tif (!ext2_root(this, root_inode, RN)) {\n\t\treturn NULL;\n\t}\n\tdebug_print(NOTICE, \"Mounted EXT2 disk, root VFS node is at %#zx\", (uintptr_t)RN);\n\treturn RN;\n}\n\nfs_node_t * ext2_fs_mount(const char * device, const char * mount_path) {\n\tchar * arg = strdup(device);\n\tchar * argv[10];\n\tint argc = tokenize(arg, \",\", argv);\n\n\tfs_node_t * dev = kopen(argv[0], 0);\n\tif (!dev) {\n\t\treturn NULL;\n\t}\n\n\tint flags = 0;\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (!strcmp(argv[i],\"rw\")) {\n\t\t\tflags |= EXT2_FLAG_READWRITE;\n\t\t}\n\t\tif (!strcmp(argv[i],\"verbose\")) {\n\t\t\tflags |= EXT2_FLAG_LOUD;\n\t\t}\n\t}\n\n\treturn mount_ext2(dev, flags);\n}\n\nstatic int init(int argc, char * argv[]) {\n\tvfs_register(\"ext2\", ext2_fs_mount);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"ext2\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/hda.c",
    "content": "/**\n * @file kernel/audio/hda.c\n * @brief Driver for the Intel High Definition Audio.\n * @package x86_64\n *\n * @warning This is a stub driver.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/process.h>\n#include <kernel/mmu.h>\n#include <kernel/list.h>\n#include <kernel/module.h>\n#include <kernel/mod/snd.h>\n\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#if 0\nstatic snd_knob_t _knobs[] = {\n\t{\n\t\t\"Master\",\n\t\tSND_KNOB_MASTER\n\t}\n};\n\nstatic int hda_mixer_read(uint32_t knob_id, uint32_t *val);\nstatic int hda_mixer_write(uint32_t knob_id, uint32_t val);\n\n/**\n * TODO: We should generate this dynamically\n *       for each card based on the ports we find.\n */\nstatic snd_device_t _snd = {\n\t.name            = \"Intel HDA\",\n\t.device          = NULL,\n\t.playback_speed  = 48000,\n\t.playback_format = SND_FORMAT_L16SLE,\n\n\t.knobs     = _knobs,\n\t.num_knobs = 1,\n\n\t.mixer_read  = hda_mixer_read,\n\t.mixer_write = hda_mixer_write,\n};\n\nstatic int hda_mixer_read(uint32_t knob_id, uint32_t *val) {\n\treturn 0;\n}\n\nstatic int hda_mixer_write(uint32_t knob_id, uint32_t val) {\n\treturn 0;\n}\n#endif\n\nstatic void hda_setup(uint32_t device) {\n\t/* Map MMIO */\n\tuintptr_t mmio_addr = pci_read_field(device, PCI_BAR0, 4) & 0xFFFFFFFE;\n\tvoid* mapped_mmio = mmu_map_mmio_region(mmio_addr, 0x1000 * 8); /* TODO size? */\n\n\t/* Enable bus mastering, MMIO */\n\tpci_write_field(device, PCI_COMMAND, 2, 0x6);\n\n\t/* Enable controller */\n\t((volatile uint32_t*)mapped_mmio)[2] |= 1;\n\twhile (!(((volatile uint32_t*)mapped_mmio)[2]) & 0x1);\n\n\tprintf(\"hda: codec bitmap: %04x\\n\",\n\t\t((volatile uint16_t*)mapped_mmio)[7]);\n\n\t/* Stop DMA engine */\n\n\t/* Configure DMA engine */\n\n\t/* Map space for DMA */\n\n\t/* Set up ring buffer pointers */\n\n\t/* Then do the same for the DMA response ring buffer... ? */\n}\n\nstatic void find_hda(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tif (vendorid == 0x8086 && deviceid == 0x2668) {\n\t\thda_setup(device);\n\t}\n}\n\nstatic int hda_install(int argc, char * argv[]) {\n\tpci_scan(&find_hda, -1, NULL);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\t#if 0\n\tsnd_unregister(&_snd);\n\n\tfree(_device.bdl);\n\tfor (int i = 0; i < AC97_BDL_LEN; i++) {\n\t\tfree(_device.bufs[i]);\n\t}\n\t#endif\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"hda\",\n\t.init = hda_install,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/i965.c",
    "content": "/**\n * @file  modules/i965.c\n * @brief Bitbanged modeset driver for a ThinkPad T410's Intel graphics.\n * @package x86_64\n *\n * This is NOT a viable driver for Intel graphics devices. It assumes Vesa\n * has already properly set up the display pipeline with the needed timings\n * for the panel on one particular model of Lenovo ThinkPad and then sets\n * a handful of registers to get the framebuffer into the right resolution.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/printf.h>\n#include <kernel/types.h>\n#include <kernel/video.h>\n#include <kernel/pci.h>\n#include <kernel/mmu.h>\n#include <kernel/vfs.h>\n#include <kernel/args.h>\n#include <kernel/module.h>\n\n#define REG_PIPEASRC      0x6001C\n#define REG_PIPEACONF     0x70008\n#define  PIPEACONF_ENABLE (1 << 31)\n#define  PIPEACONF_STATE  (1 << 30)\n#define REG_DSPALINOFF    0x70184\n#define REG_DSPASTRIDE    0x70188\n#define REG_DSPASURF      0x7019c\n\nextern fs_node_t * lfb_device;\nextern int lfb_use_write_combining;\nstatic uintptr_t ctrl_regs = 0;\n\nstatic uint32_t i965_mmio_read(uint32_t reg) {\n\treturn *(volatile uint32_t*)(ctrl_regs + reg);\n}\n\nstatic void i965_mmio_write(uint32_t reg, uint32_t val) {\n\t*(volatile uint32_t*)(ctrl_regs + reg) = val;\n}\n\nstatic void split(uint32_t val, uint32_t * a, uint32_t * b) {\n\t*a = (val & 0xFFFF) + 1;\n\t*b = (val >> 16) + 1;\n}\n\nstatic void i965_modeset(uint16_t x, uint16_t y) {\n\t/* Disable pipe A while we update source size */\n\tuint32_t pipe = i965_mmio_read(REG_PIPEACONF);\n\ti965_mmio_write(REG_PIPEACONF, pipe & ~PIPEACONF_ENABLE);\n\twhile (i965_mmio_read(REG_PIPEACONF) & PIPEACONF_STATE);\n\n\t/* Set source size */\n\ti965_mmio_write(REG_PIPEASRC, ((x - 1) << 16) | (y - 1));\n\n\t/* Re-enable pipe */\n\tpipe = i965_mmio_read(REG_PIPEACONF);\n\ti965_mmio_write(REG_PIPEACONF, pipe | PIPEACONF_ENABLE);\n\twhile (!(i965_mmio_read(REG_PIPEACONF) & PIPEACONF_STATE));\n\n\t/* Keep the plane enabled while we update stride value */\n\ti965_mmio_write(REG_DSPALINOFF, 0);        /* offset to default of 0 */\n\ti965_mmio_write(REG_DSPASTRIDE, x * 4); /* stride to 4 x width */\n\ti965_mmio_write(REG_DSPASURF, 0);          /* write to surface address triggers change; use default of 0 */\n\n\t/* Update the values we expose to userspace. */\n\tlfb_resolution_x = x;\n\tlfb_resolution_y = y;\n\tlfb_resolution_b = 32;\n\tlfb_resolution_s = i965_mmio_read(REG_DSPASTRIDE);\n\tlfb_memsize = lfb_resolution_s * lfb_resolution_y;\n\tlfb_device->length  = lfb_memsize;\n}\n\nextern void fbterm_draw_logo(void);\nextern void fbterm_reset(void);\n\nstatic void setup_framebuffer(uint32_t pcidev) {\n\t/* Map BAR space for the control registers */\n\tuint32_t ctrl_space = pci_read_field(pcidev, PCI_BAR0, 4);\n\tpci_write_field(pcidev, PCI_BAR0, 4, 0xFFFFFFFF);\n\tuint32_t ctrl_size = pci_read_field(pcidev, PCI_BAR0, 4);\n\tctrl_size = ~(ctrl_size & -15) + 1;\n\tpci_write_field(pcidev, PCI_BAR0, 4, ctrl_space);\n\tctrl_space &= 0xFFFFFF00;\n\tctrl_regs = (uintptr_t)mmu_map_mmio_region(ctrl_space, ctrl_size);\n\n\tlfb_resolution_impl = i965_modeset;\n\tlfb_set_resolution(1440,900);\n\n\tlfb_use_write_combining = 1;\n\n\t/* Normally we don't clear the screen on mode set, but we should do it here */\n\tmemset(lfb_vid_memory, 0, lfb_memsize);\n\n\t/* Redraw the boot logo; if we were loaded by userspace, it'll probably\n\t * be overwritten pretty quickly by the compositor? But whatever... */\n\tfbterm_reset();\n\tfbterm_draw_logo();\n\n\t/* Helpful to know why the console text got cleared */\n\tdprintf(\"i965: video configured for %u x %u\\n\", lfb_resolution_x, lfb_resolution_y);\n}\n\nstatic void find_intel(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif (v == 0x8086 && d == 0x0046) {\n\t\tsetup_framebuffer(device);\n\t}\n}\n\nstatic int i965_install(int argc, char * argv[]) {\n\tif (args_present(\"noi965\")) return -ENODEV;\n\tif (!lfb_resolution_x) return -ENODEV;\n\tpci_scan(find_intel, -1, NULL);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"i965\",\n\t.init = i965_install,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/iso9660.c",
    "content": "/**\n * @file modules/iso9660.c\n * @brief ISO9660 \"High Sierra\" CD file system driver.\n * @package x86_64\n * @package aarch64\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2021 K. Lange\n */\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/module.h>\n#include <kernel/args.h>\n#include <kernel/tokenize.h>\n#include <kernel/time.h>\n#include <kernel/list.h>\n#include <kernel/hashmap.h>\n\n#define ISO_SECTOR_SIZE 2048\n\n#define FLAG_HIDDEN      0x01\n#define FLAG_DIRECTORY   0x02\n#define FLAG_ASSOCIATED  0x04\n#define FLAG_EXTENDED    0x08\n#define FLAG_PERMISSIONS 0x10\n#define FLAG_CONTINUES   0x80\n\ntypedef struct {\n\tfs_node_t * block_device;\n\tuint32_t block_size;\n\thashmap_t * cache;\n\tlist_t * lru;\n} iso_9660_fs_t;\n\ntypedef struct {\n\tchar year[4];\n\tchar month[2];\n\tchar day[2];\n\tchar hour[2];\n\tchar minute[2];\n\tchar second[2];\n\tchar hundredths[2];\n\tint8_t timezone;\n} __attribute__((packed)) iso_9660_datetime_t;\n\ntypedef struct {\n\tuint8_t year;\n\tuint8_t month;\n\tuint8_t day;\n\tuint8_t hour;\n\tuint8_t minute;\n\tuint8_t second;\n\tint8_t timezone;\n} __attribute__((packed)) iso_9660_rec_date_t;\n\ntypedef struct {\n\tuint8_t length;\n\tuint8_t ext_length;\n\n\tuint32_t extent_start_LSB;\n\tuint32_t extent_start_MSB;\n\n\tuint32_t extent_length_LSB;\n\tuint32_t extent_length_MSB;\n\n\tiso_9660_rec_date_t record_date;\n\n\tuint8_t flags;\n\tuint8_t interleave_units;\n\tuint8_t interleave_gap;\n\n\tuint16_t volume_seq_LSB;\n\tuint16_t volume_seq_MSB;\n\n\tuint8_t name_len;\n\tchar name[];\n} __attribute__((packed)) iso_9660_directory_entry_t;\n\ntypedef struct {\n\tuint8_t type; /* 0x01 */\n\tchar id[5]; /* CD001 */\n\n\tuint8_t version;\n\tuint8_t _unused0;\n\n\tchar system_id[32];\n\tchar volume_id[32];\n\n\tuint8_t _unused1[8];\n\n\tuint32_t volume_space_LSB;\n\tuint32_t volume_space_MSB;\n\n\tuint8_t _unused2[32];\n\n\tuint16_t volume_set_LSB;\n\tuint16_t volume_set_MSB;\n\n\tuint16_t volume_seq_LSB;\n\tuint16_t volume_seq_MSB;\n\n\tuint16_t logical_block_size_LSB;\n\tuint16_t logical_block_size_MSB;\n\n\tuint32_t path_table_size_LSB;\n\tuint32_t path_table_size_MSB;\n\n\tuint32_t path_table_LSB;\n\tuint32_t optional_path_table_LSB;\n\n\tuint32_t path_table_MSB;\n\tuint32_t optional_path_table_MSB;\n\n\t/* iso_9660_directory_entry_t */\n\tchar root[34];\n\n\tchar volume_set_id[128];\n\tchar volume_publisher[128];\n\tchar data_preparer[128];\n\tchar application_id[128];\n\n\tchar copyright_file[38];\n\tchar abstract_file[36];\n\tchar bibliographic_file[37];\n\n\tiso_9660_datetime_t creation;\n\tiso_9660_datetime_t modification;\n\tiso_9660_datetime_t expiration;\n\tiso_9660_datetime_t effective;\n\n\tuint8_t file_structure_version;\n\tuint8_t _unused_3;\n\n\tchar application_use[];\n} __attribute__((packed)) iso_9660_volume_descriptor_t;\n\nstatic void file_from_dir_entry(iso_9660_fs_t * this, size_t sector, iso_9660_directory_entry_t * dir, size_t offset, fs_node_t * fs);\n\n#define CACHE_SIZE 64\n\nstatic int read_sector(iso_9660_fs_t * this, uint32_t sector_id, char * buffer) {\n\tif (this->cache) {\n\t\tvoid * sector_id_v = (void *)(uintptr_t)sector_id;\n\t\tif (hashmap_has(this->cache, sector_id_v)) {\n\t\t\tmemcpy(buffer,hashmap_get(this->cache, sector_id_v), this->block_size);\n\t\t\tnode_t * me = list_find(this->lru, sector_id_v);\n\t\t\tlist_delete(this->lru, me);\n\t\t\tlist_append(this->lru, me);\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tif (this->lru->length > CACHE_SIZE) {\n\t\t\t\tnode_t * l = list_dequeue(this->lru);\n\t\t\t\tfree(hashmap_get(this->cache, l->value));\n\t\t\t\thashmap_remove(this->cache, l->value);\n\t\t\t\tfree(l);\n\t\t\t}\n\t\t\tint result = read_fs(this->block_device, sector_id * this->block_size, this->block_size, (uint8_t *)buffer);\n\t\t\tif (result < 0) return result;\n\t\t\tif (result == 0) return 1;\n\t\t\tchar * buf = malloc(this->block_size);\n\t\t\tmemcpy(buf, buffer, this->block_size);\n\t\t\thashmap_set(this->cache, sector_id_v, buf);\n\t\t\tlist_insert(this->lru, sector_id_v);\n\t\t\treturn 0;\n\t\t}\n\t} else {\n\t\tint result = read_fs(this->block_device, sector_id * this->block_size, this->block_size, (uint8_t *)buffer);\n\t\tif (result < 0) return result;\n\t\tif (result == 0) return 1;\n\t\treturn 0;\n\t}\n}\n\nstatic void inplace_lower(char * string) {\n\twhile (*string) {\n\t\tif (*string >= 'A' && *string <= 'Z') {\n\t\t\t*string += ('a' - 'A');\n\t\t}\n\t\tstring++;\n\t}\n}\n\nstatic void open_iso(fs_node_t *node, unsigned int flags) {\n\t/* Nothing to do here */\n}\n\nstatic void close_iso(fs_node_t *node) {\n\t/* Nothing to do here */\n}\n\nstatic struct dirent * readdir_iso(fs_node_t *node, unsigned long index) {\n\tif (index == 0) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \".\");\n\t\treturn out;\n\t}\n\n\tif (index == 1) {\n\t\tstruct dirent * out = malloc(sizeof(struct dirent));\n\t\tmemset(out, 0x00, sizeof(struct dirent));\n\t\tout->d_ino = 0;\n\t\tstrcpy(out->d_name, \"..\");\n\t\treturn out;\n\t}\n\n\tiso_9660_fs_t * this = node->device;\n\tchar * buffer = malloc(this->block_size);\n\tread_sector(this, node->inode, buffer);\n\tiso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl);\n\n\tuint8_t * root_data = malloc(root_entry->extent_length_LSB);\n\tuint8_t * offset = root_data;\n\tsize_t sector_offset = 0;\n\tsize_t length_to_read = root_entry->extent_length_LSB;\n\twhile (length_to_read) {\n\t\tread_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset);\n\t\tif (length_to_read >= this->block_size) {\n\t\t\toffset += this->block_size;\n\t\t\tsector_offset += 1;\n\t\t\tlength_to_read -= this->block_size;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Examine directory */\n\toffset = root_data;\n\n\tunsigned int i = 0;\n\tstruct dirent *dirent = malloc(sizeof(struct dirent));\n\tfs_node_t * out = malloc(sizeof(fs_node_t));\n\tmemset(dirent, 0, sizeof(struct dirent));\n\twhile (1) {\n\t\tiso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset;\n\t\tif (dir->length == 0) {\n\t\t\tif ((size_t)(offset - root_data) < root_entry->extent_length_LSB) {\n\t\t\t\toffset += 1; // this->block_size - ((uintptr_t)offset % this->block_size);\n\t\t\t\tgoto try_again;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (!(dir->flags & FLAG_HIDDEN)) {\n\t\t\tif (i == index) {\n\t\t\t\tfile_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out);\n\t\t\t\tmemcpy(&dirent->d_name, out->name, strlen(out->name)+1);\n\t\t\t\tdirent->d_ino = out->inode;\n\t\t\t\tgoto cleanup;\n\t\t\t}\n\t\t\ti += 1;\n\t\t}\n\t\toffset += dir->length;\ntry_again:\n\t\tif ((size_t)(offset - root_data) >= root_entry->extent_length_LSB) break;\n\t}\n\n\tfree(dirent);\n\tdirent = NULL;\n\ncleanup:\n\tfree(root_data);\n\tfree(buffer);\n\tfree(out);\n\treturn dirent;\n}\n\nstatic ssize_t read_iso(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tiso_9660_fs_t * this = node->device;\n\tchar * tmp = malloc(this->block_size);\n\tread_sector(this, node->inode, tmp);\n\tiso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(tmp + node->impl);\n\n\tuint32_t end;\n\t/* We can do this in a single underlying read to the filesystem */\n\tif (offset + size > root_entry->extent_length_LSB) {\n\t\tend = root_entry->extent_length_LSB;\n\t} else {\n\t\tend = offset + size;\n\t}\n\tuint32_t size_to_read = end - offset;\n\n\tread_fs(this->block_device, root_entry->extent_start_LSB * this->block_size + offset, size_to_read, (uint8_t *)buffer);\n\n\tfree(tmp);\n\treturn size_to_read;\n}\n\nstatic fs_node_t * finddir_iso(fs_node_t *node, char *name) {\n\tiso_9660_fs_t * this = node->device;\n\tchar * buffer = malloc(this->block_size);\n\tread_sector(this, node->inode, buffer);\n\tiso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl);\n\n\tuint8_t * root_data = malloc(root_entry->extent_length_LSB);\n\tuint8_t * offset = root_data;\n\tsize_t sector_offset = 0;\n\tsize_t length_to_read = root_entry->extent_length_LSB;\n\twhile (length_to_read) {\n\t\tread_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset);\n\t\tif (length_to_read >= this->block_size) {\n\t\t\toffset += this->block_size;\n\t\t\tsector_offset += 1;\n\t\t\tlength_to_read -= this->block_size;\n\t\t} else {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t/* Examine directory */\n\toffset = root_data;\n\n\tfs_node_t * out = malloc(sizeof(fs_node_t));\n\twhile (1) {\n\t\tiso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset;\n\t\tif (dir->length == 0) {\n\t\t\tif ((size_t)(offset - root_data) < root_entry->extent_length_LSB) {\n\t\t\t\toffset += 1; // this->block_size - ((uintptr_t)offset % this->block_size);\n\t\t\t\tgoto try_next_finddir;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (!(dir->flags & FLAG_HIDDEN)) {\n\t\t\tmemset(out, 0, sizeof(fs_node_t));\n\t\t\tfile_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out);\n\n\t\t\tif (!strcmp(out->name, name)) {\n\t\t\t\tgoto cleanup; /* found it */\n\t\t\t}\n\n\t\t}\n\t\toffset += dir->length;\ntry_next_finddir:\n\t\tif ((size_t)(offset - root_data) > root_entry->extent_length_LSB) break;\n\t}\n\n\tfree(out);\n\tout = NULL;\n\ncleanup:\n\tfree(root_data);\n\tfree(buffer);\n\treturn out;\n}\n\nstatic void file_from_dir_entry(iso_9660_fs_t * this, size_t sector, iso_9660_directory_entry_t * dir, size_t offset, fs_node_t * fs) {\n\tfs->device = this;\n\tfs->inode  = sector; /* Sector the file is in */\n\tfs->impl   = offset; /* Offset */\n\n\tchar * file_name = malloc(dir->name_len + 1);\n\tmemcpy(file_name, dir->name, dir->name_len);\n\tfile_name[dir->name_len] = 0;\n\tinplace_lower(file_name);\n\tchar * dot = strchr(file_name, '.');\n\tif (!dot) {\n\t\t/* It's a directory. */\n\t} else {\n\t\tchar * ext = dot + 1;\n\t\tchar * semi = strchr(ext, ';');\n\t\tif (semi) {\n\t\t\t*semi = 0;\n\t\t}\n\t\tif (strlen(ext) == 0) {\n\t\t\t*dot = 0;\n\t\t} else {\n\t\t\tchar * derp = ext;\n\t\t\twhile (*derp == '.') derp++;\n\t\t\tif (derp != ext) {\n\t\t\t\tmemmove(ext, derp, strlen(derp)+1);\n\t\t\t}\n\t\t}\n\t}\n\tmemcpy(fs->name, file_name, strlen(file_name)+1);\n\tfree(file_name);\n\n\tfs->uid = 0;\n\tfs->gid = 0;\n\tfs->length = dir->extent_length_LSB;\n\tfs->mask = 0555;\n\tfs->nlink = 0; /* Unsupported */\n\tif (dir->flags & FLAG_DIRECTORY) {\n\t\tfs->flags = FS_DIRECTORY;\n\t\tfs->readdir = readdir_iso;\n\t\tfs->finddir = finddir_iso;\n\t} else {\n\t\tfs->flags = FS_FILE;\n\t\tfs->read = read_iso;\n\t}\n\t/* Other things not supported */\n\t/* TODO actually get these from the CD into Unix time */\n\tfs->atime = now();\n\tfs->mtime = now();\n\tfs->ctime = now();\n\n\tfs->open = open_iso;\n\tfs->close = close_iso;\n}\n\nstatic fs_node_t * iso_fs_mount(const char * device, const char * mount_path) {\n\tchar * arg = strdup(device);\n\tchar * argv[10];\n\tint argc = tokenize(arg, \",\", argv);\n\n\tfs_node_t * dev = kopen(argv[0], 0);\n\tif (!dev) {\n\t\treturn NULL;\n\t}\n\n\tint cache = 1;\n\n\tfor (int i = 1; i < argc; ++i) {\n\t\tif (!strcmp(argv[i],\"nocache\")) {\n\t\t\tcache = 0;\n\t\t}\n\t}\n\n\tif (!dev) {\n\t\tfree(arg);\n\t\treturn NULL;\n\t}\n\n\tiso_9660_fs_t * this = malloc(sizeof(iso_9660_fs_t));\n\tthis->block_device = dev;\n\tthis->block_size = ISO_SECTOR_SIZE;\n\tif (cache) {\n\t\tthis->cache = hashmap_create_int(10);\n\t\tthis->lru = list_create(\"iso9660 lru cache\", this);\n\t} else {\n\t\tthis->cache = NULL;\n\t}\n\n\t/* Probably want to put a block cache on this like EXT2 driver does; or do that in the ATAPI layer... */\n\n\t/* Read the volume descriptors */\n\tuint8_t * tmp = malloc(ISO_SECTOR_SIZE);\n\tint i = 0x10;\n\tint found = 0;\n\twhile (1) {\n\t\tif (read_sector(this,i,(char*)tmp)) break;\n\t\tif (tmp[0] == 0x00) {\n\t\t\t//debug_print(WARNING, \" Boot Record\");\n\t\t} else if (tmp[0] == 0x01) {\n\t\t\t//debug_print(WARNING, \" Primary Volume Descriptor\");\n\t\t\tfound = 1;\n\t\t\tbreak;\n\t\t} else if (tmp[0] == 0x02) {\n\t\t\t//debug_print(WARNING, \" Secondary Volume Descriptor\");\n\t\t} else if (tmp[0] == 0x03) {\n\t\t\t//debug_print(WARNING, \" Volume Partition Descriptor\");\n\t\t}\n\t\tif (tmp[0] == 0xFF) break;\n\t\ti++;\n\t}\n\n\tif (!found) {\n\t\t//debug_print(WARNING, \"No primary volume descriptor?\");\n\t\tfree(arg);\n\t\treturn NULL;\n\t}\n\n\tiso_9660_volume_descriptor_t * root = (iso_9660_volume_descriptor_t *)tmp;\n\tiso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)&root->root;\n\n\tfs_node_t * fs = malloc(sizeof(fs_node_t));\n\tmemset(fs, 0, sizeof(fs_node_t));\n\tfile_from_dir_entry(this, i, root_entry, 156, fs);\n\n\tfree(arg);\n\treturn fs;\n}\n\nstatic int init(int argc, char * argv[]) {\n\tvfs_register(\"iso\", iso_fs_mount);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"iso9660\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/pcspkr.c",
    "content": "/**\n * @file modules/pcspkr.c\n * @brief PC beeper device interface\n * @package x86_64\n *\n * Use with @ref apps/beep.c to play music.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2014-2021 K. Lange\n */\n#include <kernel/module.h>\n#include <kernel/printf.h>\n#include <kernel/process.h>\n#include <kernel/time.h>\n#include <kernel/vfs.h>\n\n#include <kernel/arch/x86_64/ports.h>\n\nstatic void note(int length, int freq) {\n\n\tuint8_t  t;\n\n\tif (length == 0) {\n\t\tt = inportb(0x61) & 0xFC;\n\t\toutportb(0x61, t);\n\t\treturn;\n\t}\n\n\tuint32_t div = 11931800 / freq;\n\n\toutportb(0x43, 0xb6);\n\toutportb(0x42, (uint8_t)(div));\n\toutportb(0x42, (uint8_t)(div >> 8));\n\n\tt = inportb(0x61);\n\toutportb(0x61, t | 0x3);\n\n\tif (length > 0) {\n\t\tunsigned long s, ss;\n\t\trelative_time(length / 1000, (length % 1000) * 1000, &s, &ss);\n\t\tsleep_until((process_t*)this_core->current_process, s, ss);\n\t\tswitch_task(0);\n\n\t\tt = inportb(0x61) & 0xFC;\n\t\toutportb(0x61, t);\n\t}\n\n}\n\nstruct spkr {\n\tint length;\n\tint frequency;\n};\n\nstatic ssize_t write_spkr(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\tif (!size % (sizeof(struct spkr))) {\n\t\treturn 0;\n\t}\n\n\tstruct spkr * s = (struct spkr *)buffer;\n\twhile ((uintptr_t)s < (uintptr_t)buffer + size) {\n\t\tnote(s->length, s->frequency);\n\t\ts++;\n\t}\n\n\treturn (uintptr_t)s - (uintptr_t)buffer;\n}\n\nstatic fs_node_t * spkr_device_create(void) {\n\tfs_node_t * fnode = malloc(sizeof(fs_node_t));\n\tmemset(fnode, 0x00, sizeof(fs_node_t));\n\tsnprintf(fnode->name, 5, \"spkr\");\n\tfnode->mask    = 0660; /* TODO need a speaker group */\n\tfnode->gid     = 1;\n\tfnode->flags   = FS_CHARDEVICE;\n\tfnode->write   = write_spkr;\n\treturn fnode;\n}\n\nstatic int init(int argc, char * argv[]) {\n\tfs_node_t * node = spkr_device_create();\n\tvfs_mount(\"/dev/spkr\", node, \"pcspkr\", \"\");\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"pcspkr\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/piix4.c",
    "content": "/**\n * @file modules/piix4.c\n * @brief Intel PIIX4 ISA Bridge Driver\n * @package x86_64\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/args.h>\n#include <kernel/printf.h>\n#include <kernel/pci.h>\n#include <kernel/module.h>\n\n#define PIIX4_PCI_PIRQRC  0x60\n\nstatic void find_isa_bridge(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tif (vendorid == 0x8086 && (deviceid == 0x7000 || deviceid == 0x7110)) {\n\t\t*((uint32_t *)extra) = device;\n\t}\n}\n\nstatic uint32_t pci_isa = 0;\nstatic int pci_isa_base_slot = 0;\nstatic int pci_isa_bus_offset = 0;\nstatic int pci_isa_last_bus = 0;\nstatic uint8_t pci_remaps[4] = {0};\n\nstatic void piix_remap(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) {\n\tuint32_t irq_pin = pci_read_field(device, PCI_INTERRUPT_PIN, 1);\n\tuint32_t irq_line = pci_read_field(device, PCI_INTERRUPT_LINE, 1);\n\tif (irq_pin == 0) return;\n\tif (pci_extract_bus(device) != pci_isa_last_bus) {\n\t\tpci_isa_bus_offset++;\n\t\tpci_isa_last_bus = pci_extract_bus(device);\n\t}\n\t/* Calculate PIRQ from pin and device slot. */\n\tint slot = (pci_extract_slot(device) - pci_isa_base_slot) % 4;\n\tint bus  = (pci_isa_bus_offset) % 4;\n\tint pirq = (slot + irq_pin + bus - 1) % 4;\n\tif (irq_line < 32 && irq_line != pci_remaps[pirq]) {\n\t\tpci_write_field(device, PCI_INTERRUPT_LINE, 1, pci_remaps[pirq]);\n\t}\n}\n\nstatic int init(int argc, char * argv[]) {\n\tif (args_present(\"nopciremap\")) return -ENODEV;\n\n\tpci_scan(&find_isa_bridge, -1, &pci_isa);\n\tif (!pci_isa) {\n\t\treturn -ENODEV;\n\t}\n\n\tpci_isa_base_slot = pci_extract_slot(pci_isa);\n\tpci_isa_last_bus = pci_extract_bus(pci_isa);\n\n\tfor (int i = 0; i < 4; ++i) {\n\t\tpci_remaps[i] = pci_read_field(pci_isa, PIIX4_PCI_PIRQRC + i, 1);\n\t}\n\n\tuint32_t out = 0;\n\tmemcpy(&out, &pci_remaps, 4);\n\tpci_write_field(pci_isa, PIIX4_PCI_PIRQRC, 4, out);\n\tpci_scan(piix_remap, -1, NULL);\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"piix4\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/test.c",
    "content": "/**\n * @file modules/test.c\n * @brief Test module.\n * @package x86_64\n * @package aarch64\n *\n * Load with various arguments to do things like crash the\n * kernel or print tracebacks.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <kernel/printf.h>\n#include <kernel/module.h>\n#include <kernel/assert.h>\n#include <kernel/misc.h>\n\nstatic int init(int argc, char * argv[]) {\n\tdprintf(\"Hello, modules.\\n\");\n\tdprintf(\"Received %d arguments.\\n\", argc);\n\n\tif (argc > 1 && !strcmp(argv[1], \"--traceback\")) {\n\t\tarch_dump_traceback();\n\t} else if (argc > 1 && !strcmp(argv[1], \"--fail\")) {\n\t\treturn 1;\n\t} else if (argc > 1 && !strcmp(argv[1], \"--crash\")) {\n\t\t*(volatile int*)0x60000000 = 42;\n\t} else if (argc > 1 && !strcmp(argv[1], \"--assert\")) {\n\t\tassert(0 && \"Intentional failure.\");\n\t}\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"test\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/vbox.c",
    "content": "/**\n * @file  kernel/arch/x86_64/vbox.c\n * @brief VirtualBox Guest Additions driver\n * @package x86_64\n *\n * Implements the following features:\n * - Absolute mouse cursor positioning\n * - \"Hardware\" cursor sprites\n * - Automatic display modesetting\n * - \"Seamless\" mode rectangle device\n * - Log device\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2016-2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/types.h>\n#include <kernel/vfs.h>\n#include <kernel/printf.h>\n#include <kernel/string.h>\n#include <kernel/pci.h>\n#include <kernel/video.h>\n#include <kernel/pipe.h>\n#include <kernel/mouse.h>\n#include <kernel/args.h>\n#include <kernel/module.h>\n#include <kernel/mmu.h>\n\n#include <kernel/arch/x86_64/regs.h>\n#include <kernel/arch/x86_64/ports.h>\n#include <kernel/arch/x86_64/irq.h>\n\n#define VBOX_VENDOR_ID 0x80EE\n#define VBOX_DEVICE_ID 0xCAFE\n\nstatic void vbox_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif (v == VBOX_VENDOR_ID && d == VBOX_DEVICE_ID) {\n\t\t*((uint32_t *)extra) = device;\n\t}\n}\n\n#define VMM_GetMouseState 1\n#define VMM_SetMouseState 2\n#define VMM_SetPointerShape 3\n#define VMM_AcknowledgeEvents 41\n#define VMM_ReportGuestInfo 50\n#define VMM_GetDisplayChangeRequest 51\n#define VMM_ReportGuestCapabilities 55\n#define VMM_VideoSetVisibleRegion 72\n\n#define VMMCAP_SeamlessMode (1 << 0)\n#define VMMCAP_HostWindows (1 << 1)\n#define VMMCAP_Graphics (1 << 2)\n\n#define VMMDEV_VERSION 0x00010003\n#define VBOX_REQUEST_HEADER_VERSION 0x10001\nstruct vbox_header {\n\tuint32_t size;\n\tuint32_t version;\n\tuint32_t requestType;\n\tint32_t  rc;\n\tuint32_t reserved1;\n\tuint32_t reserved2;\n};\n\nstruct vbox_guest_info {\n\tstruct vbox_header header;\n\tuint32_t version;\n\tuint32_t ostype;\n};\n\nstruct vbox_guest_caps {\n\tstruct vbox_header header;\n\tuint32_t caps;\n};\n\nstruct vbox_ack_events {\n\tstruct vbox_header header;\n\tuint32_t events;\n};\n\nstruct vbox_display_change {\n\tstruct vbox_header header;\n\tuint32_t xres;\n\tuint32_t yres;\n\tuint32_t bpp;\n\tuint32_t eventack;\n};\n\nstruct vbox_mouse {\n\tstruct vbox_header header;\n\tuint32_t features;\n\tint32_t x;\n\tint32_t y;\n};\n\nstruct vbox_rtrect {\n\tint32_t xLeft;\n\tint32_t yTop;\n\tint32_t xRight;\n\tint32_t yBottom;\n};\n\nstruct vbox_visibleregion {\n\tstruct vbox_header header;\n\tuint32_t count;\n\tstruct vbox_rtrect rect[1];\n};\n\nstruct vbox_pointershape {\n\tstruct vbox_header header;\n\tuint32_t flags;\n\tuint32_t xHot;\n\tuint32_t yHot;\n\tuint32_t width;\n\tuint32_t height;\n\tunsigned char data[];\n};\n\n\n#define EARLY_LOG_DEVICE 0x504\nstatic size_t _vbox_write(size_t size, uint8_t * buffer) {\n\tfor (unsigned int i = 0; i < size; ++i) {\n\t\toutportb(EARLY_LOG_DEVICE, buffer[i]);\n\t}\n\treturn size;\n}\n\nvoid vbox_set_log(void) {\n\tprintf_output = &_vbox_write;\n\tprintf(\"Hello world, using VBox machine log for kernel output\\n\");\n}\n\nstatic uint32_t vbox_device = 0;\nstatic uint32_t vbox_port = 0x0;\nstatic int vbox_irq = 0;\n\nstatic struct vbox_ack_events * vbox_irq_ack;\nstatic uint32_t vbox_phys_ack;\nstatic struct vbox_display_change * vbox_disp;\nstatic uint32_t vbox_phys_disp;\nstatic struct vbox_mouse * vbox_m;\nstatic uint32_t vbox_phys_mouse;\nstatic struct vbox_mouse * vbox_mg;\nstatic uint32_t vbox_phys_mouse_get;\nstatic struct vbox_visibleregion * vbox_visibleregion;\nstatic uint32_t vbox_phys_visibleregion;\nstatic struct vbox_pointershape * vbox_pointershape = NULL;\nstatic uint32_t vbox_phys_pointershape;\n\nstatic volatile uint32_t * vbox_vmmdev = 0;\n\nstatic fs_node_t * mouse_pipe;\nstatic fs_node_t * rect_pipe;\nstatic fs_node_t * pointer_pipe;\n\nstatic int mouse_state;\n\n#define PACKETS_IN_PIPE 1024\n#define DISCARD_POINT 32\n\n#define VMM_Event_DisplayChange (1 << 2)\nstatic void vbox_do_modeset(void) {\n\toutportl(vbox_port, vbox_phys_disp);\n\toutportl(vbox_port, vbox_phys_disp);\n\tif (lfb_resolution_x && vbox_disp->xres && (vbox_disp->xres != lfb_resolution_x  || vbox_disp->yres != lfb_resolution_y)) {\n\t\tlfb_set_resolution(vbox_disp->xres, vbox_disp->yres);\n\t}\n}\n\n#define VMM_Event_Mouse (1 << 9)\nstatic void vbox_do_mouse(void) {\n\toutportl(vbox_port, vbox_phys_mouse_get);\n\tunsigned int x, y;\n\n\tif (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y && vbox_mg->x && vbox_mg->y) {\n\t\tx = ((unsigned int)vbox_mg->x * lfb_resolution_x) / 0xFFFF;\n\t\ty = ((unsigned int)vbox_mg->y * lfb_resolution_y) / 0xFFFF;\n\t} else {\n\t\tx = vbox_mg->x;\n\t\ty = vbox_mg->y;\n\t}\n\n\tmouse_device_packet_t packet;\n\tpacket.magic = MOUSE_MAGIC;\n\tpacket.x_difference = x;\n\tpacket.y_difference = y;\n\tpacket.buttons = 0;\n\n\tmouse_device_packet_t bitbucket;\n\twhile (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {\n\t\tread_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);\n\t}\n\twrite_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);\n}\n\nstatic int vbox_irq_handler(struct regs *r) {\n\tif (!vbox_vmmdev[2]) return 0;\n\n\tuint32_t events;\n\n\tevents = vbox_irq_ack->events = vbox_vmmdev[2];\n\toutportl(vbox_port, vbox_phys_ack);\n\tirq_ack(vbox_irq);\n\n\tif (events & VMM_Event_Mouse) vbox_do_mouse();\n\tif (events & VMM_Event_DisplayChange) vbox_do_modeset();\n\n\treturn 1;\n}\n\n#define VBOX_MOUSE_ON (1 << 0) | (1 << 4)\n#define VBOX_MOUSE_OFF (0)\n\nstatic void mouse_on_off(unsigned int status) {\n\tmouse_state = status;\n\n\tvbox_m->header.size = sizeof(struct vbox_mouse);\n\tvbox_m->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tvbox_m->header.requestType = VMM_SetMouseState;\n\tvbox_m->header.rc = 0;\n\tvbox_m->header.reserved1 = 0;\n\tvbox_m->header.reserved2 = 0;\n\tvbox_m->features = status;\n\tvbox_m->x = 0;\n\tvbox_m->y = 0;\n\toutportl(vbox_port, vbox_phys_mouse);\n}\n\nstatic int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) {\n\tif (request == 1) {\n\t\t/* Disable */\n\t\tmouse_on_off(VBOX_MOUSE_OFF);\n\t\treturn 0;\n\t}\n\tif (request == 2) {\n\t\t/* Enable */\n\t\tmouse_on_off(VBOX_MOUSE_ON);\n\t\treturn 0;\n\t}\n\tif (request == 3) {\n\t\treturn mouse_state == (VBOX_MOUSE_ON);\n\t}\n\treturn -1;\n}\n\nstatic ssize_t write_pointer(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) {\n\tif (!mouse_state) {\n\t\treturn -1;\n\t}\n\n\tmemcpy(&vbox_pointershape->data[288], buffer, 48*48*4);\n\toutportl(vbox_port, vbox_phys_pointershape);\n\n\treturn size;\n}\n\nstatic ssize_t write_rectpipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) {\n\t(void)node;\n\t(void)offset;\n\n\t/* TODO should check size */\n\n\t/* This is kinda special and always assumes everything was written at once. */\n\tuint32_t count = ((uint32_t *)buffer)[0];\n\tvbox_visibleregion->count = count;\n\n\tif (count > 254) count = 254; /* enforce maximum */\n\n\tbuffer += sizeof(uint32_t);\n\n\tfor (unsigned int i = 0; i < count; ++i) {\n\t\tmemcpy(&vbox_visibleregion->rect[i], buffer, sizeof(struct vbox_rtrect));\n\t\tbuffer += sizeof(struct vbox_rtrect);\n\t}\n\n\tvbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * count * 4;\n\toutportl(vbox_port, vbox_phys_visibleregion);\n\n\treturn size;\n}\n\nstatic void * kvmalloc_p(size_t size, uint32_t * outphys) {\n\tuintptr_t index = mmu_allocate_n_frames(size / 0x1000) << 12;\n\t*outphys = index;\n\treturn mmu_map_from_physical(index);\n}\n\nstatic int vbox_install(int argc, char * argv[]) {\n\tif (args_present(\"novbox\")) return -ENODEV;\n\tpci_scan(vbox_scan_pci, -1, &vbox_device);\n\tif (!vbox_device) return -ENODEV;\n\n\tif (!args_present(\"novboxdebug\")) {\n\t\tvbox_set_log();\n\t}\n\n\tuintptr_t t = pci_read_field(vbox_device, PCI_BAR0, 4);\n\tif (t > 0) {\n\t\tvbox_port = (t & 0xFFFFFFF0);\n\t}\n\n\tuint16_t c = pci_read_field(vbox_device, PCI_COMMAND, 2);\n\t//fprintf(&vb, \"Command register: 0x%4x\\n\", c);\n\tif (!!(c & (1 << 10))) {\n\t\t//fprintf(&vb, \"Interrupts are disabled\\n\");\n\t}\n\n\n\tmouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE);\n\tmouse_pipe->flags = FS_CHARDEVICE;\n\tmouse_pipe->ioctl = ioctl_mouse;\n\n\tvfs_mount(\"/dev/absmouse\", mouse_pipe, \"vbox-tablet\", \"\");\n\n\tvbox_irq = pci_get_interrupt(vbox_device);\n\t//fprintf(&vb, \"irq line is %d\\n\", vbox_irq);\n\tirq_install_handler(vbox_irq, vbox_irq_handler, \"vbox\");\n\n\tuint32_t vbox_phys = 0;\n\tstruct vbox_guest_info * packet = (void*)kvmalloc_p(0x1000, &vbox_phys);\n\tpacket->header.size = sizeof(struct vbox_guest_info);\n\tpacket->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tpacket->header.requestType = VMM_ReportGuestInfo;\n\tpacket->header.rc = 0;\n\tpacket->header.reserved1 = 0;\n\tpacket->header.reserved2 = 0;\n\tpacket->version = VMMDEV_VERSION;\n\tpacket->ostype = 0x00100; /* Unknown, x86-64 */\n\n\toutportl(vbox_port, vbox_phys);\n\n\tstruct vbox_guest_caps * caps = (void*)kvmalloc_p(0x1000, &vbox_phys);\n\tcaps->header.size = sizeof(struct vbox_guest_caps);\n\tcaps->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tcaps->header.requestType = VMM_ReportGuestCapabilities;\n\tcaps->header.rc = 0;\n\tcaps->header.reserved1 = 0;\n\tcaps->header.reserved2 = 0;\n\tcaps->caps = VMMCAP_Graphics | (args_present(\"novboxseamless\") ? 0 : VMMCAP_SeamlessMode);\n\toutportl(vbox_port, vbox_phys);\n\n\tvbox_irq_ack = (void*)kvmalloc_p(0x1000, &vbox_phys_ack);\n\tvbox_irq_ack->header.size = sizeof(struct vbox_ack_events);\n\tvbox_irq_ack->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tvbox_irq_ack->header.requestType = VMM_AcknowledgeEvents;\n\tvbox_irq_ack->header.rc = 0;\n\tvbox_irq_ack->header.reserved1 = 0;\n\tvbox_irq_ack->header.reserved2 = 0;\n\tvbox_irq_ack->events = 0;\n\n\n\tvbox_disp = (void*)kvmalloc_p(0x1000, &vbox_phys_disp);\n\tvbox_disp->header.size = sizeof(struct vbox_display_change);\n\tvbox_disp->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tvbox_disp->header.requestType = VMM_GetDisplayChangeRequest;\n\tvbox_disp->header.rc = 0;\n\tvbox_disp->header.reserved1 = 0;\n\tvbox_disp->header.reserved2 = 0;\n\tvbox_disp->xres = 0;\n\tvbox_disp->yres = 0;\n\tvbox_disp->bpp = 0;\n\tvbox_disp->eventack = 1;\n\n\tvbox_m = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse);\n\tmouse_on_off(VBOX_MOUSE_ON);\n\n\t/* For use with later receives */\n\tvbox_mg = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse_get);\n\tvbox_mg->header.size = sizeof(struct vbox_mouse);\n\tvbox_mg->header.version = VBOX_REQUEST_HEADER_VERSION;\n\tvbox_mg->header.requestType = VMM_GetMouseState;\n\tvbox_mg->header.rc = 0;\n\tvbox_mg->header.reserved1 = 0;\n\tvbox_mg->header.reserved2 = 0;\n\n\tif (!args_present(\"novboxpointer\")) {\n\t\tvbox_pointershape = (void*)kvmalloc_p(0x4000, &vbox_phys_pointershape);\n\n\t\tif (vbox_pointershape) {\n\t\t\t//fprintf(&vb, \"Got a valid set of pages to load up a cursor.\\n\");\n\t\t\tvbox_pointershape->header.version = VBOX_REQUEST_HEADER_VERSION;\n\t\t\tvbox_pointershape->header.requestType = VMM_SetPointerShape;\n\t\t\tvbox_pointershape->header.rc = 0;\n\t\t\tvbox_pointershape->header.reserved1 = 0;\n\t\t\tvbox_pointershape->header.reserved2 = 0;\n\t\t\tvbox_pointershape->flags = (1 << 0) | (1 << 1) | (1 << 2); /* visible, alpha, shape */\n\t\t\tvbox_pointershape->xHot = 26;\n\t\t\tvbox_pointershape->yHot = 26;\n\t\t\tvbox_pointershape->width = 48;\n\t\t\tvbox_pointershape->height = 48;\n\n\t\t\tunsigned int mask_bytes = ((vbox_pointershape->width + 7) / 8) * vbox_pointershape->height;\n\n\t\t\tfor (uint32_t i = 0; i < mask_bytes; ++i) {\n\t\t\t\tvbox_pointershape->data[i] = 0x00;\n\t\t\t}\n\n\t\t\twhile (mask_bytes & 3) {\n\t\t\t\tmask_bytes++;\n\t\t\t}\n\t\t\tint base = mask_bytes;\n\t\t\t//fprintf(&vb, \"mask_bytes = %d\\n\", mask_bytes);\n\n\t\t\tvbox_pointershape->header.size = sizeof(struct vbox_pointershape) + (48*48*4)+mask_bytes; /* update later */\n\n\t\t\tfor (int i = 0; i < 48 * 48; ++i) {\n\t\t\t\tvbox_pointershape->data[base+i*4] = 0x00; /* blue */\n\t\t\t\tvbox_pointershape->data[base+i*4+1] = 0x00; /* red */\n\t\t\t\tvbox_pointershape->data[base+i*4+2] = 0x00; /* green */\n\t\t\t\tvbox_pointershape->data[base+i*4+3] = 0x00; /* alpha */\n\t\t\t}\n\t\t\toutportl(vbox_port, vbox_phys_pointershape);\n\n\t\t\tif (vbox_pointershape->header.rc < 0) {\n\t\t\t\t//fprintf(&vb, \"Bad response code: -%d\\n\", -vbox_pointershape->header.rc);\n\t\t\t} else {\n\t\t\t\t/* Success, let's install the device file */\n\t\t\t\t//fprintf(&vb, \"Successfully initialized cursor, going to allow compositor to set it.\\n\");\n\t\t\t\tpointer_pipe = malloc(sizeof(fs_node_t));\n\t\t\t\tmemset(pointer_pipe, 0, sizeof(fs_node_t));\n\t\t\t\tpointer_pipe->mask = 0666;\n\t\t\t\tpointer_pipe->flags = FS_CHARDEVICE;\n\t\t\t\tpointer_pipe->write = write_pointer;\n\n\t\t\t\tvfs_mount(\"/dev/vboxpointer\", pointer_pipe, \"vbox-pointer\", \"\");\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!args_present(\"novboxseamless\")) {\n\t\tvbox_visibleregion = (void*)kvmalloc_p(0x1000, &vbox_phys_visibleregion);\n\t\tvbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * 4; /* TODO + more for additional rects? */\n\t\tvbox_visibleregion->header.version = VBOX_REQUEST_HEADER_VERSION;\n\t\tvbox_visibleregion->header.requestType = VMM_VideoSetVisibleRegion;\n\t\tvbox_visibleregion->header.rc = 0;\n\t\tvbox_visibleregion->header.reserved1 = 0;\n\t\tvbox_visibleregion->header.reserved2 = 0;\n\t\tvbox_visibleregion->count = 1;\n\t\tvbox_visibleregion->rect[0].xLeft = 0;\n\t\tvbox_visibleregion->rect[0].yTop = 0;\n\t\tvbox_visibleregion->rect[0].xRight = 1440;\n\t\tvbox_visibleregion->rect[0].yBottom = 900;\n\t\toutportl(vbox_port, vbox_phys_visibleregion);\n\n\t\trect_pipe = malloc(sizeof(fs_node_t));\n\t\tmemset(rect_pipe, 0, sizeof(fs_node_t));\n\t\trect_pipe->mask = 0666;\n\t\trect_pipe->flags = FS_CHARDEVICE;\n\t\trect_pipe->write = write_rectpipe;\n\n\t\tvfs_mount(\"/dev/vboxrects\", rect_pipe, \"vbox-rects\", \"\");\n\t}\n\n\t/* device memory region mapping? */\n\t{\n\t\tuintptr_t t = pci_read_field(vbox_device, PCI_BAR1, 4);\n\t\t//fprintf(&vb, \"mapping vmm_dev = 0x%x\\n\", t);\n\t\tif (t > 0) {\n\t\t\tvbox_vmmdev =  mmu_map_from_physical(t & 0xFFFFFFF0);\n\t\t\tprintf(\"Setting vbox mem device at %p\\n\", (void*)vbox_vmmdev);\n\t\t}\n\t}\n\n\t/* Try a mode set */\n\tvbox_do_modeset();\n\n\tvbox_vmmdev[3] = 0xFFFFFFFF; /* Enable all for now */\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"vbox\",\n\t.init = vbox_install,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/vmware.c",
    "content": "/**\n * @file  kernel/arch/x86_64/vmware.c\n * @brief VMware/QEMU mouse and VMWare backdoor driver.\n * @package x86_64\n *\n * Supports absolute mouse cursor and resolution setting.\n *\n * Mouse:\n *   Toggle off / on with ioctl 1 and 2 respectively to /dev/vmmouse.\n *   Supports mouse buttons, unlike the one in VirtualBox.\n *   This device is also available by default in QEMU.\n *\n * Resolution setting:\n *   Enabled when the \"vmware\" LFB driver is active. Automatically\n *   resizes the display when the window size changes.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2017-2021 K. Lange\n */\n\n#include <errno.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <kernel/vfs.h>\n#include <kernel/string.h>\n#include <kernel/printf.h>\n#include <kernel/types.h>\n#include <kernel/video.h>\n#include <kernel/pipe.h>\n#include <kernel/process.h>\n#include <kernel/mouse.h>\n#include <kernel/time.h>\n#include <kernel/args.h>\n#include <kernel/module.h>\n\n#include <kernel/arch/x86_64/ports.h>\n\n#define VMWARE_MAGIC  0x564D5868 /* hXMV */\n#define VMWARE_PORT   0x5658\n#define VMWARE_PORTHB 0x5659\n\n#define PACKETS_IN_PIPE 1024\n#define DISCARD_POINT 32\n\n#define CMD_GETVERSION         10\n#define CMD_MESSAGE            30\n#define CMD_ABSPOINTER_DATA    39\n#define CMD_ABSPOINTER_STATUS  40\n#define CMD_ABSPOINTER_COMMAND 41\n\n#define ABSPOINTER_ENABLE   0x45414552 /* Q E A E */\n#define ABSPOINTER_RELATIVE 0xF5\n#define ABSPOINTER_ABSOLUTE 0x53424152 /* R A B S */\n\n#define MESSAGE_RPCI   0x49435052 /* R P C I */\n#define MESSAGE_TCLO   0x4f4c4354 /* T C L O */\n\n/* -Wpedantic complains about unnamed unions */\n#pragma GCC diagnostic ignored \"-Wpedantic\"\n\nextern void (*ps2_mouse_alternate)(uint8_t); /* modules/mouse.c */\n\nstatic fs_node_t * mouse_pipe;\n\ntypedef struct {\n\tunion {\n\t\tuint32_t ax;\n\t\tuint32_t magic;\n\t};\n\tunion {\n\t\tuint32_t bx;\n\t\tsize_t size;\n\t};\n\tunion {\n\t\tuint32_t cx;\n\t\tuint16_t command;\n\t};\n\tunion {\n\t\tuint32_t dx;\n\t\tuint16_t port;\n\t};\n\tuintptr_t si;\n\tuintptr_t di;\n} vmware_cmd;\n\n/** Low bandwidth backdoor */\nstatic void vmware_send(vmware_cmd * cmd) {\n\tcmd->magic = VMWARE_MAGIC;\n\tcmd->port = VMWARE_PORT;\n\n\tasm volatile(\"in %%dx, %0\" : \"+a\"(cmd->ax), \"+b\"(cmd->bx), \"+c\"(cmd->cx), \"+d\"(cmd->dx), \"+S\"(cmd->si), \"+D\"(cmd->di));\n}\n\n/** Output to high bandwidth backdoor */\nstatic void vmware_send_hb(vmware_cmd * cmd) {\n\tcmd->magic = VMWARE_MAGIC;\n\tcmd->port = VMWARE_PORTHB;\n\n\tasm volatile(\"cld; rep; outsb\" : \"+a\"(cmd->ax), \"+b\"(cmd->bx), \"+c\"(cmd->cx), \"+d\"(cmd->dx), \"+S\"(cmd->si), \"+D\"(cmd->di));\n}\n\n/** Input from high bandwidth backdoor */\nstatic void vmware_get_hb(vmware_cmd * cmd) {\n\tcmd->magic = VMWARE_MAGIC;\n\tcmd->port = VMWARE_PORTHB;\n\n\tasm volatile(\"cld; rep; insb\" : \"+a\"(cmd->ax), \"+b\"(cmd->bx), \"+c\"(cmd->cx), \"+d\"(cmd->dx), \"+S\"(cmd->si), \"+D\"(cmd->di));\n}\n\nstatic void mouse_off(void) {\n\t/* Disable the absolute mouse */\n\tvmware_cmd cmd;\n\tcmd.bx = ABSPOINTER_RELATIVE;\n\tcmd.command = CMD_ABSPOINTER_COMMAND;\n\tvmware_send(&cmd);\n}\n\nstatic void mouse_absolute(void) {\n\t/*\n\t * Set the mouse to absolute.\n\t *\n\t * You can also set a relative mode, but there's not\n\t * a lot of use in that as disabling the device just\n\t * falls back to the PS/2 (or USB, I guess) device anyway,\n\t * so instead of using that we just... turn it off.\n\t */\n\tvmware_cmd cmd;\n\n\t/* Enable */\n\tcmd.bx = ABSPOINTER_ENABLE;\n\tcmd.command = CMD_ABSPOINTER_COMMAND;\n\tvmware_send(&cmd);\n\n\t/* Status */\n\tcmd.bx = 0;\n\tcmd.command = CMD_ABSPOINTER_STATUS;\n\tvmware_send(&cmd);\n\n\t/* Read data (1) */\n\tcmd.bx = 1;\n\tcmd.command = CMD_ABSPOINTER_DATA;\n\tvmware_send(&cmd);\n\n\t/* Enable absolute */\n\tcmd.bx = ABSPOINTER_ABSOLUTE;\n\tcmd.command = CMD_ABSPOINTER_COMMAND;\n\tvmware_send(&cmd);\n}\n\nvolatile int8_t vmware_mouse_byte = 0;\n\nstatic void vmware_mouse(uint8_t byte) {\n\t/* unused, but we need to read the fake mouse event bytes from the PS/2 device. */\n\tvmware_mouse_byte = byte;\n\n\t/* Read status byte. */\n\tvmware_cmd cmd;\n\tcmd.bx = 0;\n\tcmd.command = CMD_ABSPOINTER_STATUS;\n\tvmware_send(&cmd);\n\n\tif (cmd.ax == 0xffff0000) {\n\t\t/* Device error; turn it off and back on again. */\n\t\tmouse_off();\n\t\tmouse_absolute();\n\t\treturn;\n\t}\n\n\tint words = cmd.ax & 0xFFFF;\n\n\tif (!words || words % 4) {\n\t\t/* If we don't have data, or for some reason data isn't a multiple of 4... bail */\n\t\treturn;\n\t}\n\n\t/* Read 4 bytes of data */\n\tcmd.bx = 4; /* how many */\n\tcmd.command = CMD_ABSPOINTER_DATA; /* read */\n\tvmware_send(&cmd);\n\n\t/*\n\t * I guess the flags tell you if this was relative or absolute, so if we\n\t * actually used the relative mode, we'd want to check that, but...\n\t */\n\t//int flags   = (cmd.ax & 0xFFFF0000) >> 16;\n\tint buttons = (cmd.ax & 0x0000FFFF);\n\n\tunsigned int x = 0;\n\tunsigned int y = 0;\n\n\tif (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y) {\n\t\t/*\n\t\t * Just like the virtualbox stuff, this is based on a mapping\n\t\t * to the display resolution, independently scaled in\n\t\t * each dimension...\n\t\t */\n\t\tx = ((unsigned int)cmd.bx * lfb_resolution_x) / 0xFFFF;\n\t\ty = ((unsigned int)cmd.cx * lfb_resolution_y) / 0xFFFF;\n\t} else {\n\t\tx = cmd.bx;\n\t\ty = cmd.cx;\n\t}\n\n\tmouse_device_packet_t packet;\n\tpacket.magic = MOUSE_MAGIC;\n\tpacket.x_difference = x;\n\tpacket.y_difference = y;\n\tpacket.buttons = 0;\n\n\t/* The particular bits for the buttons seem weird, but okay... */\n\tif (buttons & 0x20) {\n\t\tpacket.buttons |= LEFT_CLICK;\n\t}\n\tif (buttons & 0x10) {\n\t\tpacket.buttons |= RIGHT_CLICK;\n\t}\n\tif (buttons & 0x08) {\n\t\tpacket.buttons |= MIDDLE_CLICK;\n\t}\n\n\t/* dx = z = scroll amount */\n\tif ((int8_t)cmd.dx > 0) {\n\t\tpacket.buttons |= MOUSE_SCROLL_DOWN;\n\t} else if ((int8_t)cmd.dx < 0) {\n\t\tpacket.buttons |= MOUSE_SCROLL_UP;\n\t}\n\n\tmouse_device_packet_t bitbucket;\n\twhile (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) {\n\t\tread_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket);\n\t}\n\twrite_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet);\n}\n\nstatic int detect_device(void) {\n\tvmware_cmd cmd;\n\n\t/* read version */\n\tcmd.bx = ~VMWARE_MAGIC;\n\tcmd.command = CMD_GETVERSION;\n\tvmware_send(&cmd);\n\n\tif (cmd.bx != VMWARE_MAGIC || cmd.ax == 0xFFFFFFFF) {\n\t\t/* Not a vmware device... */\n\t\treturn 0;\n\t}\n\n\t/* Good to go! */\n\treturn 1;\n}\n\n\nstatic int open_msg_channel(uint32_t proto) {\n\tvmware_cmd cmd;\n\tcmd.cx = CMD_MESSAGE | 0x00000000; /* CMD_MESSAGE */\n\tcmd.bx = proto;\n\tvmware_send(&cmd);\n\n\tif ((cmd.cx & 0x10000) == 0) {\n\t\treturn -1;\n\t}\n\n\treturn cmd.dx >> 16;\n}\n\nstatic void msg_close(int channel) {\n\tvmware_cmd cmd = {0};\n\tcmd.cx = CMD_MESSAGE | 0x00060000;\n\tcmd.bx = 0;\n\tcmd.dx = channel << 16;\n\n\tvmware_send(&cmd);\n}\n\nstatic int open_rpci_channel(void) {\n\treturn open_msg_channel(MESSAGE_RPCI);\n}\n\nstatic int tclo_channel = -1;\n\nstatic int open_tclo_channel(void) {\n\tif (tclo_channel != -1) {\n\t\tmsg_close(tclo_channel);\n\t}\n\ttclo_channel = open_msg_channel(MESSAGE_TCLO);\n\treturn tclo_channel;\n}\n\nstatic int msg_send(int channel, const char * msg, size_t size) {\n\t{\n\t\tvmware_cmd cmd = {0};\n\t\tcmd.cx = CMD_MESSAGE | 0x00010000; /* CMD_MESSAGE size */\n\t\tcmd.size = size;\n\t\tcmd.dx   = channel << 16;\n\t\tvmware_send(&cmd);\n\n\t\tif (size == 0) return 0;\n\n\t\tif (((cmd.cx >> 16) & 0x0081) != 0x0081) {\n\t\t\treturn -2;\n\t\t}\n\t}\n\n\t{\n\t\tvmware_cmd cmd = {0};\n\t\tcmd.bx = 0x0010000;\n\t\tcmd.cx = size;\n\t\tcmd.dx = channel << 16;\n\t\tcmd.si = (uintptr_t)msg;\n\t\tvmware_send_hb(&cmd);\n\n\t\tif (!(cmd.bx & 0x0010000)) {\n\t\t\treturn -3;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nstatic int msg_recv(int channel, char * buf, size_t bufsize) {\n\tsize_t size;\n\t{\n\t\tvmware_cmd cmd = {0};\n\t\tcmd.cx = CMD_MESSAGE | 0x00030000; /* CMD_MESSAGE receive ize */\n\t\tcmd.dx   = channel << 16;\n\t\tvmware_send(&cmd);\n\n\t\tsize = cmd.bx;\n\t\tif (size == 0) return 0;\n\t\tif (((cmd.cx >> 16) & 0x0083) != 0x0083) {\n\t\t\treturn -2;\n\t\t}\n\t\tif (size > bufsize) return -1;\n\t}\n\n\t{\n\t\tvmware_cmd cmd = {0};\n\t\tcmd.bx = 0x00010000;\n\t\tcmd.cx = size;\n\t\tcmd.dx = channel << 16;\n\t\tcmd.di = (uintptr_t)buf;\n\n\t\tvmware_get_hb(&cmd);\n\t\tif (!(cmd.bx & 0x00010000)) {\n\t\t\treturn -3;\n\t\t}\n\t}\n\n\t{\n\t\tvmware_cmd cmd = {0};\n\t\tcmd.cx = CMD_MESSAGE | 0x00050000;\n\t\tcmd.bx = 0x0001;\n\t\tcmd.dx = channel << 16;\n\n\t\tvmware_send(&cmd);\n\t}\n\n\treturn size;\n}\n\nstatic int rpci_string(const char * request) {\n\t/* Open channel */\n\tint channel = open_rpci_channel();\n\tif (channel < 0) return channel;\n\n\tsize_t size = strlen(request) + 1;\n\tmsg_send(channel, request, size);\n\n\tchar buf[16];\n\tint recv_size = msg_recv(channel, buf, 16);\n\n\tmsg_close(channel);\n\n\tif (recv_size < 0) return recv_size;\n\n\treturn 0;\n}\n\nstatic int attempt_scale(void) {\n\n\tint i;\n\tint c = open_tclo_channel();\n\tif (c < 0) {\n\t\treturn 1;\n\t}\n\n\tchar buf[256];\n\tif ((i = msg_send(c, buf, 0)) < 0) { return 1; }\n\n\tint resend = 0;\n\n\twhile (1) {\n\t\ti = msg_recv(c, buf, 256);\n\t\tif (i < 0) {\n\t\t\treturn 1;\n\t\t} else if (i == 0) {\n\t\t\tif (resend) {\n\t\t\t\tif ((i = rpci_string(\"tools.capability.resolution_set 1\")) < 0) { return 1; }\n\t\t\t\tif ((i = rpci_string(\"tools.capability.resolution_server toolbox 1\")) < 0) { return 1; }\n\t\t\t\tif ((i = rpci_string(\"tools.capability.display_topology_set 1\")) < 0) { return 1; }\n\t\t\t\tif ((i = rpci_string(\"tools.capability.color_depth_set 1\")) < 0) { return 1; }\n\t\t\t\tif ((i = rpci_string(\"tools.capability.resolution_min 0 0\")) < 0) { return 1; }\n\t\t\t\tif ((i = rpci_string(\"tools.capability.unity 1\")) < 0) { return 1; }\n\t\t\t\tresend = 0;\n\t\t\t} else {\n\t\t\t\tunsigned long s, ss;\n\t\t\t\trelative_time(0, 10000, &s, &ss);\n\t\t\t\tsleep_until((process_t *)this_core->current_process, s, ss);\n\t\t\t\tswitch_task(0);\n\t\t\t}\n\t\t\tif ((i = msg_send(c, buf, 0)) < 0) { return 1; }\n\t\t} else {\n\t\t\tbuf[i] = '\\0';\n\t\t\tif (startswith(buf, \"reset\")) {\n\t\t\t\tif ((i = msg_send(c, \"OK ATR toolbox\", strlen(\"OK ATR toolbox\"))) < 0) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t} else if (startswith(buf, \"ping\")) {\n\t\t\t\tif ((i = msg_send(c, \"OK \", strlen(\"OK \"))) < 0) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t} else if (startswith(buf, \"Capabilities_Register\")) {\n\t\t\t\tif ((i = msg_send(c, \"OK \", strlen(\"OK \"))) < 0) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tresend = 1;\n\t\t\t} else if (startswith(buf, \"Resolution_Set\")) {\n\t\t\t\tchar * x = &buf[15];\n\t\t\t\tchar * y = strstr(x,\" \");\n\t\t\t\tif (!y) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\t*y = '\\0';\n\t\t\t\ty++;\n\t\t\t\tint _x = atoi(x);\n\t\t\t\tint _y = atoi(y);\n\n\t\t\t\tif (lfb_resolution_x && _x && (_x != lfb_resolution_x  || _y != lfb_resolution_y)) {\n\t\t\t\t\tlfb_set_resolution(_x, _y);\n\t\t\t\t}\n\n\t\t\t\tif ((i = msg_send(c, \"OK \", strlen(\"OK \"))) < 0) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\n\t\t\t\tmsg_close(c);\n\t\t\t\treturn 0;\n\t\t\t} else {\n\t\t\t\tif ((i = msg_send(c, \"ERROR Unknown command\", strlen(\"ERROR Unknown command\"))) < 0) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void vmware_resize(void * data) {\n\twhile (1) {\n\t\tattempt_scale();\n\t\tunsigned long s, ss;\n\t\trelative_time(1, 0, &s, &ss);\n\t\tsleep_until((process_t *)this_core->current_process, s, ss);\n\t\tswitch_task(0);\n\t}\n}\n\nstatic int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) {\n\tswitch (request) {\n\t\tcase 1:\n\t\t\t/* Disable */\n\t\t\tmouse_off();\n\t\t\tps2_mouse_alternate = NULL;\n\t\t\treturn 0;\n\t\tcase 2:\n\t\t\t/* Enable */\n\t\t\tps2_mouse_alternate = vmware_mouse;\n\t\t\tmouse_absolute();\n\t\t\treturn 0;\n\t\tcase 3:\n\t\t\treturn ps2_mouse_alternate == vmware_mouse;\n\t\tdefault:\n\t\t\treturn -EINVAL;\n\t}\n}\n\nstatic int vmware_initialize(int argc, char * argv[]) {\n\tif (!detect_device()) return -ENODEV;\n\n\tmouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE);\n\tmouse_pipe->flags = FS_CHARDEVICE;\n\n\tvfs_mount(\"/dev/vmmouse\", mouse_pipe, \"vmware-mouse\", \"\");\n\n\tmouse_pipe->flags = FS_CHARDEVICE;\n\tmouse_pipe->ioctl = ioctl_mouse;\n\n\t/*\n\t * We have a hack in the PS/2 mouse driver that lets us\n\t * take over for the normal mouse driver and essential\n\t * intercept the interrputs when they are valid.\n\t */\n\tps2_mouse_alternate = vmware_mouse;\n\n\tmouse_absolute();\n\n\tif (lfb_driver_name && !strcmp(lfb_driver_name, \"vmware\") && !args_present(\"novmwareresset\")) {\n\t\tspawn_worker_thread(vmware_resize, \"[vmware]\", NULL);\n\t}\n\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"vmware\",\n\t.init = vmware_initialize,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "modules/xhci.c",
    "content": "/**\n * @brief xHCI Host Controller Driver\n * @file modules/xhci.c\n * @package x86_64\n *\n * @warning This is a stub driver.\n *\n * @copyright\n * This file is part of ToaruOS and is released under the terms\n * of the NCSA / University of Illinois License - see LICENSE.md\n * Copyright (C) 2021 K. Lange\n */\n#include <errno.h>\n#include <kernel/module.h>\n#include <kernel/printf.h>\n#include <kernel/types.h>\n#include <kernel/string.h>\n#include <kernel/pci.h>\n#include <kernel/mmu.h>\n#include <kernel/args.h>\n#include <kernel/procfs.h>\n#include <kernel/syscall.h>\n\nstatic void delay_yield(size_t subticks) {\n\t#ifdef __aarch64__\n\tasm volatile (\"dsb sy\\nisb\" ::: \"memory\");\n\t#endif\n\tunsigned long s, ss;\n\trelative_time(0, subticks, &s, &ss);\n\tsleep_until((process_t *)this_core->current_process, s, ss);\n\tswitch_task(0);\n\t#ifdef __aarch64__\n\tasm volatile (\"dmb sy\\n\" ::: \"memory\");\n\t#endif\n}\n\n\nstruct xhci_cap_regs {\n\tvolatile uint32_t cap_caplen_version;\n\tvolatile uint32_t cap_hcsparams1;\n\tvolatile uint32_t cap_hcsparams2;\n\tvolatile uint32_t cap_hcsparams3;\n\tvolatile uint32_t cap_hccparams1;\n\tvolatile uint32_t cap_dboff;\n\tvolatile uint32_t cap_rtsoff;\n\tvolatile uint32_t cap_hccparams2;\n} __attribute__((packed));\n\nstruct xhci_port_regs {\n\tvolatile uint32_t port_status;\n\tvolatile uint32_t port_pm_status;\n\tvolatile uint32_t port_link_info;\n\tvolatile uint32_t port_lpm_control;\n} __attribute__((packed));\n\nstruct xhci_op_regs {\n\tvolatile uint32_t op_usbcmd;   /* 0 */\n\tvolatile uint32_t op_usbsts;   // 4\n\tvolatile uint32_t op_pagesize; // 8h\n\tvolatile uint32_t op__pad1[2]; // ch 10h\n\tvolatile uint32_t op_dnctrl;   // 14h\n\tvolatile uint32_t op_crcr[2];     // 18h 1ch\n\tvolatile uint32_t op__pad2[4]; // 20h 24h 28h 2ch\n\tvolatile uint32_t op_dcbaap[2];   // 30h 34h\n\tvolatile uint32_t op_config;   // 38h\n\tvolatile uint8_t  op_more_padding[964]; // 3ch-400h\n\tstruct xhci_port_regs op_portregs[256];\n} __attribute__((packed));\n\nstruct xhci_trb {\n\tuint32_t trb_thing_a;\n\tuint32_t trb_thing_b;\n\tuint32_t trb_status;\n\tuint32_t trb_control;\n} __attribute__((packed));\n\nstruct XHCIControllerData {\n\tuintptr_t mmio;\n\tuint32_t device;\n\tuint64_t pcie_offset;\n\tstruct xhci_cap_regs * cregs;\n\tstruct xhci_op_regs * oregs;\n\tprocess_t * thread;\n\tvolatile struct xhci_trb * cr_trbs;\n\tvolatile struct xhci_trb * er_trbs;\n\tspin_lock_t command_queue;\n\tuint32_t command_queue_cycle;\n\tint command_queue_enq;\n\tvolatile uint32_t * doorbells;\n};\n\nstatic uint64_t pci_addr_map(struct XHCIControllerData * controller, uint64_t addr) {\n\treturn addr + controller->pcie_offset;\n}\n\nstatic uintptr_t pci_to_cpu(struct XHCIControllerData * controller, uint64_t addr) {\n\treturn addr - controller->pcie_offset;\n}\n\nstatic uintptr_t allocate_page(uint64_t * phys_out) {\n\tuint64_t phys = mmu_allocate_a_frame() << 12;\n\tuintptr_t virt = (uintptr_t)mmu_map_mmio_region(phys, 4096);\n\tmemset((void*)virt,0,4096);\n\t*phys_out = phys;\n\treturn virt;\n}\n\nstatic int xhci_command(struct XHCIControllerData * controller, uint32_t p1, uint32_t p2, uint32_t status, uint32_t control) {\n\tspin_lock(controller->command_queue);\n\n\tcontrol &= ~1;\n\tcontrol |= controller->command_queue_cycle;\n\n\tcontroller->cr_trbs[controller->command_queue_enq].trb_thing_a = p1;\n\tcontroller->cr_trbs[controller->command_queue_enq].trb_thing_b = p2;\n\tcontroller->cr_trbs[controller->command_queue_enq].trb_status  = status;\n\tcontroller->cr_trbs[controller->command_queue_enq].trb_control = control;\n\n\tcontroller->command_queue_enq++;\n\tif (controller->command_queue_enq == 63) {\n\t\tcontroller->cr_trbs[controller->command_queue_enq].trb_control ^= 1;\n\t\tif (controller->cr_trbs[controller->command_queue_enq].trb_control & (1 << 1)) {\n\t\t\tcontroller->command_queue_cycle ^= 1;\n\t\t}\n\t\tcontroller->command_queue_enq = 0;\n\t}\n\n\t/* ring doorbell */\n\tcontroller->doorbells[0] = 0;\n\n\tspin_unlock(controller->command_queue);\n\treturn 0;\n}\n\nstatic ssize_t xhci_write(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) {\n\tstruct XHCIControllerData * controller = node->device;\n\n\tif (size != sizeof(struct xhci_trb)) return -EINVAL;\n\n\tstruct xhci_trb * data = (void*)buffer;\n\n\txhci_command(controller, data->trb_thing_a, data->trb_thing_b, data->trb_status, data->trb_control);\n\n\treturn sizeof(struct xhci_trb);\n\n}\n\n\nstatic struct XHCIControllerData * _irq_owner = NULL;\n#include <kernel/arch/x86_64/irq.h>\nstatic int irq_handler(struct regs *r) {\n\tint irq = r->int_no - 32;\n\n\tif (_irq_owner) {\n\t\t/* Is it ours? */\n\t\tuint32_t status = _irq_owner->oregs->op_usbsts;\n\t\tif (status & (1 << 3)) {\n\t\t\t_irq_owner->oregs->op_usbsts = (1 << 3);\n\t\t\tdprintf(\"xhci: irq\\n\");\n\n\t\t\tuintptr_t rts = (uintptr_t)_irq_owner->cregs + _irq_owner->cregs->cap_rtsoff;\n\t\t\tvolatile uint32_t * irs0_32 = (uint32_t*)(rts + 0x20);\n\t\t\tirs0_32[0] |= 1;\n\n\t\t\tmake_process_ready(_irq_owner->thread);\n\n\t\t\tirq_ack(irq);\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nvoid xhci_thread(void * arg) {\n\tstruct XHCIControllerData * controller = arg;\n\n\tcontroller->thread = (process_t*)this_core->current_process;\n\tspin_init(controller->command_queue);\n\n\t/* Begin generic XHCi */\n\tdprintf(\"xhci: available slots: %d\\n\", controller->cregs->cap_hcsparams1 & 0xFF);\n\tdprintf(\"xhci: available ports: %d\\n\", controller->cregs->cap_hcsparams1 >> 24);\n\tdprintf(\"xhci: resetting controller\\n\");\n\tdprintf(\"xhci: waiting for controller to stop...\\n\");\n\tuint32_t cmd = controller->oregs->op_usbcmd;\n\tcmd &= ~(1);\n\tcontroller->oregs->op_usbcmd = cmd;\n\twhile (!(controller->oregs->op_usbsts & (1 << 0)));\n\n\tdprintf(\"xhci: restarting controller...\\n\");\n\tcmd = controller->oregs->op_usbcmd;\n\tcmd |= (1 << 1);\n\tcontroller->oregs->op_usbcmd = cmd;\n\twhile ((controller->oregs->op_usbcmd & (1 << 1)));\n\twhile ((controller->oregs->op_usbsts & (1 << 11)));\n\tdprintf(\"xhci: controller is ready: %#x\\n\", controller->oregs->op_usbsts);\n\n\tdprintf(\"xhci: slot config %#x -> %#x\\n\",\n\t\tcontroller->oregs->op_config, controller->cregs->cap_hcsparams1 & 0xFF);\n\tcontroller->oregs->op_config = controller->cregs->cap_hcsparams1 & 0xFF;\n\n\t/* TODO We may need to clear interrupts here by writing status back */\n\tuint32_t sts = controller->oregs->op_usbsts;\n\t(void)sts;\n\n\tdprintf(\"xhci: context size is %d\\n\",\n\t\t(controller->cregs->cap_hccparams1 & (1 << 2)) ? 64 : 32);\n\n\tuintptr_t ext_off = (controller->cregs->cap_hccparams1 >> 16) << 2;\n\n\tvolatile uint32_t * ext_caps = (void*)((uintptr_t)controller->cregs + ext_off);\n\n\t/**\n\t * Verify port configurations;\n\t * should be port 1 is usb 2.0\n\t *           port 2, 3, 4, 5 are 3.0\n\t * port 1 has a hub with 4 ports?\n\t */\n\twhile (1) {\n\t\tuint32_t cap_val = *ext_caps;\n\n\t\tdprintf(\"xhci: ecap = %#x\\n\", cap_val);\n\n\t\t/* Bottom byte is type */\n\t\tif ((cap_val & 0xFF) == 2) {\n\t\t\tuint8_t rev_minor = ext_caps[0] >> 16;\n\t\t\tuint8_t rev_major = ext_caps[0] >> 24;\n\t\t\t//uint32_t name_str = ext_caps[1];\n\n\t\t\tuint8_t port_offset = ext_caps[2];\n\t\t\tuint8_t port_count  = ext_caps[2] >> 8;\n\t\t\tuint8_t psic = ext_caps[2] >> 28;\n\n\t\t\tdprintf(\"xhci:  protocol %d.%d %d port%s starting from port %d has %d speed%s\\n\",\n\t\t\t\trev_major, rev_minor,\n\t\t\t\tport_count, &\"s\"[port_count==1],\n\t\t\t\tport_offset,\n\t\t\t\tpsic, &\"s\"[psic==1]);\n\t\t}\n\n\t\tif (cap_val == 0xFFFFffff) break;\n\t\tif ((cap_val & 0xFF00) == 0) break;\n\t\text_caps = (void*)((uintptr_t)ext_caps + ((cap_val & 0xFF00) >> 6));\n\t}\n\n\t/* Device Context Base Address Array */\n\tuint64_t dcbaap;\n\tuint64_t * baseCtx = (void*)allocate_page(&dcbaap);\n\n\tdprintf(\"xhci: DCBAAP at %#zx (phys=%#zx)\\n\", (uintptr_t)baseCtx, dcbaap);\n\tcontroller->oregs->op_dcbaap[0] = pci_addr_map(controller, dcbaap);\n\tcontroller->oregs->op_dcbaap[1] = pci_addr_map(controller, dcbaap) >> 32;\n\n\t/* Enable slots */\n\tuint32_t cfg = controller->oregs->op_config;\n\tcfg &= ~0xFF;\n\tcfg |= 32;\n\tdprintf(\"xhci: set cfg = %#x\\n\", cfg);\n\tcontroller->oregs->op_config = cfg;\n\n\t/* trbs for event ring */\n\tuint64_t er_trbs_phys;\n\tvoid * er_trbs_virt = (void*)allocate_page(&er_trbs_phys);\n\tdprintf(\"xhci: er trbs = %#zx (phys=%#zx)\\n\",\n\t\t(uintptr_t)er_trbs_virt, er_trbs_phys);\n\n\t/* erst */\n\tuint64_t er_erst_phys;\n\tvoid * er_erst_virt = (void*)allocate_page(&er_erst_phys);\n\tdprintf(\"xhci: er erst = %#zx (phys=%#zx)\\n\",\n\t\t(uintptr_t)er_erst_virt, er_erst_phys);\n\n\t((volatile uint64_t*)er_erst_virt)[0] = pci_addr_map(controller, er_trbs_phys);\n\t((volatile uint64_t*)er_erst_virt)[1] = 64;\n\n\tdprintf(\"xhci: rtsoff = %#x\\n\", controller->cregs->cap_rtsoff);\n\tuintptr_t rts = (uintptr_t)controller->cregs + controller->cregs->cap_rtsoff;\n\n\t/* Interrupter points to event ring */\n\tvolatile uint32_t * irs0_32 = (uint32_t*)(rts + 0x20);\n\tirs0_32[2] = 1; /* Size = 1 */\n\tirs0_32[6] = pci_addr_map(controller, er_trbs_phys) | (1 << 3);\n\tirs0_32[7] = (pci_addr_map(controller, er_trbs_phys) | (1 << 3)) >> 32;\n\tirs0_32[1] = 500; /* IMOD */\n\tirs0_32[0] = 2; /* enable interrupts */\n\tirs0_32[4] = pci_addr_map(controller, er_erst_phys);\n\tirs0_32[5] = pci_addr_map(controller, er_erst_phys) >> 32;\n\n\t/* trbs for control ring */\n\tuint64_t cr_trbs_phys;\n\tvoid * cr_trbs_virt = (void*)allocate_page(&cr_trbs_phys);\n\n\t((volatile uint64_t*)cr_trbs_virt)[63*2] = pci_addr_map(controller, cr_trbs_phys);\n\t((volatile uint64_t*)cr_trbs_virt)[63*2+1] = ((0x2UL | (6UL << 10)) << 32);\n\n\tcontroller->oregs->op_crcr[0] = (pci_addr_map(controller, cr_trbs_phys) | 1);\n\tcontroller->oregs->op_crcr[1] = (pci_addr_map(controller, cr_trbs_phys) | 1) >> 32;\n\n\t/* Scratchpad buffers, if needed */\n\tuint32_t hcs2 = controller->cregs->cap_hcsparams2;\n\tuint32_t sb_hi = (hcs2 >> 21) & 0x1f;\n\tuint32_t sb_lo = (hcs2 >> 27) & 0x1f;\n\tuint32_t sb_max = (sb_hi << 5) | sb_lo;\n\n\t/* should be 31 */\n\tif (sb_max) {\n\t\tdprintf(\"num scratchpad buffers = %u\\n\", sb_max);\n\n\t\t/* Allocate buffer for array */\n\t\tuint64_t scratch_phys;\n\t\tuint64_t *scratch_virt = (uint64_t*)allocate_page(&scratch_phys);\n\t\tdprintf(\"xhci: scratch at %#zx (phys=%#zx)\\n\", (uintptr_t)scratch_virt, scratch_phys);\n\t\t/* Our DMA mapping should be 1:1, so, uh, yolo */\n\t\tfor (unsigned int i = 0; i < sb_max; ++i) {\n\t\t\tuint64_t sb_phys;\n\t\t\tallocate_page(&sb_phys);\n\t\t\tscratch_virt[i] = pci_addr_map(controller, sb_phys);\n\t\t}\n\t\tbaseCtx[0] = pci_addr_map(controller, scratch_phys);\n\t\tdprintf(\"xhci: assigned scratchpad buffer array\\n\");\n\t}\n\n\t/* TODO This irq API sucks */\n\tint irq_number = pci_get_interrupt(controller->device);\n\tirq_install_handler(irq_number, irq_handler, \"xhci\");\n\t_irq_owner = controller;\n\n\tdprintf(\"xhci: Starting command ring...\\n\");\n\t{\n\t\tuint32_t cmd = controller->oregs->op_usbcmd;\n\t\tdprintf(\"cmd before = %#x\\n\", cmd);\n\t\tcmd |= (1 << 0) | (1 << 2);\n\t\tcontroller->oregs->op_usbcmd = cmd;\n\t}\n\n\tdprintf(\"xhci: status = %#x\\n\", controller->oregs->op_usbsts);\n\n\tdelay_yield(50000);\n\n\tdprintf(\"xhci: status = %#x\\n\", controller->oregs->op_usbsts);\n\tif (controller->oregs->op_usbsts & (1 << 2)) goto _bail;\n\n\n\tdprintf(\"xhci: doorbells at %#x\\n\", controller->cregs->cap_dboff);\n\tcontroller->doorbells = (void*)((uintptr_t)controller->cregs + controller->cregs->cap_dboff);\n\n\t/* Just want to enable the hub for now, see if we can id it */\n\tcontroller->cr_trbs = cr_trbs_virt;\n\tcontroller->er_trbs = er_trbs_virt;\n\n\tcontroller->command_queue_cycle = 1;\n\tcontroller->command_queue_enq = 0;\n\n\tdprintf(\"xhci: status before ring = %#x\\n\", controller->oregs->op_usbsts);\n\n\txhci_command(controller, 0, 0, 0, (23 << 10));\n\n\tchar devName[20] = \"/dev/xhciN\";\n\tsnprintf(devName, 19, \"/dev/xhci%d\", 0);\n\tfs_node_t * fnode = calloc(sizeof(fs_node_t), 1);\n\tsnprintf(fnode->name, 100, \"xhci%d\", 0);\n\tfnode->flags   = FS_BLOCKDEVICE;\n\tfnode->mask    = 0660; /* Only accessible to root user/group */\n\tfnode->read    = NULL;\n\tfnode->write   = xhci_write;\n\tfnode->device  = controller;\n\tvfs_mount(devName, fnode, \"xhci\", fnode->name);\n\n\tint event_deq = 0;\n\tuint32_t event_cycle_state = 1;\n\n\twhile (1) {\n\t\twhile ((controller->er_trbs[event_deq].trb_control & 1) != event_cycle_state) {\n\t\t\tswitch_task(0);\n\t\t}\n\n\t\tuint32_t thing_a = controller->er_trbs[event_deq].trb_thing_a;\n\t\tuint32_t thing_b = controller->er_trbs[event_deq].trb_thing_a;\n\t\tuint32_t status  = controller->er_trbs[event_deq].trb_status;\n\t\tuint32_t control = controller->er_trbs[event_deq].trb_control;\n\n\t\tdprintf(\"xhci: event %d [%#x %#x %#x %#x]\\n\",\n\t\t\tevent_deq, thing_a, thing_b, status, control);\n\n\t\tevent_deq++;\n\t\tif (event_deq == 64) {\n\t\t\tevent_deq = 0;\n\t\t\tevent_cycle_state = !event_cycle_state;\n\t\t}\n\n\t\t/* Write new event dequeue pointer */\n\t\tuint64_t new_deq_phys = pci_addr_map(controller, er_trbs_phys + sizeof(struct xhci_trb) * event_deq) | (1 << 3);\n\t\tirs0_32[6] = new_deq_phys;\n\t\tirs0_32[7] = new_deq_phys >> 32;\n\t}\n\n_bail:\n\ttask_exit(1);\n\t__builtin_unreachable();\n}\n\nstatic void find_xhci(uint32_t device, uint16_t v, uint16_t d, void * extra) {\n\tif (pci_find_type(device) != 0x0C03) return;\n\tif (pci_read_field(device, PCI_PROG_IF, 1) != 0x30) return;\n\tfs_node_t * stderr = extra;\n\n\tuint16_t command_reg = pci_read_field(device, PCI_COMMAND, 2);\n\tcommand_reg |= (1 << 2);\n\tcommand_reg |= (1 << 1);\n\tpci_write_field(device, PCI_COMMAND, 2, command_reg);\n\n\t/* The mmio address is 64 bits and combines BAR0 and BAR1... */\n\tuint64_t addr_low  = pci_read_field(device, PCI_BAR0, 4) & 0xFFFFFFF0;\n\tuint64_t addr_high = pci_read_field(device, PCI_BAR1, 4) & 0xFFFFFFFF; /* I think this is right? */\n\tuint64_t mmio_addr = (addr_high << 32) | addr_low;\n\n\tif (mmio_addr == 0) {\n\t\t/* Need to map... */\n\t\tfprintf(stderr, \"xhci: Device is unmapped. TODO: Check if this is behind a PCI bridge...\\n\");\n\t\treturn;\n\t\t#if 0\n\t\tmmio_addr = mmu_allocate_n_frames(2) << 12;\n\t\tpci_write_field(device, PCI_BAR0, 4, (mmio_addr & 0xFFFFFFF0) | (1 << 2));\n\t\tpci_write_field(device, PCI_BAR1, 4, (mmio_addr >> 32));\n\t\t#endif\n\t}\n\n\tfprintf(stderr, \"xhci: controller found\\n\");\n\n\tstruct XHCIControllerData * controller = calloc(sizeof(struct XHCIControllerData), 1);\n\tcontroller->device = device;\n\n\t/* Map mmio space... */\n\tuintptr_t xhci_regs = (uintptr_t)mmu_map_mmio_region(mmio_addr, 0x1000 * 4); /* I don't know. */\n\tcontroller->mmio  = mmio_addr;\n\tcontroller->cregs = (struct xhci_cap_regs*)xhci_regs;\n\tcontroller->oregs = (struct xhci_op_regs*)(xhci_regs + (controller->cregs->cap_caplen_version & 0xFF));\n\tcontroller->pcie_offset = 0;\n\n\tspawn_worker_thread(xhci_thread, \"[xhci]\", controller);\n}\n\nstatic int init(int argc, char * argv[]) {\n\tfs_node_t * node = FD_ENTRY(1); /* Get the stdout for the process that loaded the module */\n\tpci_scan(find_xhci, -1, node);\n\treturn 0;\n}\n\nstatic int fini(void) {\n\treturn 0;\n}\n\nstruct Module metadata = {\n\t.name = \"xhci\",\n\t.init = init,\n\t.fini = fini,\n};\n\n"
  },
  {
    "path": "util/__init__.krk",
    "content": "import fileio\n\ndef to_int(little: bool, data: bytes):\n    let out = 0\n    if little: data = reversed(data)\n    for x in data:\n        out *= 0x100\n        out += x\n    return out\n\ndef to_bytes(little: bool, val: int, length: int):\n    let out = [0] * length\n    let i = 0\n    while val and i < length:\n        out[i] = val & 0xFF\n        val >>= 8\n        i++\n    if not little:\n        out = reversed(out)\n    return bytes(out)\n\ndef read_struct(fmt: str, data: bytes, offset: int):\n    if not fmt or not isinstance(fmt,str):\n        raise ValueError\n    # First, read the endianness\n    let littleEndian = True\n    if fmt[0] in '@<>#!':\n        if fmt[0] == '>': littleEndian = False\n        else if fmt[0] == '!': littleEndian = False\n        fmt = fmt[1:]\n    # Then read length\n    let length = None\n    if fmt[0] in '0123456789':\n        length = int(fmt[0])\n        fmt = fmt[1:]\n        while fmt[0] in '012345679':\n            length *= 10\n            length += int(fmt[0])\n            fmt = fmt[1:]\n    # Then read type\n    if fmt[0] == 'B':\n        return int(data[offset]), offset + 1\n    else if fmt[0] == 'b':\n        let out = int(data[offset])\n        if out > 0x7F: out = -out\n        return out, offset + 1\n    else if fmt[0] == 's':\n        return bytes([data[x] for x in range(offset,offset+length)]), offset + length\n    else if fmt[0] == 'I':\n        return to_int(littleEndian, bytes([data[x] for x in range(offset,offset+4)])), offset + 4\n    else if fmt[0] == 'H':\n        return to_int(littleEndian, bytes([data[x] for x in range(offset,offset+2)])), offset + 2\n    raise ValueError(\"Huh\")\n\ndef pack_into(fmt: str, data: bytes, offset: int, val: any):\n    if not fmt or not isinstance(fmt,str):\n        raise ValueError\n    # First, read the endianness\n    let littleEndian = True\n    if fmt[0] in '@<>#!':\n        if fmt[0] == '>': littleEndian = False\n        else if fmt[0] == '!': littleEndian = False\n        fmt = fmt[1:]\n    # Then read length\n    let length = None\n    if fmt[0] in '0123456789':\n        length = int(fmt[0])\n        fmt = fmt[1:]\n        while fmt[0] in '012345679':\n            length *= 10\n            length += int(fmt[0])\n            fmt = fmt[1:]\n    # Then read type\n    if fmt[0] == 'B':\n        data[offset] = val\n        return offset + 1\n    else if fmt[0] == 'b':\n        data[offset] = val\n        return offset + 1\n    else if fmt[0] == 's':\n        for x in range(length):\n            data[offset+x] = val[x]\n        return offset + length\n    else if fmt[0] == 'I':\n        for x in to_bytes(littleEndian, val, 4):\n            data[offset] = x\n            offset++\n        return offset\n    else if fmt[0] == 'H':\n        for x in to_bytes(littleEndian, val, 2):\n            data[offset] = x\n            offset++\n        return offset\n    raise ValueError(\"Huh\")\n\nclass ISO(object):\n\n    def __init__(self, path):\n        let data\n        with fileio.open(path, 'rb') as f:\n            self.data = bytearray(f.read())\n        self.sector_size = 2048\n        let o = 0x10 * self.sector_size\n        let _unused\n        self.type,             o = read_struct('B',self.data,o)\n        self.id,               o = read_struct('5s',self.data,o)\n        self.version,          o = read_struct('B',self.data,o)\n        _unused,               o = read_struct('B',self.data,o)\n        self.system_id,        o = read_struct('32s',self.data,o)\n        self.volume_id,        o = read_struct('32s',self.data,o)\n        _unused,               o = read_struct('8s',self.data,o)\n        self.volume_space_lsb, o = read_struct('<I',self.data,o)\n        self.volume_space_msb, o = read_struct('>I',self.data,o)\n        _unused,               o = read_struct('32s',self.data,o)\n        self.volume_set_lsb,   o = read_struct('<H',self.data,o)\n        self.volume_set_msb,   o = read_struct('>H',self.data,o)\n        self.volume_seq_lsb,   o = read_struct('<H',self.data,o)\n        self.volume_seq_msb,   o = read_struct('>H',self.data,o)\n        self.logical_block_size_lsb,   o = read_struct('<H',self.data,o)\n        self.logical_block_size_msb,   o = read_struct('>H',self.data,o)\n        self.path_table_size_lsb,   o = read_struct('<I',self.data,o)\n        self.path_table_size_msb,   o = read_struct('>I',self.data,o)\n        self.path_table_lsb,   o = read_struct('<I',self.data,o)\n        self.optional_path_table_lsb,   o = read_struct('<I',self.data,o)\n        self.path_table_msb,   o = read_struct('>I',self.data,o)\n        self.optional_path_table_msb,   o = read_struct('>I',self.data,o)\n        let _offset = o\n        self.root_dir_entry, o = read_struct('34s',self.data,o)\n\n        self.root = ISOFile(self,_offset)\n        self._cache = {}\n\n    def get_file(self, path):\n        if path == '/':\n            return self.root\n        else:\n            if path in self._cache:\n                return self._cache[path]\n            let units = path.split('/')\n            units = units[1:] # remove root\n            let me = self.root\n            for i in units:\n                let next_file = me.find(i)\n                if not next_file:\n                    me = None\n                    break\n                else:\n                    me = next_file\n            self._cache[path] = me\n            return me\n\nclass ISOFile(object):\n\n    def __init__(self, iso, offset):\n        self.iso = iso\n        self.offset = offset\n\n        let o = offset\n        self.length,            o = read_struct('B', self.iso.data, o)\n        if not self.length:\n            return\n        self.ext_length,        o = read_struct('B', self.iso.data, o)\n        self.extent_start_lsb,  o = read_struct('<I',self.iso.data, o)\n        self.extent_start_msb,  o = read_struct('>I',self.iso.data, o)\n        self.extent_length_lsb, o = read_struct('<I',self.iso.data, o)\n        self.extent_length_msb, o = read_struct('>I',self.iso.data, o)\n\n        self.date_data, o = read_struct('7s', self.iso.data, o)\n\n        self.flags, o = read_struct('b', self.iso.data, o)\n        self.interleave_units, o = read_struct('b', self.iso.data, o)\n        self.interleave_gap, o = read_struct('b', self.iso.data, o)\n\n        self.volume_seq_lsb, o = read_struct('<H',self.iso.data, o)\n        self.volume_seq_msb, o = read_struct('>H',self.iso.data, o)\n\n        self.name_len, o = read_struct('b', self.iso.data, o)\n        self.name, o = read_struct('{}s'.format(self.name_len), self.iso.data, o)\n        self.name = self.name.decode()\n\n    def write_extents(self):\n        pack_into('<I', self.iso.data, self.offset + 2, self.extent_start_lsb)\n        pack_into('>I', self.iso.data, self.offset + 6, self.extent_start_lsb)\n        pack_into('<I', self.iso.data, self.offset + 10, self.extent_length_lsb)\n        pack_into('>I', self.iso.data, self.offset + 14, self.extent_length_lsb)\n\n    def readable_name(self):\n        if not ';' in self.name:\n            return self.name.lower()\n        else:\n            let tmp, _\n            tmp, _ = self.name.split(';')\n            return tmp.lower()\n\n\n    def list(self):\n        let sectors = self.iso.data[self.extent_start_lsb * self.iso.sector_size: self.extent_start_lsb * self.iso.sector_size+ 3 * self.iso.sector_size]\n        let offset = 0\n\n        while 1:\n            let f = ISOFile(self.iso, self.extent_start_lsb * self.iso.sector_size + offset)\n            yield f\n            offset += f.length\n            if not f.length:\n                break\n\n    def find(self, name):\n        let sectors = self.iso.data[self.extent_start_lsb * self.iso.sector_size: self.extent_start_lsb * self.iso.sector_size+ 3 * self.iso.sector_size]\n        let offset = 0\n        if '.' in name and len(name.split('.')[0]) > 8:\n            let a, b\n            a, b = name.split('.')\n            name = a[:8] + '.' + b\n        if '-' in name:\n            name = name.replace('-','_')\n        while 1:\n            let f = ISOFile(self.iso, self.extent_start_lsb * self.iso.sector_size + offset)\n            if not f.length:\n                if offset < self.extent_length_lsb:\n                    offset += 1\n                    continue\n                else:\n                    break\n            if ';' in f.name:\n                let tmp, _\n                tmp, _ = f.name.split(';')\n                if tmp.endswith('.'):\n                    tmp = tmp[:-1]\n                if tmp.lower() == name.lower():\n                    return f\n            elif f.name.lower() == name.lower():\n                return f\n            offset += f.length\n        return None\n\nclass FAT(object):\n\n    def __init__(self, iso, offset):\n        self.iso = iso\n        self.offset = offset\n\n        let _\n        self.bytespersector,    _ = read_struct('H', self.iso.data, offset + 11)\n        self.sectorspercluster, _ = read_struct('B', self.iso.data, offset + 13)\n        self.reservedsectors,   _ = read_struct('H', self.iso.data, offset + 14)\n        self.numberoffats,      _ = read_struct('B', self.iso.data, offset + 16)\n        self.numberofdirs,      _ = read_struct('H', self.iso.data, offset + 17)\n        self.fatsize, _ = read_struct('H', self.iso.data, offset + 22)\n\n        self.root_dir_sectors = (self.numberofdirs * 32 + (self.bytespersector - 1)) // self.bytespersector\n        self.first_data_sector = self.reservedsectors + (self.numberoffats * self.fatsize) + self.root_dir_sectors\n        self.root_sector= self.first_data_sector - self.root_dir_sectors\n        self.root = FATDirectory(self, self.offset + self.root_sector * self.bytespersector)\n\n    def get_offset(self, cluster):\n        return self.offset + ((cluster - 2) * self.sectorspercluster + self.first_data_sector) * self.bytespersector\n\n    def get_file(self, path):\n        let units = path.split('/')\n        units = units[1:]\n\n        let me = self.root\n        let out = None\n        for i in units:\n            for fatfile in me.list():\n                if fatfile.readable_name() == i:\n                    me = fatfile.to_dir()\n                    out = fatfile\n                    break\n        return out\n\nclass FATDirectory(object):\n\n    def __init__(self, fat, offset):\n        self.fat = fat\n        self.offset = offset\n\n    def list(self):\n        let o = self.offset\n        while 1:\n            let out = FATFile(self.fat, o)\n            if out.name != '\\0\\0\\0\\0\\0\\0\\0\\0':\n                yield out\n            else:\n                break\n            o += out.size\n\n\nclass FATFile(object):\n\n    def __init__(self, fat, offset):\n\n        self.fat = fat\n        self.offset = offset\n        self.magic_long = None\n        self.size = 0\n        self.long_name = ''\n\n        let o = self.offset\n        self.actual_offset = o\n\n        let _\n        self.attrib,     _ = read_struct('B',self.fat.iso.data,o+11)\n\n        while (self.attrib & 0x0F) == 0x0F:\n            # Long file name entry\n            let tmp = read_struct('10s',self.fat.iso.data,o+1)[0]\n            tmp += read_struct('12s',self.fat.iso.data,o+14)[0]\n            tmp += read_struct('4s',self.fat.iso.data,o+28)[0]\n            let s = []\n            for i = 0; i < len(tmp); i += 2:\n                if tmp[x] != '\\xFF':\n                    s.append(chr(tmp[x]))\n            tmp = \"\".join(s).strip('\\x00')\n            self.long_name = tmp + self.long_name\n            self.size += 32\n            o = self.offset + self.size\n            self.actual_offset = o\n            self.attrib,     _ = read_struct('B',self.fat.iso.data,o+11)\n\n        o = self.offset + self.size\n\n        self.name,       o = read_struct('8s',self.fat.iso.data,o)\n        self.ext,        o = read_struct('3s',self.fat.iso.data,o)\n        self.attrib,     o = read_struct('B',self.fat.iso.data,o)\n        self.userattrib, o = read_struct('B',self.fat.iso.data,o)\n        self.undelete,   o = read_struct('b',self.fat.iso.data,o)\n        self.createtime, o = read_struct('H',self.fat.iso.data,o)\n        self.createdate, o = read_struct('H',self.fat.iso.data,o)\n        self.accessdate, o = read_struct('H',self.fat.iso.data,o)\n        self.clusterhi,  o = read_struct('H',self.fat.iso.data,o)\n        self.modifiedti, o = read_struct('H',self.fat.iso.data,o)\n        self.modifiedda, o = read_struct('H',self.fat.iso.data,o)\n        self.clusterlow, o = read_struct('H',self.fat.iso.data,o)\n        self.filesize,   o = read_struct('I',self.fat.iso.data,o)\n\n        self.name = self.name.decode()\n        self.ext  = self.ext.decode()\n\n        self.size += 32\n\n        self.cluster = (self.clusterhi << 16) + self.clusterlow\n\n    def is_dir(self):\n        return bool(self.attrib & 0x10)\n\n    def is_long(self):\n        return bool((self.attrib & 0x0F) == 0x0F)\n\n    def to_dir(self):\n        return FATDirectory(self.fat, self.fat.get_offset(self.cluster))\n\n    def get_offset(self):\n        return self.fat.get_offset(self.cluster)\n\n    def readable_name(self):\n        if self.long_name:\n            return self.long_name\n        if self.ext.strip():\n            return (self.name.strip() + '.' + self.ext.strip()).lower()\n        else:\n            return self.name.strip().lower()\n\n"
  },
  {
    "path": "util/activate.sh",
    "content": "#!/bin/bash\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nexport PATH=\"$DIR/local/bin:$PATH\"\nexport TOOLCHAIN=\"$DIR\"\necho \"$PATH\"\n\n"
  },
  {
    "path": "util/arch.sh",
    "content": "#!/bin/bash\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nif [ -e \"$DIR/../.arch\" ]; then\n\tcat \"$DIR/../.arch\"\n\texit 0\nfi\n\necho \"x86_64\"\n"
  },
  {
    "path": "util/auto-dep.krk",
    "content": "#!/bin/kuroko\nimport os, kuroko, fileio\n\nlet cflags = \"-O2 -g -I. -Iapps -fplan9-extensions -Wall -Wextra -Wno-unused-parameter\"\n\ndef basename(path: str) -> str:\n    return path.strip('/').split('/')[-1]\n\nclass Classifier:\n    dependency_hints = {\n        # Toaru Standard Library\n        '<toaru/kbd.h>':         (None, '-ltoaru_kbd',         []),\n        '<toaru/list.h>':        (None, '-ltoaru_list',        []),\n        '<toaru/hashmap.h>':     (None, '-ltoaru_hashmap',     ['<toaru/list.h>']),\n        '<toaru/tree.h>':        (None, '-ltoaru_tree',        ['<toaru/list.h>']),\n        '<toaru/pex.h>':         (None, '-ltoaru_pex',         []),\n        '<toaru/auth.h>':        (None, '-ltoaru_auth',        []),\n        '<toaru/graphics.h>':    (None, '-ltoaru_graphics',    []),\n        '<toaru/inflate.h>':     (None, '-ltoaru_inflate',     []),\n        '<toaru/drawstring.h>':  (None, '-ltoaru_drawstring',  ['<toaru/graphics.h>']),\n        '<toaru/jpeg.h>':        (None, '-ltoaru_jpeg',        ['<toaru/graphics.h>']),\n        '<toaru/png.h>':         (None, '-ltoaru_png',         ['<toaru/graphics.h>','<toaru/inflate.h>']),\n        '<toaru/rline.h>':       (None, '-ltoaru_rline',       ['<toaru/kbd.h>']),\n        '<toaru/confreader.h>':  (None, '-ltoaru_confreader',  ['<toaru/hashmap.h>']),\n        '<toaru/markup.h>':      (None, '-ltoaru_markup',      ['<toaru/hashmap.h>']),\n        '<toaru/json.h>':        (None, '-ltoaru_json',        ['<toaru/hashmap.h>']),\n        '<toaru/yutani.h>':      (None, '-ltoaru_yutani',      ['<toaru/kbd.h>', '<toaru/list.h>', '<toaru/pex.h>', '<toaru/graphics.h>', '<toaru/hashmap.h>']),\n        '<toaru/decorations.h>': (None, '-ltoaru_decorations', ['<toaru/menu.h>', '<toaru/text.h>', '<toaru/graphics.h>', '<toaru/yutani.h>']),\n        '<toaru/termemu.h>':     (None, '-ltoaru_termemu',     ['<toaru/graphics.h>']),\n        '<toaru/icon_cache.h>':  (None, '-ltoaru_icon_cache',  ['<toaru/graphics.h>', '<toaru/hashmap.h>']),\n        '<toaru/menu.h>':        (None, '-ltoaru_menu',        ['<toaru/yutani.h>', '<toaru/icon_cache.h>', '<toaru/graphics.h>', '<toaru/hashmap.h>']),\n        '<toaru/button.h>':      (None, '-ltoaru_button',      ['<toaru/graphics.h>','<toaru/text.h>', '<toaru/icon_cache.h>']),\n        '<toaru/text.h>':        (None, '-ltoaru_text',        ['<toaru/graphics.h>', '<toaru/hashmap.h>']),\n        '<toaru/markup_text.h>': (None, '-ltoaru_markup_text', ['<toaru/graphics.h>', '<toaru/markup.h>', '<toaru/text.h>']),\n        # Kuroko\n        '<kuroko/kuroko.h>':     ('../../../kuroko/src', '-lkuroko', []),\n    }\n\n    def __init__(self, filename: str):\n        self.export_dynamic_hint = False\n        self.filename  = filename\n        self.includes, self.libs = self._depends()\n\n    def _calculate(self, depends, new):\n        \"\"\"Calculate all dependencies for the given set of new elements.\"\"\"\n        for k in new:\n            if not k in depends:\n                depends.append(k)\n                let other = self.dependency_hints[k][2]\n                depends = self._calculate(depends, other)\n        return depends\n\n    def _sort(self, depends: list[str]) -> list[str]:\n        \"\"\"Sort the list of dependencies so that elements appearing first depend on elements following.\"\"\"\n        let satisfied = []\n        let a = depends[:]\n\n        while set(satisfied) != set(depends):\n            let b = []\n            for k in a:\n                if all(x in satisfied for x in self.dependency_hints[k][2]):\n                    satisfied.append(k)\n                else:\n                    b.append(k)\n            a = b[:]\n        return reversed(satisfied)\n\n    def _depends(self) -> (list[str],list[str]):\n        \"\"\"Calculate include and library dependencies.\"\"\"\n        let lines = []\n        let depends = []\n        with fileio.open(self.filename,'r') as f:\n            lines = f.readlines()\n        for l in lines:\n            if l.startswith('#include'):\n                depends.extend(k for k in list(self.dependency_hints.keys()) if l.startswith('#include ' + k))\n            elif l.startswith('/* auto-dep: export-dynamic */'):\n                self.export_dynamic_hint = True\n        depends = self._calculate([], depends)\n        depends = self._sort(depends)\n        let includes  = []\n        let libraries = []\n        for k in depends:\n            let dep = self.dependency_hints[k]\n            if dep[0]:\n                includes.append('-I' + 'base/usr/include/' + dep[0])\n            if dep[1]:\n                libraries.append(dep[1])\n        return includes, libraries\n\n\ndef todep(name: str) -> (bool, str):\n    \"\"\"Convert a library name to an archive path or object file name.\"\"\"\n    if name.startswith(\"-l\"):\n        name = name.replace(\"-l\",\"\",1)\n        if name.startswith('toaru'):\n            return (True, \"{}/lib{}.so\".format('base/lib', name))\n        elif name.startswith('kuroko'):\n            return (True, \"{}/lib{}.so\".format('base/lib', name))\n        else:\n            return (True, \"{}/lib{}.so\".format('base/usr/lib', name))\n    else:\n        return (False, name)\n\ndef toheader(name: str) -> str:\n    if name.startswith('-ltoaru_'):\n        return name.replace('-ltoaru_','base/usr/include/toaru/') + '.h'\n    else:\n        return ''\n\nif __name__ == \"__main__\":\n    if len(kuroko.argv) < 3:\n        print(\"usage: util/auto-dep.krk command filename\")\n        return 1\n    let command  = kuroko.argv[1]\n    let filename = kuroko.argv[2]\n    let c = Classifier(filename)\n\n    if command == \"--cflags\":\n        print(\" \".join([x for x in c.includes]))\n    elif command == \"--libs\":\n        print(\" \".join([x for x in c.libs]))\n    elif command == \"--deps\":\n        let results = [todep(x) for x in c.libs]\n        let normal = [x[1] for x in results if not x[0]]\n        let order_only = [x[1] for x in results if x[0]]\n        print(\" \".join(normal) + \" | \" + \" \".join(order_only))\n    elif command == \"--build\":\n        os.system(\"gcc {cflags} {extra} {includes} -o {app} {source} {libraries}\".format(\n            cflags=cflags,\n            app=basename(filename).replace('.c++','').replace(\".c\",\"\"),\n            source=filename,\n            headers=\" \".join([toheader(x) for x in c.libs]),\n            libraries=\" \".join([x for x in c.libs]),\n            includes=\" \".join([x for x in c.includes if x is not None]),\n            extra=\"-rdynamic\" if c.export_dynamic_hint else \"\",\n            ))\n    elif command == \"--buildlib\":\n        let libname = basename(filename).replace('.c++','').replace(\".c\",\"\")\n        let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace(\"-ltoaru_\",\"\") != libname]\n        os.system(\"gcc {cflags} {includes} -shared -fPIC -olibtoaru_{lib}.so {source} {libraries}\".format(\n            cflags=cflags,\n            lib=libname,\n            source=filename,\n            headers=\" \".join([toheader(x) for x in c.libs]),\n            libraryfiles=\" \".join([todep(x)[1] for x in _libs]),\n            libraries=\" \".join([x for x in _libs]),\n            includes=\" \".join([x for x in c.includes if x is not None])\n            ))\n    elif command == \"--make\":\n        print(\"base/bin/{app}: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\\n\\t{comp} {extra} {includes} -o $@ $< {libraries}\".format(\n            app=basename(filename).replace('.c++','').replace(\".c\",\"\"),\n            source=filename,\n            headers=\" \".join([toheader(x) for x in c.libs]),\n            libraryfiles=\" \".join([todep(x)[1] for x in c.libs]),\n            libraries=\" \".join([x for x in c.libs]),\n            includes=\" \".join([x for x in c.includes if x is not None]),\n            comp=\"$(CC) $(CFLAGS)\" if '.c++' not in filename else \"$(CXX) $(CXXFLAGS)\",\n            extra=\"-rdynamic\" if c.export_dynamic_hint else \"\"\n            ))\n    elif command == \"--makelib\":\n        let libname = basename(filename).replace('.c++','').replace(\".c\",\"\")\n        let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace(\"-ltoaru_\",\"\") != libname]\n        print(\"base/lib/libtoaru_{lib}.so: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\\n\\t{comp} {includes} -shared -fPIC -o $@ $< {libraries}\".format(\n            lib=libname,\n            source=filename,\n            headers=\" \".join([toheader(x) for x in c.libs]),\n            libraryfiles=\" \".join([todep(x)[1] for x in _libs]),\n            libraries=\" \".join([x for x in _libs]),\n            includes=\" \".join([x for x in c.includes if x is not None]),\n            comp=\"$(CC) $(CFLAGS)\" if '.c++' not in filename else \"$(CXX) $(CXXFLAGS)\",\n            ))\n    elif command == \"--makekurokomod\":\n        let libname = basename(filename).replace('.c++','').replace(\".c\",\"\").replace(\"module_\",\"\")\n        let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace(\"-ltoaru_\",\"\") != libname]\n        print(\"base/lib/kuroko/{lib}.so: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\\n\\t$(CC) $(CFLAGS) {includes} -shared -fPIC -o $@ $< {libraries}\".format(\n            lib=libname,\n            source=filename,\n            headers=\" \".join([toheader(x) for x in c.libs]),\n            libraryfiles=\" \".join([todep(x)[1] for x in _libs]),\n            libraries=\" \".join([x for x in _libs]),\n            includes=\" \".join([x for x in c.includes if x is not None])\n            ))\n\n\n"
  },
  {
    "path": "util/bochsrc.txt",
    "content": "# configuration file generated by Bochs\nplugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, gameport=true, iodebug=true\nconfig_interface: textconfig\ndisplay_library: x\nmemory: host=512, guest=512\nromimage: file=\"/usr/share/bochs/BIOS-bochs-latest\", address=0x00000000, options=none\nvgaromimage: file=\"/usr/share/bochs/VGABIOS-lgpl-latest\"\nboot: cdrom\nfloppy_bootsig_check: disabled=0\n# no floppya\n# no floppyb\nata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14\nata0-master: type=cdrom, path=\"image.iso\", status=inserted, model=\"Generic 1234\", biosdetect=auto\nata0-slave: type=none\nata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15\nata1-master: type=none\nata1-slave: type=none\nata2: enabled=false\nata3: enabled=false\noptromimage1: file=none\noptromimage2: file=none\noptromimage3: file=none\noptromimage4: file=none\noptramimage1: file=none\noptramimage2: file=none\noptramimage3: file=none\noptramimage4: file=none\npci: enabled=1, chipset=i440fx, slot1=pcivga\nvga: extension=vbe, update_freq=5, realtime=1\ncpu: count=2, ips=4000000, quantum=16, model=bx_generic, reset_on_triple_fault=1, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0\ncpuid: level=6, stepping=3, model=3, family=6, vendor_string=\"GenuineIntel\", brand_string=\"              Intel(R) Pentium(R) 4 CPU        \"\ncpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true\ncpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, avx_f16c=false\ncpuid: avx_fma=false, bmi=0, xop=false, fma4=false, tbm=false, x86_64=true, 1g_pages=false\ncpuid: pcid=false, fsgsbase=false, smep=false, smap=false, mwait=true, vmx=1, svm=false\nprint_timestamps: enabled=0\ndebugger_log: -\nmagic_break: enabled=0\nport_e9_hack: enabled=0\nprivate_colormap: enabled=0\nclock: sync=realtime, time0=utc, rtc_sync=1\n# no cmosimage\nlog: -\nlogprefix: %t%e%d\ndebug: action=ignore\ninfo: action=report\nerror: action=report\npanic: action=ask\nkeyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none\nmouse: type=ps2, enabled=false, toggle=ctrl+mbutton\nsound: waveoutdrv=alsa, waveout=none, waveindrv=alsa, wavein=none, midioutdrv=alsa, midiout=none\nspeaker: enabled=true, mode=sound\nparport1: enabled=true, file=none\nparport2: enabled=false\ncom1: enabled=true, mode=null\ncom2: enabled=false\ncom3: enabled=false\ncom4: enabled=false\n"
  },
  {
    "path": "util/build-in-docker-aarch64.sh",
    "content": "#!/bin/bash\n\n# Give other users access to /root\n# (We probably should have just built the build tools somewhere else...)\nchmod o+x /root\nchmod -R o+rw /root/gcc_local/bin\n\n# Who owns this directory?\nNEWUID=`stat -c '%u' .`\n\nif [[ \"$NEWUID\" == \"0\" ]]; then\n    echo \"Are you running this on Docker for Mac? Owner UID is 0, going to use 501 instead.\"\n    NEWUID=501\nfi\n\n# Create a fake user with this name\nuseradd -u $NEWUID local\n\n# Map the build tools\nln -s /root/gcc_local util/local\n\n# Run make as local\nrunuser -u local -- sh -c 'make ARCH=aarch64 util/local/bin/kuroko && make ARCH=aarch64 base/lib/libc.so && make ARCH=aarch64 -j4' || exit 1\n\n# Remove the build tools\nrm util/local\n\n\n"
  },
  {
    "path": "util/build-in-docker.sh",
    "content": "#!/bin/bash\n\n# Give other users access to /root\n# (We probably should have just built the build tools somewhere else...)\nchmod o+x /root\nchmod -R o+rw /root/gcc_local/bin\n\n# Who owns this directory?\nNEWUID=`stat -c '%u' .`\n\nif [[ \"$NEWUID\" == \"0\" ]]; then\n    echo \"Are you running this on Docker for Mac? Owner UID is 0, going to use 501 instead.\"\n    NEWUID=501\nfi\n\n# Create a fake user with this name\nuseradd -u $NEWUID local\n\n# Map the build tools\nln -s /root/gcc_local util/local\n\n# Run make as local\nrunuser -u local -- sh -c 'make util/local/bin/kuroko && make base/lib/libc.so && make -j4' || exit 1\n\n# Remove the build tools\nrm util/local\n\n"
  },
  {
    "path": "util/build-toolchain.sh",
    "content": "DIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\nARCH=x86_64\nTARGET=x86_64-pc-toaru\nPREFIX=\"$DIR/local\"\nSYSROOT=\"$DIR/../base\"\n\n# --disable-multilib\n\ncd $DIR\nmkdir -p $PREFIX/bin\n\nmkdir -p $DIR/build/binutils\ncd $DIR/build/binutils\n../../binutils-gdb/configure --target=$TARGET --prefix=\"$PREFIX\" --with-sysroot=\"$SYSROOT\" --disable-werror --enable-shared\nmake -j8\nmake install\n\nmkdir -p $DIR/build/gcc\ncd $DIR/build/gcc\n../../gcc/configure --target=$TARGET --prefix=\"$PREFIX\" --with-sysroot=\"$SYSROOT\" --enable-languages=c,c++ --enable-shared\nmake -j8 all-gcc\nmake install-gcc\nmake -j8 $TARGET/libgcc/{libgcc.a,crtbegin.o,crtend.o,crtbeginS.o,crtendS.o}\ncp $TARGET/libgcc/{libgcc.a,crtbegin.o,crtend.o,crtbeginS.o,crtendS.o} ../../local/lib/gcc/$TARGET/10.3.0/\n\ncd $DIR/../\nmake ARCH=$ARCH base/lib/libc.so\n\ncd $DIR/build/gcc\nmake -j8 all-target-libgcc\nmake install-target-libgcc\n\ncd $DIR/../\nrm base/lib/libc.so\nmake ARCH=$ARCH base/lib/libc.so\nmake ARCH=$ARCH base/lib/libm.so\n\n#cd $DIR/build/gcc\n#make -j8 all-target-libstdc++-v3\n#make install-target-libstdc++-v3\n"
  },
  {
    "path": "util/createramdisk.py",
    "content": "#!/usr/bin/python3\n\"\"\"\nGenerates, from this source repository, a \"tarramdisk\" - a ustar archive\nsuitable for booting ToaruOS. \n\"\"\"\n\nimport os\nimport tarfile\n\nusers = {\n    'root': 0,\n    'local': 1000,\n    'guest': 1001,\n}\n\nrestricted_files = {\n    'etc/master.passwd': 0o600,\n    'etc/sudoers': 0o600,\n    'tmp': 0o777,\n    'var': 0o755,\n    'bin/sudo': 0o4555,\n    'bin/gsudo': 0o4555,\n}\n\ndef file_filter(tarinfo):\n    # Root owns files by default.\n    tarinfo.uid = 0\n    tarinfo.gid = 0\n\n    if tarinfo.name.startswith('home/'):\n        # Home directory contents are owned by their users.\n        user = tarinfo.name.split('/')[1]\n        tarinfo.uid = users.get(user,0)\n        tarinfo.gid = tarinfo.uid\n    elif tarinfo.name in restricted_files:\n        tarinfo.mode = restricted_files[tarinfo.name]\n\n    if tarinfo.name.startswith('usr/include/kuroko') and tarinfo.type == tarfile.SYMTYPE:\n        return None\n\n    if tarinfo.name.startswith('src'):\n        # Let local own the files here\n        tarinfo.uid = users.get('local')\n        tarinfo.gid = tarinfo.uid\n        # Skip object files\n        if tarinfo.name.endswith('.so') or tarinfo.name.endswith('.o') or tarinfo.name.endswith('.sys'):\n            return None\n\n    return tarinfo\n\ndef symlink(file,target):\n    ti = tarfile.TarInfo(file)\n    ti.type = tarfile.SYMTYPE\n    ti.linkname = target\n    return ti\n\nwith tarfile.open('ramdisk.igz','w:gz') as ramdisk:\n    ramdisk.add('base',arcname='/',filter=file_filter)\n\n    ramdisk.add('.',arcname='/src',filter=file_filter,recursive=False) # Add a src directory\n    ramdisk.add('apps',arcname='/src/apps',filter=file_filter)\n    ramdisk.add('kernel',arcname='/src/kernel',filter=file_filter)\n    ramdisk.add('linker',arcname='/src/linker',filter=file_filter)\n    ramdisk.add('lib',arcname='/src/lib',filter=file_filter)\n    ramdisk.add('libc',arcname='/src/libc',filter=file_filter)\n    ramdisk.add('boot',arcname='/src/boot',filter=file_filter)\n    ramdisk.add('modules',arcname='/src/modules',filter=file_filter)\n    if os.path.exists('tags'):\n        ramdisk.add('tags',arcname='/src/tags',filter=file_filter)\n    ramdisk.add('util/auto-dep.krk',arcname='/bin/auto-dep.krk',filter=file_filter)\n    ramdisk.add('kuroko/src/kuroko',arcname='/usr/include/kuroko',filter=file_filter)\n    ramdisk.addfile(symlink('bin/sh','esh'))\n    ramdisk.addfile(symlink('bin/mandelbrot','julia'))\n    ramdisk.addfile(symlink('bin/fgrep','grep'))\n\n\n"
  },
  {
    "path": "util/docker/Dockerfile",
    "content": "FROM ubuntu:20.04\n\nENV DEBIAN_FRONTEND=noninteractive\n\nRUN apt-get update \\\n\t&& apt-get install -y build-essential python3 xorriso genext2fs mtools gnu-efi git automake autoconf wget libgmp-dev libmpfr-dev libmpc-dev flex bison texinfo dosfstools \\\n\t&& rm -rf /var/lib/apt/lists/* /var/cache/apt/apt-file/*\n"
  },
  {
    "path": "util/docker/README.md",
    "content": "1. Build the base image from the Dockerfile.\n\n```bash\ndocker build .\n```\n\n2. Run the docker image with `-it bash`.\n\n3. Clone the repository over `https`:\n\n```bash\ncd\ngit clone --recurse-submodules https://github.com/toaruos/misaka\ncd misaka\nutil/build-toolchain.sh\ncd util/build/binutils\nwhile [[ -e confdir3/confdir3 ]]; do mv confdir3/confdir3 confdir3a; rmdir confdir3; mv confdir3a confdir3; done; rmdir confdir3\ncd ../../..\nmv local /root/gcc_local\ncd /root\nrm -rf misaka\n```\n\n"
  },
  {
    "path": "util/gen_wcwidth.krk",
    "content": "import fileio\nimport os\n\nlet eaw_txt = '/tmp/EastAsianWidth.txt'\n\nos.system(f\"wget -O '{eaw_txt}' https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt\")\n\nlet lines\n\nwith fileio.open(eaw_txt,'r') as f:\n    lines = f.readlines()\n\ndef classify(cp, gr, ct):\n    # U+00AD: Soft hyphen - other things seem to want this to be 1\n    if cp == 0xAD: return 1\n    # Low control codes\n    if cp < 0x20:  return -1\n    # Del, higher control codes\n    if cp >= 0x7f and cp < 0xa0: return -1\n    # Surrogates\n    if cp >= 0xd800 and cp <= 0xdfff: return -1\n\n    # Combining characters\n    if ct in ['Cf','Me','Mn']: return 0\n\n    # Hangul jamo\n    if cp >= 0x1160 and cp <= 0x11FF: return 0\n\n    # Zero-width space\n    if cp == 0x200b: return 0\n\n    # Mark neutral, narrow, ambigus, and half-width as 1\n    if gr in ['N','Na','A','H']: return 1\n\n    # Mark wide and full-width as 2\n    if gr in ['W','F']: return 2\n\n    # Mark everything else as invalid\n    return -1\n\nlet classes = [None] * 0x110000\n\nfor line in lines:\n    if !line or line.startswith('#') or ';' not in line:\n        continue\n    line = line.strip()\n\n    let codepoint, rest = line.split(';',1)\n    let group, comment = rest.split('#',1)\n\n    group = group.strip()\n    comment = comment.strip()\n\n    let ctype = comment.split(' ')[0]\n\n    # Is this a range?\n    if '..' in codepoint:\n        let start, end = codepoint.split('..',1)\n        start = int(f'0x{start}',0)\n        end = int(f'0x{end}',0)\n        for i = start; i <= end; i++:\n            classes[i] = classify(i, group, ctype)\n    else:\n        codepoint = int(f'0x{codepoint}',0)\n        classes[codepoint] = classify(codepoint, group, ctype)\n\nfor i in range(1,0x110000):\n    if classes[i] is None: classes[i] = -1\n\nprint('''/* Generated by util/gen_wcwidth.krk */\n#include <wchar.h>\n\nint wcwidth(wchar_t wc) {\n\\tif (wc == 0) return 0;''')\n\nlet last = None\n\nfor i in range(1,0x110000):\n    if last is not None and classes[i] != last:\n        print(f'\\telse if (wc < {hex(i)}) return {last};')\n\n    last = classes[i]\n\nprint(f'\\telse if (wc < 0x110000) return {last};\\n\\treturn -1;\\n}}')\n"
  },
  {
    "path": "util/generate-etc-issue.sh",
    "content": "#!/bin/bash\n\nMAJOR=$(grep __kernel_version_major kernel/sys/version.c | sed s'/.*= \\(.*\\);/\\1/')\nMINOR=$(grep __kernel_version_minor kernel/sys/version.c | sed s'/.*= \\(.*\\);/\\1/')\n\ncat << EOF\nToaruOS ${MAJOR}.${MINOR} \\n \\l\n\nEOF\n"
  },
  {
    "path": "util/generate-etc-os-release.sh",
    "content": "#!/bin/bash\n\nMAJOR=$(grep __kernel_version_major kernel/sys/version.c | sed s'/.*= \\(.*\\);/\\1/')\nMINOR=$(grep __kernel_version_minor kernel/sys/version.c | sed s'/.*= \\(.*\\);/\\1/')\nLOWER=$(grep __kernel_version_lower kernel/sys/version.c | sed s'/.*= \\(.*\\);/\\1/')\n\ncat << EOF\nPRETTY_NAME=\"ToaruOS ${MAJOR}.${MINOR}\"\nNAME=\"ToaruOS\"\nVERSION_ID=\"${MAJOR}.${MINOR}.${LOWER}\"\nVERSION=\"${MAJOR}.${MINOR}.${LOWER}\"\nID=toaru\nHOME_URL=\"https://toaruos.org/\"\nSUPPORT_URL=\"https://github.com/klange/toaruos\"\nBUG_REPORT_URL=\"https://github.com/klange/toaruos\"\nEOF\n"
  },
  {
    "path": "util/generate-release-notes.sh",
    "content": "#!/bin/bash\nVERSION=$(git describe --exact-match --tags)\nLAST=$(git describe --abbrev=0 --tags ${VERSION}^)\nCHANGELOG=$(git log --pretty=format:%s ${LAST}..HEAD | grep ':' | sed -re 's/([^:]*)\\:/- \\`\\1\\`\\:/' | sort)\ncat <<NOTES\n# ToaruOS ${VERSION}\n\nPut a screenshot here.\n\n## What's New in ${VERSION}?\n\nDescribe the release here.\n\n## What is ToaruOS?\n\nToaruOS is a hobbyist, educational operating system for x86-64 PCs, focused primarily on use in virtual machines. It provides a Unix-like environment, complete with a graphical desktop interface, shared libraries, feature-rich terminal emulator, and support for running, GCC, Quake, and several other ports. The core of ToaruOS, provided by the CD images in this release, is built completely from scratch. The bootloader, kernel, drivers, C standard library, and userspace applications are all original software created by the authors, as are the graphical assets.\n\n## Who wrote ToaruOS?\n\nToaruOS is primarily written by a single maintainer, with several contributions from others. A complete list of contributors is available from [AUTHORS](https://github.com/klange/toaruos/blob/master/AUTHORS).\n\n## Running ToaruOS\n\nIt is recommended that you run ToaruOS in a virtual machine / emulator, for maximum compatibility. ToaruOS's driver support is limited, and running on real \"bare metal\", while possible, does not provide the most complete experience of the OS's capabilities except on very particular hardware. ToaruOS is regularly tested in VirtualBox, QEMU, and VMWare Player, and can be successfully booted (with poor performance) in Bochs. ToaruOS is intended to run from a live CD, though it is possible to install to a hard disk. Additional details on running ToaruOS in different virtual machines is available [from the README](https://github.com/klange/toaruos#running-toaruos).\n\n## Release Files\n\n\\`image.iso\\` is the standard build of ToaruOS, built by the Github Actions CI workflow. It uses ToaruOS's native bootloaders and should work in most virtual machines using BIOS.\n\n## Changelog\n${CHANGELOG}\n\n## Known Issues\n- The SMP scheduler is known to have performance issues.\n- Several utilities, libc functions, and hardware drivers are missing functionality.\n- There are many known security issues with ToaruOS. You should not use ToaruOS in a production environment - it is a hobby project, not a production operating system. If you find security issues in ToaruOS and would like to responsibly report them, please file a regular issue report here on GitHub.\nNOTES\n"
  },
  {
    "path": "util/gensym.krk",
    "content": "#!/usr/bin/env kuroko\n'''\n@brief Generate symbol table.\n'''\nimport fileio\n\nlet size = 'quad'\n\ndef extern(sym):\n    print(f'.extern {sym}')\n    print(f'.type {sym}, @function')\n\ndef entry(sym):\n    print(f'.{size} {sym}')\n    print(f'.asciz \"{sym}\"')\n\nlet ignore = ['abs','kernel_symbols_start','kernel_symbols_end','_GLOBAL_OFFSET_TABLE_']\nlet source = (x.strip() for x in fileio.stdin.readlines())\nlet symbols = set(x.split()[0] for x in source if not x.endswith(':'))\nlet lines  = sorted(x for x in symbols if x not in ignore)\n\n\nprint('.section .symbols')\nprint()\nfor name in lines:\n    extern(name)\n\nprint('.global kernel_symbols_start')\nprint('kernel_symbols_start:')\nprint()\nfor name in lines:\n    entry(name)\n\nprint('.global kernel_symbols_end')\nprint('kernel_symbols_end:')\n"
  },
  {
    "path": "util/init.krk",
    "content": "#!/bin/kuroko\nimport os\nimport kuroko\n\nlet serial = os.open('/dev/ttyS1',os.O_RDWR)\nif serial == 0:\n    os.dup(serial)\n    os.dup(serial)\n\n# Say hello\nlet uname = os.uname()\nprint(f\"Test script running on {uname['sysname']} {uname['release']} {uname['version']} ({uname['machine']}), Kuroko {kuroko.version}\")\n\nprint(\"====\")\n\n# Perform tests\nos.system(\"/bin/insmod /mod/ahci.ko\")\n\n# Reboot\nprint(\"====\")\nprint('Tests completed, exiting.')\nos.execv('/bin/reboot',['/bin/reboot'])\n"
  },
  {
    "path": "util/libm.c",
    "content": "/* Stub */\n"
  },
  {
    "path": "util/make-version",
    "content": "#!/bin/bash\n\nVERSION=`git rev-parse --short HEAD`\n\nX=$(git status -s | grep -q '^.M')\nif [ $? -eq 0 ]; then\n    VERSION=\"$VERSION-dirty\"\nfi\n\necho -n $VERSION\n"
  },
  {
    "path": "util/make_mbr.krk",
    "content": "#!/usr/bin/env kuroko\nfrom util import ISO\n\nlet image = ISO('image.iso')\nlet cdfile = image.get_file('/boot.sys')\n\nprint(f'--defsym BOOT_FILE_SIZE={cdfile.extent_length_lsb} --defsym BOOT_FILE_OFFSET={cdfile.extent_start_lsb * 2048}')\n\n"
  },
  {
    "path": "util/mkdisk.sh",
    "content": "#!/bin/bash\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nOUT=$1\nIN=$2\n\nOUTDIR=`dirname $1`\n\n# Calculate required space\nSPACE_REQ=$(du -s -B 2048 \"$DIR/../fatbase\" | cut -f 1)\nlet \"SIZE = (($SPACE_REQ + 25) * 2048)\"\nSPC=1\n\n\n# Create empty FAT image\nrm -f $OUT\nmkdir -p cdrom\nfallocate -l ${SIZE} $OUT || dd if=/dev/zero bs=1 count=${SIZE} of=$OUT\nmkfs.fat -s $SPC -S 2048 $OUT\n\n#echo \"Turning $IN into $OUT\"\n\n# Add files\nfor i in $(find $IN)\ndo\n    if [[ $i == $IN ]]; then\n        continue\n    fi\n    OUT_FILE=`echo $i | sed s\"/^$IN/$OUTDIR/\"`\n    IN_FILE=`echo $i | sed s\"/^$IN//\"`\n    #echo $IN_FILE  $OUT_FILE\n    if [ -d \"$i\" ]; then\n        mmd -i $OUT $IN_FILE || exit 1\n        mkdir -p $OUT_FILE || exit 1\n    else\n        mcopy -i $OUT $i '::'$IN_FILE || exit 1\n        touch $OUT_FILE\n    fi\ndone\n\nrm -f cdrom/efi/boot/bootia32.efi # Otherwise virtualbox may erroneously try to load from this\nrm -f cdrom/efi/boot/bootx64.efi # Same\n\n"
  },
  {
    "path": "util/update-extents.krk",
    "content": "#!/usr/bin/env kuroko\nimport fileio\nfrom util import ISO, FAT\n\nlet image = ISO('image.iso')\nlet fat = image.root.find('FAT.IMG')\nlet fatfs = FAT(image, fat.extent_start_lsb * image.sector_size)\n\ndef process(fatfile, path):\n    if fatfile.is_long():\n        return\n    if fatfile.readable_name() == '.':\n        return\n    if fatfile.readable_name() == '..':\n        return\n    if fatfile.is_dir():\n        for i in fatfile.to_dir().list():\n            process(i, path + fatfile.readable_name() + '/')\n    else:\n        let cdfile = image.get_file(path + fatfile.readable_name())\n        if not cdfile:\n            if fatfile.readable_name() != 'bootia32.efi' and fatfile.readable_name() != 'bootx64.efi':\n                print(\"Warning:\", fatfile.readable_name(), \"not found in ISO\")\n        else:\n            cdfile.extent_start_lsb = fatfile.get_offset() // 2048\n            cdfile.extent_length_lsb = fatfile.filesize\n            cdfile.write_extents()\n\n\nfor i in fatfs.root.list():\n    process(i,'/')\n\nwith fileio.open('image.iso','wb') as f:\n    f.write(bytes(image.data))\n\n"
  },
  {
    "path": "util/valid-modules.sh",
    "content": "#!/bin/bash\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" && pwd )\"\n\nfor mod in $DIR/../modules/*.c; do\n\tfgrep -q \"@package $1\" \"$mod\" || continue\n\techo -n $mod | sed s'#.*/\\([^/]*\\)\\.c#\\1 #'\ndone\n\necho\n"
  }
]