Browse Source

Merge nih

K. Lange 2 years ago
parent
commit
cb45fbc0c4
100 changed files with 27849 additions and 688 deletions
  1. 41 91
      .gitignore
  2. 0 24
      .travis.yml
  3. 28 30
      LICENSE
  4. 331 465
      Makefile
  5. 155 13
      README.md
  6. 0 65
      README_OLD.md
  7. 223 0
      apps/about.c
  8. 173 0
      apps/background.c
  9. 55 0
      apps/beep.c
  10. 5999 0
      apps/bim.c
  11. 162 0
      apps/cat-img.c
  12. 63 0
      apps/cat.c
  13. 135 0
      apps/chmod.c
  14. 19 0
      apps/clear.c
  15. 2653 0
      apps/compositor.c
  16. 87 0
      apps/cp.c
  17. 19 0
      apps/cursor-off.c
  18. 59 0
      apps/date.c
  19. 134 0
      apps/drawlines.c
  20. 124 0
      apps/echo.c
  21. 49 0
      apps/env.c
  22. 10 0
      apps/false.c
  23. 407 0
      apps/fetch.c
  24. 45 0
      apps/fgrep.c
  25. 510 0
      apps/file-browser.c
  26. 97 0
      apps/font-server.c
  27. 96 0
      apps/free.c
  28. 96 0
      apps/getty.c
  29. 589 0
      apps/glogin-provider.c
  30. 134 0
      apps/glogin.c
  31. 134 0
      apps/gsudo.c
  32. 72 0
      apps/head.c
  33. 13 0
      apps/hello.c
  34. 242 0
      apps/help-browser.c
  35. 199 0
      apps/hexify.c
  36. 37 0
      apps/hostname.c
  37. 235 0
      apps/imgviewer.c
  38. 146 0
      apps/init.c
  39. 18 0
      apps/insmod.c
  40. 542 0
      apps/irc.c
  41. 349 0
      apps/julia.c
  42. 138 0
      apps/kcmdline.c
  43. 16 0
      apps/kdebug.c
  44. 126 0
      apps/kill.c
  45. 222 0
      apps/killall.c
  46. 61 0
      apps/live-session.c
  47. 60 0
      apps/ln.c
  48. 32 0
      apps/login-loop.c
  49. 163 0
      apps/login.c
  50. 485 0
      apps/ls.c
  51. 132 0
      apps/lspci.c
  52. 56 0
      apps/menu.c
  53. 240 0
      apps/migrate.c
  54. 126 0
      apps/mixerctl.c
  55. 71 0
      apps/mkdir.c
  56. 29 0
      apps/mount.c
  57. 41 0
      apps/mv.c
  58. 39 0
      apps/nslookup.c
  59. 923 0
      apps/nyancat.c
  60. 0 0
      apps/nyancat/animation.h
  61. 0 0
      apps/nyancat/telnet.h
  62. 1288 0
      apps/panel.c
  63. 78 0
      apps/piano.c
  64. 120 0
      apps/pidof.c
  65. 205 0
      apps/plasma.c
  66. 44 0
      apps/play.c
  67. 308 0
      apps/pong.c
  68. 289 0
      apps/ps.c
  69. 185 0
      apps/pstree.c
  70. 19 0
      apps/pwd.c
  71. 86 0
      apps/qemu-display-hack.c
  72. 187 0
      apps/qemu-fwcfg.c
  73. 256 0
      apps/readelf.c
  74. 29 0
      apps/readlink.c
  75. 17 0
      apps/reboot.c
  76. 31 0
      apps/rm.c
  77. 156 0
      apps/sdf-demo.c
  78. 148 0
      apps/serial-console.c
  79. 50 0
      apps/session.c
  80. 48 0
      apps/set-resolution.c
  81. 1811 0
      apps/sh.c
  82. 27 0
      apps/sleep.c
  83. 100 0
      apps/sort.c
  84. 92 0
      apps/stat.c
  85. 330 0
      apps/stty.c
  86. 90 0
      apps/sudo.c
  87. 17 0
      apps/sysfunc.c
  88. 125 0
      apps/sysinfo.c
  89. 62 0
      apps/tee.c
  90. BIN
      apps/terminal-font.h
  91. 273 0
      apps/terminal-palette.h
  92. 1298 0
      apps/terminal-vga.c
  93. 2412 0
      apps/terminal.c
  94. 25 0
      apps/test-conf.c
  95. 5 0
      apps/test.sh
  96. 110 0
      apps/toaru_logo.h
  97. 48 0
      apps/toggle-abs-mouse.c
  98. 30 0
      apps/touch.c
  99. 10 0
      apps/true.c
  100. 0 0
      apps/ttysize.c

+ 41 - 91
.gitignore

@@ -1,94 +1,44 @@
-*.aux
-*.idx
-*.ilg
-*.ind
-*.log
+*.so
+*.a
 *.o
-*.out
-*.pdf
-*.swp
-*.swn
-*.toc
-*.ko
-*.img.gz
-
-# Makefile magic
-/.compositor-check
-/.config
-/.passed
-/.userspace-check
-/.disk_size
-/.ramdisk_size
-/.toolchain
-
-/util/*.img
-/util/*.vmdk
-/util/*.vdi
-/util/bin/
-
-/hdd/bin/
-/hdd/boot/
-/hdd/home/local/
-/hdd/home/root/
-/hdd/etc/hostname
-
-/hdd/lib
-/hdd/opt
-
-/hdd/usr/bin
-/hdd/usr/etc
-/hdd/usr/i686-pc-toaru
-/hdd/usr/include
-/hdd/usr/lib
-/hdd/usr/libexec
-/hdd/usr/local
-/hdd/usr/man
-/hdd/usr/share/aclocal
-/hdd/usr/share/doc
-/hdd/usr/share/games
-/hdd/usr/share/gcc-4.6.*
-/hdd/usr/share/gtk-doc
-/hdd/usr/share/info
-/hdd/usr/share/locale
-/hdd/usr/share/man
-/hdd/usr/share/tabset
-/hdd/usr/share/terminfo
-/hdd/usr/share/vim
-/hdd/usr/share/icons/external
-/hdd/usr/share/fontconfig
-/hdd/usr/share/xml
-/hdd/usr/python
-/hdd/usr/gcc
-
-# pseudo-submodules
-/toaru-pdfviewer
-/toaru-python
-/pycairo
-
-# Generated files / targets
-/kernel/symbols.s
-/kernel/symbols.S
-/tags
-/toaruos-disk.img
-/toaruos-kernel
-/toaruos.iso
-/toaruos-big.iso
-/modpack.kop
-/cdrom/
-/kernel-headers.img
-
-# Toolchain
-/toolchain/build
-/toolchain/local
-/toolchain/tarballs
-
-# Tool-generated files
-/qemu-vlan0.pcap
-# gdb history can end up all sorts of places
+*.iso
+*.efi
 .gdb_history
 
-/contrib
-
-# Vagrant configs shouldn't end up in git
-/.vagrant
-/Vagrantfile
+/.make/*
+/base/bin/*
+/cdrom/kernel
+/cdrom/netinit
+/cdrom/mod/*
+/cdrom/ramdisk.img
+/cdrom/boot.sys
+/cdrom/fat.img
+/fatbase/kernel
+/fatbase/netinit
+/fatbase/mod/*
+/fatbase/ramdisk.img
+/fatbase/efi/boot/bootia32.efi
+/util/tarballs
+/util/build
+/util/local
+/util/devtable
+/util/tmp-gcc
+/kernel/symbols.S
+/base/usr/python
+/base/usr/bin/*
+/base/usr/lib/*
+/base/usr/share/python-demos
+/base/usr/share/help
+
+# Generic
+/base/usr/share/aclocal
+/base/usr/share/gtk-doc
+
+# Freetype
+/base/usr/include/freetype2
+/base/usr/include/ft2build.h
+/base/usr/share/fonts
+
+# Cairo + Pixman
+/base/usr/include/cairo
+/base/usr/include/pixman-1

+ 0 - 24
.travis.yml

@@ -1,24 +0,0 @@
-language: c
-script:
-  - docker run -v `pwd`:/opt/tree -w /opt/tree -t toaruos/build-tools:test util/build-travis.sh
-sudo: required
-dist: trusty
-serivces:
-  - docker
-before_install:
-  - docker pull toaruos/build-tools:test
-notifications:
-  irc:
-    channels:
-      - secure: "YIH2do6BypI1ZiXONyldvW/xt8O2j/PDb3sPzGUwJql08pItH3t3MPN3JHtKEBU7+/yFB58HbA2C8B1gu/oAGs2tqMCFYiapVUYYv5CudvMt+XkBzxgKRFwPcvPtz+lAjVbIM0SXDdlrfjczDGKPnEvCYBIu+ZYlz8dgn5DEVX8="
-    use_notice: false
-    skip_join: true
-deploy:
-  provider: releases
-  api_key:
-    secure: Lcs5kHe9HAwcVLdm8c0FkAb77U9yzUQdI5PmTU1AnFyHtrPmW1+9d6HeYSPLHm+cPAUdWKqouJNKW7s76Sqhz+4IDcwro7VkbV/fd9NFQCOf9Jb5QScmCZndmYVQUiUmy/7wVqGdy+vatKEsnngctT7aVVhJ5SrWoCPmcttUez8=
-  file: toaruos.iso
-  skip_cleanup: true
-  overwrite: true
-  on:
-    tags: true

+ 28 - 30
LICENSE

@@ -1,35 +1,33 @@
-Copyright (c) 2011-2017 Kevin Lange.  All rights reserved.
 
-                          Dedicated to the memory of
-                               Dennis Ritchie
-                                  1941-2011
+University of Illinois/NCSA Open Source License 
 
-Developed by: ToAruOS Kernel Development Team
-              http://toaruos.org
+Copyright (c) 2011-2018 K Lange, et al. (hereafter [fullname]). All rights reserved. 
 
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to
-deal with the Software without restriction, including without limitation the
-rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-sell copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-  1. Redistributions of source code must retain the above copyright notice,
-     this list of conditions and the following disclaimers.
-  2. Redistributions in binary form must reproduce the above copyright
-     notice, this list of conditions and the following disclaimers in the
-     documentation and/or other materials provided with the distribution.
-  3. Neither the names of the ToAruOS Kernel Development Team, Kevin Lange,
-     nor the names of its contributors may be used to endorse
-     or promote products derived from this Software without specific prior
-     written permission.
+Developed by: ToaruOS (hereafter [project])
+                  
+Permission is hereby granted, free of charge, to any person 
+obtaining a copy of this software and associated documentation files 
+(the "Software"), to deal with the Software without restriction, 
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software, 
+and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-WITH THE SOFTWARE.
+* Redistributions of source code must retain the above copyright notice, 
+  this list of conditions and the following disclaimers.
 
-Additional licenses are available from hdd/usr/share/help/licenses, or through
-the Help Browser > Contents > Licenses within the OS.
+* Redistributions in binary form must reproduce the above copyright 
+  notice, this list of conditions and the following disclaimers in the 
+  documentation and/or other materials provided with the distribution.
+
+* Neither the names of [fullname], [project] nor the names of its 
+  contributors may be used to endorse or promote products derived from
+  this Software without specific prior written permission.
+  
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
+CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH
+THE SOFTWARE.

+ 331 - 465
Makefile

@@ -1,511 +1,377 @@
-# ToaruOS Build Scripts
-ifneq ($(MAKECMDGOALS),toolchain)
- ifeq ($(TOOLCHAIN),)
-  $(error $(shell util/helpful-toolchain-error.sh))
- else
-  $(shell util/cache-toolchain.sh)
- endif
+ifeq ($(TOOLCHAIN),)
+  ifeq ($(shell util/check.sh),y)
+    export PATH := $(shell util/activate.sh)
+  else
+    FOO := $(shell util/prompt.sh)
+    ifeq ($(shell util/check.sh),y)
+      export PATH := $(shell util/activate.sh)
+    else
+      $(error "No toolchain, and you did not ask to build it.")
+    endif
+  endif
 endif
 
-KERNEL_TARGET=i686-elf
-USER_TARGET=i686-pc-toaru
-
-# Pretty output utilities.
-BEG = util/mk-beg
-END = util/mk-end
-INFO = util/mk-info
-ERRORS = 2>>/tmp/.`whoami`-build-errors || util/mk-error
-ERRORSS = >>/tmp/.`whoami`-build-errors || util/mk-error
-BEGRM = util/mk-beg-rm
-ENDRM = util/mk-end-rm
-
-# Rules start here.
-.PHONY: all system install test toolchain userspace modules cdrom fix-cd
-.PHONY: clean clean-soft clean-hard clean-user clean-mods clean-core clean-disk clean-once
-.PHONY: run vga term headless curses quick
-.PHONY: debug debug-vga debug-term debug-curses
-.PHONY: virtualbox virtualbox-cdrom run-cdrom
-
 # Prevents Make from removing intermediary files on failure
 .SECONDARY:
 
 # Disable built-in rules
 .SUFFIXES:
 
-all: $(shell util/detect-make-all.sh)
-
-toolchain:
-	@cd toolchain; ./toolchain-build.sh
-
-###########################
-# Emulator Pseudo-targets #
-###########################
-
-# Specify which modules should be included on startup.
-# There are a few modules that are kinda required for a working system
-# such as all of the dependencies needed to mount the root partition.
-# We can also include things like the debug shell...
-# Note that ordering matters - list dependencies first.
-BOOT_MODULES := zero random serial
-BOOT_MODULES += procfs tmpfs ata
-#BOOT_MODULES += dospart
-BOOT_MODULES += ext2
-BOOT_MODULES += debug_shell
-BOOT_MODULES += ps2mouse ps2kbd
-BOOT_MODULES += lfbvideo
-BOOT_MODULES += vidset
-BOOT_MODULES += packetfs
-BOOT_MODULES += snd
-BOOT_MODULES += pcspkr
-BOOT_MODULES += ac97
-BOOT_MODULES += net rtl
-
-# This is kinda silly. We're going to form an -initrd argument..
-# which is basically -initrd "hdd/mod/%.ko,hdd/mod/%.ko..."
-# for each of the modules listed above in BOOT_MODULES
-COMMA := ,
-EMPTY :=
-SPACE := $(EMPTY) $(EMPTY)
-BOOT_MODULES_X = -initrd "$(subst $(SPACE),$(COMMA),$(foreach mod,$(BOOT_MODULES),hdd/mod/$(mod).ko))"
-
-# QEMU Configuration
-EMU = qemu-system-i386
-
-# Force the SDL backend with no frame and English (US) keyboard.
-EMUARGS  = -sdl -no-frame -k en-us
-# 1GB of RAM
-EMUARGS += -m 1024
-# Bochs VBE display device
-EMUARGS += -vga std
-# Realtime clock based on localtime (we don't NTP or support timezone configs yet)
-EMUARGS += -rtc base=localtime
-# Network hardware: RTL8139, usermode network emulation.
-EMUARGS += -net nic,model=rtl8139 -net user
-# Enable TCP dumps for monitoring.
-EMUARGS += -net dump
-# Sound hardware: Intel AC'97, PC beeper
-EMUARGS += -soundhw pcspk,ac97
-# Enable KVM if available, or fall back to TCG
-EMUARGS += -M accel=kvm:tcg
-
-# For development images, load the kernel, modules, hard disk.
-EMUKARGS  = -kernel toaruos-kernel
-EMUKARGS += $(BOOT_MODULES_X)
-EMUKARGS += -hda toaruos-disk.img
-
-# These arguments are passed to the kernel command line.
-DISK_ROOT = root=/dev/hda
-VID_QEMU  = vid=qemu,,1280,,720
-START_VGA = start=--vga
-START_SINGLE = start=--single
-START_LIVE = start=live-welcome
-WITH_LOGS = logtoserial=1
-
-# Various different quick options
-run: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -append "$(VID_QEMU) $(DISK_ROOT)"
-quick: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -append "$(VID_QEMU) $(DISK_ROOT) start=quick-launch"
-debug: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -serial stdio -append "$(VID_QEMU) $(WITH_LOGS) $(DISK_ROOT)"
-vga: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -append "$(START_VGA) $(DISK_ROOT)"
-debug-vga: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -serial stdio -append "$(WITH_LOGS) $(START_VGA) $(DISK_ROOT)"
-term: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -append "$(VID_QEMU) $(START_SINGLE) $(DISK_ROOT)"
-debug-term: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -serial stdio -append "$(VID_QEMU) $(START_SINGLE) $(WITH_LOGS) $(DISK_ROOT)"
-headless: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -display none -append "$(START_VGA) $(DISK_ROOT)"
-curses: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -curses -append "$(START_VGA) $(DISK_ROOT)"
-debug-curses: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -serial file:serial-debug.log -curses -append "$(WITH_LOGS) $(START_VGA) $(DISK_ROOT)"
-live: system
-	${EMU} ${EMUARGS} ${EMUKARGS} -append "$(VID_QEMU) $(START_LIVE) $(DISK_ROOT)"
-
-# Run the cdrom
-run-cdrom: toaruos.iso
-	${EMU} ${EMUARGS} -cdrom toaruos.iso
-
-# Run VirtualBox
-virtualbox: system
-	util/run-virtualbox.sh
-virtualbox-cdrom: toaruos.iso
-	util/run-virtualbox-cdrom.sh
-
-# Run the test suite
-test: system
-	expect util/test.exp
-
-################
-#    Kernel    #
-################
-
-# Kernel build flags
-CFLAGS  = -O2 -std=c99
-CFLAGS += -finline-functions -ffreestanding
-CFLAGS += -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wno-format
-CFLAGS += -pedantic -fno-omit-frame-pointer
-CFLAGS += -D_KERNEL_
-ASFLAGS = --32
-
-# Build kernel with bare elf toolchain
-KCC = $(KERNEL_TARGET)-gcc
-KNM = $(KERNEL_TARGET)-nm
-KCXX= $(KERNEL_TARGET)-g++
-KAR = $(KERNEL_TARGET)-ar
-KAS = $(KERNEL_TARGET)-as
-KSTRIP = $(KERNEL_TARGET)-strip
-
-# Kernel autoversioning with git sha
-CFLAGS += -DKERNEL_GIT_TAG=`util/make-version`
-
-# All of the core parts of the kernel are built directly.
+all: image.iso
+
+TARGET_TRIPLET=i686-pc-toaru
+
+# Userspace flags
+
+CC=$(TARGET_TRIPLET)-gcc
+AR=$(TARGET_TRIPLET)-ar
+AS=$(TARGET_TRIPLET)-as
+CFLAGS= -O3 -g -std=gnu99 -I. -Iapps -pipe -mmmx -msse -msse2 -fplan9-extensions -Wall -Wextra -Wno-unused-parameter
+
+##
+# C library objects from libc/ C sources (and setjmp, which is assembly)
+LIBC_OBJS  = $(patsubst %.c,%.o,$(wildcard libc/*.c))
+LIBC_OBJS += $(patsubst %.c,%.o,$(wildcard libc/*/*.c))
+LIBC_OBJS += libc/setjmp.o
+LC=base/lib/libc.so
+
+##
+#  APPS      = C sources from apps/
+#  APPS_X    = binaries
+#  APPS_Y    = generated makefiles for binaries (except init)
+#  APPS_SH   = shell scripts to copy to base/bin/ and mark executable
+#  APPS_SH_X = destinations for shell scripts
+APPS=$(patsubst apps/%.c,%,$(wildcard apps/*.c))
+APPS_X=$(foreach app,$(APPS),base/bin/$(app))
+APPS_Y=$(foreach app,$(filter-out init,$(APPS)),.make/$(app).mak)
+APPS_SH=$(patsubst apps/%.sh,%.sh,$(wildcard apps/*.sh))
+APPS_SH_X=$(foreach app,$(APPS_SH),base/bin/$(app))
+
+##
+# LIBS   = C sources from lib/
+# LIBS_X = Shared libraries (.so)
+# LIBS_Y = Generated makefiles for libraries
+LIBS=$(patsubst lib/%.c,%,$(wildcard lib/*.c))
+LIBS_X=$(foreach lib,$(LIBS),base/lib/libtoaru_$(lib).so)
+LIBS_Y=$(foreach lib,$(LIBS),.make/$(lib).lmak)
+
+##
+# Files that must be present in the ramdisk (apps, libraries)
+RAMDISK_FILES= ${APPS_X} ${APPS_SH_X} ${LIBS_X} base/lib/ld.so base/lib/libm.so
+
+# Kernel / module flags
+
+KCC = $(TARGET_TRIPLET)-gcc
+KAS = $(TARGET_TRIPLET)-as
+KLD = $(TARGET_TRIPLET)-ld
+KNM = $(TARGET_TRIPLET)-nm
+
+KCFLAGS  = -O2 -std=c99
+KCFLAGS += -finline-functions -ffreestanding
+KCFLAGS += -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wno-format
+KCFLAGS += -pedantic -fno-omit-frame-pointer
+KCFLAGS += -D_KERNEL_
+KCFLAGS += -DKERNEL_GIT_TAG=$(shell util/make-version)
+KASFLAGS = --32
+
+##
+# Kernel objects from kernel/ C sources
 KERNEL_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/*.c))
 KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*.c))
 KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*/*.c))
 
+##
+# Kernel objects from kernel/ assembly sources
 KERNEL_ASMOBJS = $(filter-out kernel/symbols.o,$(patsubst %.S,%.o,$(wildcard kernel/*.S)))
 
-toaruos-kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o
-	@${BEG} "CC" "$@"
-	@${KCC} -T kernel/link.ld ${CFLAGS} -nostdlib -o toaruos-kernel ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o -lgcc ${ERRORS}
-	@${END} "CC" "$@"
-	@${INFO} "--" "Kernel is ready!"
+# Kernel
 
-kernel/symbols.o: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} util/generate_symbols.py
-	@-rm -f kernel/symbols.o
-	@${BEG} "NM" "Generating symbol list..."
-	@${KCC} -T kernel/link.ld ${CFLAGS} -nostdlib -o toaruos-kernel ${KERNEL_ASMOBJS} ${KERNEL_OBJS} -lgcc ${ERRORS}
-	@${KNM} toaruos-kernel -g | util/generate_symbols.py > kernel/symbols.S
-	@${END} "NM" "Generated symbol list."
-	@${BEG} "AS" "kernel/symbols.S"
-	@${KAS} ${ASFLAGS} kernel/symbols.S -o $@ ${ERRORS}
-	@${END} "AS" "kernel/symbols.S"
+fatbase/kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o
+	${KCC} -T kernel/link.ld ${KCFLAGS} -nostdlib -o $@ ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o -lgcc
 
+##
+# Symbol table for the kernel. Instead of relying on getting
+# the symbol table from our bootloader (eg. through ELF
+# headers provided via multiboot structure), we have a dedicated
+# object that build with all the symbols. This allows us to
+# build the kernel as a flat binary or load it with less-capable
+# multiboot loaders and still get symbols, which we need to
+# load kernel modules and link them properly.
+kernel/symbols.o: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} util/generate_symbols.py
+	-rm -f kernel/symbols.o
+	${KCC} -T kernel/link.ld ${KCFLAGS} -nostdlib -o .toaruos-kernel ${KERNEL_ASMOBJS} ${KERNEL_OBJS} -lgcc
+	${KNM} .toaruos-kernel -g | util/generate_symbols.py > kernel/symbols.S
+	${KAS} ${KASFLAGS} kernel/symbols.S -o $@
+	-rm -f .toaruos-kernel
+
+##
+# version.o should be rebuilt whenever the kernel changes
+# in order to get fresh git commit hash information.
 kernel/sys/version.o: kernel/*/*.c kernel/*.c
 
-hdd/mod:
-	@mkdir -p hdd/mod
+kernel/%.o: kernel/%.S
+	${KAS} ${ASFLAGS} $< -o $@
+
+kernel/%.o: kernel/%.c ${HEADERS}
+	${KCC} ${KCFLAGS} -nostdlib -g -c -o $@ $<
+
+# Modules
 
-# Loadable modules
-MODULES = $(patsubst modules/%.c,hdd/mod/%.ko,$(wildcard modules/*.c))
+fatbase/mod:
+	@mkdir -p $@
 
-# We also want to rebuild when a header changes.
-# This is a naive approach, but it works...
-HEADERS = $(shell find kernel/include/ -type f -name '*.h')
+##
+# Modules need to be installed on the boot image
+MODULES = $(patsubst modules/%.c,fatbase/mod/%.ko,$(wildcard modules/*.c))
+HEADERS = $(shell find base/usr/include/kernel -type f -name '*.h')
 
-hdd/mod/%.ko: modules/%.c ${HEADERS} | hdd/mod
-	@${BEG} "CC" "$< [module]"
-	@${KCC} -T modules/link.ld -I./kernel/include -nostdlib ${CFLAGS} -c -o $@ $< ${ERRORS}
-	@${END} "CC" "$< [module]"
+fatbase/mod/%.ko: modules/%.c ${HEADERS} | fatbase/mod
+	${KCC} -T modules/link.ld -nostdlib ${KCFLAGS} -c -o $@ $<
 
 modules: ${MODULES}
 
-kernel/%.o: kernel/%.S
-	@${BEG} "AS" "$<"
-	@${KAS} ${ASFLAGS} $< -o $@ ${ERRORS}
-	@${END} "AS" "$<"
+# Root Filesystem
 
-kernel/%.o: kernel/%.c ${HEADERS}
-	@${BEG} "CC" "$<"
-	@${KCC} ${CFLAGS} -nostdlib -g -I./kernel/include -c -o $@ $< ${ERRORS}
-	@${END} "CC" "$<"
-
-system: toaruos-disk.img toaruos-kernel ${MODULES}
-
-#############
-# Userspace #
-#############
-
-# Userspace build flags
-USER_CFLAGS   = -O3 -m32 -Wa,--32 -g -Iuserspace -std=c99 -U__STRICT_ANSI__ -Lhdd/usr/lib
-USER_CXXFLAGS = -O3 -m32 -Wa,--32 -g -Iuserspace
-USER_BINFLAGS =
-STRIP_LIBS = 1
-
-# We always build with our targetted cross-compiler
-CC = $(USER_TARGET)-gcc
-NM = $(USER_TARGET)-nm
-CXX= $(USER_TARGET)-g++
-AR = $(USER_TARGET)-ar
-AS = $(USER_TARGET)-as
-STRIP = $(USER_TARGET)-strip
-
-# Userspace binaries and libraries
-USER_CFILES   = $(filter-out userspace/core/init.c,$(shell find userspace -not -wholename '*/lib/*' -not -wholename '*.static.*' -name '*.c'))
-USER_CXXFILES = $(shell find userspace -not -wholename '*/lib/*' -name '*.c++')
-USER_LIBFILES = $(shell find userspace -wholename '*/lib/*' -name '*.c')
-
-LIBC=hdd/usr/lib/libc.so
-
-# PYthon sources
-PYTHON_LIBS = $(shell find userspace -wholename '*/lib/*' -name '*.py')
-PYTHON_BINS = $(shell find userspace -wholename '*/bin/*' -name '*.py')
-
-PYTHON_FILES  = $(foreach file,$(PYTHON_LIBS),$(patsubst %.py,hdd/usr/python/lib/python3.6/%.py,$(notdir ${file})))
-PYTHON_FILES += $(foreach file,$(PYTHON_BINS),$(patsubst %.py,hdd/bin/%.py,$(notdir ${file})))
-
-# Userspace output files (so we can define metatargets)
-NONTEST_C   = $(foreach f,$(USER_CFILES),$(if $(findstring /tests/,$f),,$f))
-NONTEST_CXX = $(foreach f,$(USER_CXXFILES),$(if $(findstring /tests/,$f),,$f))
-
-NONTEST  = $(foreach file,$(NONTEST_C),$(patsubst %.c,hdd/bin/%,$(notdir ${file})))
-NONTEST += $(foreach file,$(NONTEST_CXX),$(patsubst %.c++,hdd/bin/%,$(notdir ${file})))
-NONTEST += $(foreach file,$(USER_CSTATICFILES),$(patsubst %.static.c,hdd/bin/%,$(notdir ${file})))
-NONTEST += $(foreach file,$(USER_LIBFILES),$(patsubst %.c,hdd/usr/lib/libtoaru-%.so,$(notdir ${file})))
-NONTEST += $(LIBC) hdd/bin/init hdd/lib/ld.so
-
-USERSPACE  = $(foreach file,$(USER_CFILES),$(patsubst %.c,hdd/bin/%,$(notdir ${file})))
-USERSPACE += $(foreach file,$(USER_CXXFILES),$(patsubst %.c++,hdd/bin/%,$(notdir ${file})))
-USERSPACE += $(foreach file,$(USER_CSTATICFILES),$(patsubst %.static.c,hdd/bin/%,$(notdir ${file})))
-USERSPACE += $(foreach file,$(USER_LIBFILES),$(patsubst %.c,hdd/usr/lib/libtoaru-%.so,$(notdir ${file})))
-USERSPACE += $(LIBC) hdd/bin/init hdd/lib/ld.so
-
-userspace: ${USERSPACE}
-
-# Init must be built static at the moment.
-hdd/bin/init: userspace/core/init.c
-	@${BEG} "CC" "$< (static)"
-	@${CC} -o $@ -static -Wl,-static $(USER_CFLAGS) $(USER_BINFLAGS) $< ${ERRORS}
-	@${END} "CC" "$< (static)"
-
-# Libraries
-define user-c-rule
-$1: $2 $(shell util/auto-dep.py --deps $2) $(LIBC)
-	@${BEG} "CCSO" "$$<"
-	@${CC} -o $$@ $(USER_CFLAGS) -shared -fPIC $$(shell util/auto-dep.py --cflags $$<) $$< $$(shell util/auto-dep.py --libs $$<) -lc ${ERRORS}
-	@if [ "x$(STRIP_LIBS)" = "x1" ]; then ${STRIP} $$@; fi
-	@${END} "CCSO" "$$<"
-endef
-$(foreach file,$(USER_LIBFILES),$(eval $(call user-c-rule,$(patsubst %.c,hdd/usr/lib/libtoaru-%.so,$(notdir ${file})),${file})))
-
-# Binaries from C sources
-define user-c-rule
-$1: $2 $(shell util/auto-dep.py --deps $2) $(LIBC)
-	@${BEG} "CC" "$$<"
-	@${CC} -o $$@ $(USER_CFLAGS) $(USER_BINFLAGS) -fPIE $$(shell util/auto-dep.py --cflags $$<) $$< $$(shell util/auto-dep.py --libs $$<) -lc ${ERRORS}
-	@${END} "CC" "$$<"
-endef
-$(foreach file,$(USER_CFILES),$(eval $(call user-c-rule,$(patsubst %.c,hdd/bin/%,$(notdir ${file})),${file})))
-
-# Binaries from C++ sources
-define user-cxx-rule
-$1: $2 $(shell util/auto-dep.py --deps $2) $(LIBC)
-	@${BEG} "C++" "$$<"
-	@${CXX} -o $$@ $(USER_CXXFLAGS) $(USER_BINFLAGS) $$(shell util/auto-dep.py --cflags $$<) $$< $$(shell util/auto-dep.py --libs $$<) -lc ${ERRORS}
-	@${END} "C++" "$$<"
-endef
-$(foreach file,$(USER_CXXFILES),$(eval $(call user-cxx-rule,$(patsubst %.c++,hdd/bin/%,$(notdir ${file})),${file})))
-
-hdd/usr/lib:
-	@mkdir -p hdd/usr/lib
-
-# Bad implementations of shared libraries
-hdd/usr/lib/libc.so: ${TOOLCHAIN}/lib/libc.a | hdd/usr/lib
-	@${BEG} "SO" "$@"
-	@cp ${TOARU_SYSROOT}/usr/lib/libc.a libc.a
-	@# init and fini don't belong in our shared object
-	@ar d libc.a lib_a-init.o
-	@ar d libc.a lib_a-fini.o
-	@# Remove references to newlib's reentrant malloc
-	@ar d libc.a lib_a-callocr.o
-	@ar d libc.a lib_a-cfreer.o
-	@ar d libc.a lib_a-freer.o
-	@ar d libc.a lib_a-malignr.o
-	@ar d libc.a lib_a-mallinfor.o
-	@ar d libc.a lib_a-mallocr.o
-	@ar d libc.a lib_a-malloptr.o
-	@ar d libc.a lib_a-msizer.o
-	@ar d libc.a lib_a-mallstatsr.o
-	@ar d libc.a lib_a-pvallocr.o
-	@ar d libc.a lib_a-vallocr.o
-	@ar d libc.a lib_a-reallocr.o
-	@ar d libc.a lib_a-realloc.o
-	@ar d libc.a lib_a-calloc.o
-	@ar d libc.a lib_a-reallo.o
-	@${CC} -shared -o libc.so -Wl,--whole-archive libc.a -Wl,--no-whole-archive ${ERRORS}
-	@mv libc.so hdd/usr/lib/libc.so
-	@if [ "x$(STRIP_LIBS)" = "x1" ]; then ${STRIP} $@; fi
-	@rm libc.a
-	@${END} "SO" "$@"
-
-
-hdd/lib:
-	@mkdir -p hdd/lib
-
-hdd/lib/ld.so: linker/linker.c | hdd/lib
-	@${BEG} "CC" "$<"
-	@${CC} -static -Wl,-static -std=c99 -g -U__STRICT_ANSI__ -o $@ -Os -T linker/link.ld $< ${ERRORS}
-	@${END} "CC" "$<"
-
-define basic-so-wrapper
-hdd/usr/lib/lib$(1).so: ${TOOLCHAIN}/lib/lib$(1).a
-	@${BEG} "SO" "$$@"
-	@${CC} -shared -Wl,-soname,lib$(1).so -o lib$(1).so -Lhdd/usr/lib -Wl,--whole-archive ${TOOLCHAIN}/lib/lib$(1).a -Wl,--no-whole-archive $2 -lgcc
-	@mv lib$(1).so hdd/usr/lib/lib$(1).so
-	@if [ "x$(STRIP_LIBS)" = "x1" ]; then ${STRIP} $$@; fi
-	@${END} "SO" "$$@"
-endef
+base/dev:
+	mkdir -p $@
+base/tmp:
+	mkdir -p $@
+base/proc:
+	mkdir -p $@
+base/bin:
+	mkdir -p $@
+base/lib:
+	mkdir -p $@
+base/cdrom:
+	mkdir -p $@
+fatbase/efi/boot:
+	mkdir -p $@
+.make:
+	mkdir -p .make
+dirs: base/dev base/tmp base/proc base/bin base/lib base/cdrom fatbase/efi/boot .make
 
-$(eval $(call basic-so-wrapper,gcc,))
-$(eval $(call basic-so-wrapper,m,))
-$(eval $(call basic-so-wrapper,z,))
-$(eval $(call basic-so-wrapper,ncurses,))
-$(eval $(call basic-so-wrapper,panel,-lncurses))
-$(eval $(call basic-so-wrapper,png15,-lz))
-$(eval $(call basic-so-wrapper,freetype,-lz))
-$(eval $(call basic-so-wrapper,pixman-1,-lm))
-$(eval $(call basic-so-wrapper,cairo,-lpixman-1 -lpng15 -lfreetype))
+# C Library
 
-# Python parts of userspace
+crts: base/lib/crt0.o base/lib/crti.o base/lib/crtn.o | dirs
 
-hdd/usr/python/lib/python3.6:
-	@mkdir -p $@
+base/lib/crt%.o: libc/crt%.s
+	yasm -f elf -o $@ $<
 
-hdd/bin/%.py: userspace/py/bin/%.py
-	@cp $< $@
+libc/setjmp.o: libc/setjmp.S
+	$(AS) -o $@ $<
 
-hdd/usr/python/lib/python3.6/%.py: userspace/py/lib/%.py hdd/usr/python/lib/python3.6
-	@cp $< $@
+libc/%.o: libc/%.c
+	$(CC) $(CFLAGS) -fPIC -c -o $@ $<
 
-####################
-# Hard Disk Images #
-####################
+base/lib/libc.a: ${LIBC_OBJS} | dirs crts
+	$(AR) cr $@ $^
 
-# Hard disk image generation
-GENEXT = genext2fs
-DISK_SIZE = `util/disk_size.sh`
+base/lib/libc.so: ${LIBC_OBJS} | dirs crts
+	$(CC) -nodefaultlibs -o $@ $(CFLAGS) -shared -fPIC $^ -lgcc
 
-toaruos-disk.img: ${USERSPACE} util/devtable ${PYTHON_FILES}
-	@${BEG} "hdd" "Generating a Hard Disk image..."
-	@-rm -f toaruos-disk.img
-	@${GENEXT} -B 4096 -d hdd -D util/devtable -U -b ${DISK_SIZE} -N 4096 toaruos-disk.img ${ERRORS}
-	@${END} "hdd" "Generated Hard Disk image"
-	@${INFO} "--" "Hard disk image is ready!"
+base/lib/libm.so: util/lm.c | dirs crts
+	$(CC) -nodefaultlibs -o $@ $(CFLAGS) -shared -fPIC $^ -lgcc
 
-#############
-# CD Images #
-#############
+# Userspace Linker/Loader
 
-cdrom: toaruos.iso
+base/lib/ld.so: linker/linker.c base/lib/libc.a | dirs
+	$(CC) -static -Wl,-static $(CFLAGS) -o $@ -Os -T linker/link.ld $<
 
-hdd/usr/share/terminfo/t/toaru: util/toaru.tic
-	@mkdir -p hdd/usr/share/terminfo/t
-	@cp $< $@
+# Shared Libraries
+.make/%.lmak: lib/%.c util/auto-dep.py | dirs
+	util/auto-dep.py --makelib $< > $@
 
-FORCE:
+ifeq (,$(findstring clean,$(MAKECMDGOALS)))
+-include ${LIBS_Y}
+endif
+
+# Init (static)
+
+base/bin/init: apps/init.c base/lib/libc.a | dirs
+	$(CC) -static -Wl,-static $(CFLAGS) -o $@ $<
+
+fatbase/netinit: util/netinit.c base/lib/libc.a | dirs
+	$(CC) -static -Wl,-static $(CFLAGS) -o $@ $<
 
-_cdrom: FORCE | ${NONTEST} toaruos-kernel
-	@-rm -rf _cdrom
-	@cp -r util/cdrom _cdrom
+# Userspace applications
 
-_cdrom/mod: ${MODULES} _cdrom
-	@mv hdd/mod $@
+.make/%.mak: apps/%.c util/auto-dep.py | dirs
+	util/auto-dep.py --make $< > $@
 
-_cdrom/kernel: toaruos-kernel _cdrom
-	@cp $< $@
+ifeq (,$(findstring clean,$(MAKECMDGOALS)))
+-include ${APPS_Y}
+endif
+
+base/bin/%.sh: apps/%.sh
+	cp $< $@
+	chmod +x $@
 
-BLACKLIST  = hdd/usr/share/wallpapers/grandcanyon.png
-BLACKLIST += hdd/usr/share/wallpapers/paris.png
-BLACKLIST += hdd/usr/share/wallpapers/southbay.png
-BLACKLIST += hdd/usr/share/wallpapers/yokohama.png
-BLACKLIST += hdd/usr/share/wallpapers/yosemite.png
-BLACKLIST += hdd/usr/share/wallpapers/fuji.png
+# Ramdisk
 
-_cdrom/ramdisk.img: ${NONTEST} hdd/usr/share/wallpapers util/devtable hdd/usr/share/terminfo/t/toaru _cdrom _cdrom/mod ${PYTHON_FILES}
-	@${BEG} "ext" "Generating a ramdisk image..."
-	@rm -f $(filter-out ${NONTEST},${USERSPACE})
-	@rm -f ${BLACKLIST}
-	@${STRIP} ${NONTEST}
-	@${GENEXT} -B 4096 -d hdd -D util/devtable -U -b 16384 -N 2048 $@
-	@${END} "ext" "Generated ramdisk image"
+util/devtable: ${RAMDISK_FILES} $(shell find base) util/update-devtable.py
+	util/update-devtable.py
 
-_cdrom/ramdisk.img.gz: _cdrom/ramdisk.img
-	@gzip $<
+fatbase/ramdisk.img: ${RAMDISK_FILES} $(shell find base) Makefile util/devtable | dirs
+	genext2fs -B 4096 -d base -D util/devtable -U -b `util/calc-size.sh` -N 2048 $@
 
-define fixup-cd
-	@git checkout hdd/usr/share/wallpapers
-	@mv _cdrom/mod hdd/mod
-	@rm -r _cdrom
+# CD image
+
+ifeq (,$(wildcard /usr/lib32/crt0-efi-ia32.o))
+$(error Missing GNU-EFI.)
+endif
+
+EFI_XORRISO=-eltorito-alt-boot -e fat.img -no-emul-boot -isohybrid-gpt-basdat
+EFI_BOOT=cdrom/fat.img
+EFI_UPDATE=util/update-extents.py
+
+image.iso: ${EFI_BOOT} cdrom/boot.sys fatbase/netinit ${MODULES} util/update-extents.py
+	xorriso -as mkisofs -R -J -c bootcat \
+	  -b boot.sys -no-emul-boot -boot-load-size 24 \
+	  ${EFI_XORRISO} \
+	  -o image.iso cdrom
+	${EFI_UPDATE}
+
+# Boot loader
+
+##
+# FAT EFI payload
+# This is the filesystem the EFI loaders see, so it must contain
+# the kernel, modules, and ramdisk, plus anything else we want
+# available to the bootloader (eg., netinit).
+cdrom/fat.img: fatbase/ramdisk.img ${MODULES} fatbase/kernel fatbase/netinit fatbase/efi/boot/bootia32.efi fatbase/efi/boot/bootx64.efi util/mkdisk.sh
+	util/mkdisk.sh $@ fatbase
+
+##
+# For EFI, we build two laoders: ia32 and x64
+# We build them as ELF shared objects and the use objcopy to convert
+# them to PE executables / DLLs (as expected by EFI).
+EFI_CFLAGS=-fno-stack-protector -fpic -DEFI_PLATFORM -ffreestanding -fshort-wchar -I /usr/include/efi -mno-red-zone
+EFI_SECTIONS=-j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel -j .rela -j .reloc
+
+# ia32
+boot/efi.so: boot/cstuff.c boot/*.h
+	$(CC) ${EFI_CFLAGS} -I /usr/include/efi/ia32 -c -o boot/efi.o $<
+	$(LD) boot/efi.o /usr/lib32/crt0-efi-ia32.o -nostdlib -znocombreloc -T /usr/lib32/elf_ia32_efi.lds -shared -Bsymbolic -L /usr/lib32 -lefi -lgnuefi -o boot/efi.so
+
+fatbase/efi/boot/bootia32.efi: boot/efi.so
+	objcopy ${EFI_SECTIONS} --target=efi-app-ia32 $< $@
+
+# x64
+boot/efi64.so: boot/cstuff.c boot/*.h
+	gcc ${EFI_CFLAGS} -I /usr/include/efi/x86_64 -DEFI_FUNCTION_WRAPPER -c -o boot/efi64.o $<
+	$(LD) boot/efi64.o /usr/lib/crt0-efi-x86_64.o -nostdlib -znocombreloc -T /usr/lib/elf_x86_64_efi.lds -shared -Bsymbolic -L /usr/lib -lefi -lgnuefi -o boot/efi64.so
+
+fatbase/efi/boot/bootx64.efi: boot/efi64.so
+	objcopy ${EFI_SECTIONS} --target=efi-app-x86_64 $< $@
+
+# BIOS loader
+cdrom/boot.sys: boot/boot.o boot/cstuff.o boot/link.ld | dirs
+	${KLD} -T boot/link.ld -o $@ boot/boot.o boot/cstuff.o
+
+boot/cstuff.o: boot/cstuff.c boot/*.h
+	${KCC} -c -Os -o $@ $<
+
+boot/boot.o: boot/boot.s
+	yasm -f elf -o $@ $<
+
+.PHONY: clean
+clean:
+	rm -f base/lib/*.so
+	rm -f base/lib/libc.a
+	rm -f ${APPS_X} ${APPS_SH_X}
+	rm -f libc/*.o libc/*/*.o
+	rm -f image.iso
+	rm -f fatbase/ramdisk.img
+	rm -f cdrom/boot.sys
+	rm -f boot/*.o
+	rm -f boot/*.efi
+	rm -f boot/*.so
+	rm -f cdrom/fat.img cdrom/kernel cdrom/mod/* cdrom/ramdisk.img
+	rm -f fatbase/kernel fatbase/efi/boot/bootia32.efi fatbase/efi/boot/bootx64.efi
+	rm -f cdrom/netinit fatbase/netinit
+	rm -f ${KERNEL_OBJS} ${KERNEL_ASMOBJS} kernel/symbols.o kernel/symbols.S
+	rm -f base/lib/crt*.o
+	rm -f ${MODULES}
+	rm -f ${APPS_Y} ${LIBS_Y} ${EXT_LIBS_Y}
+
+ifneq (,$(findstring Microsoft,$(shell uname -r)))
+  QEMU_ARGS=-serial mon:stdio -m 1G -rtc base=localtime -vnc :0
+else
+  ifeq (,${NO_KVM})
+    KVM=-enable-kvm
+  else
+    KVM=
+  endif
+  QEMU_ARGS=-serial mon:stdio -m 1G -soundhw ac97,pcspk ${KVM} -rtc base=localtime
+endif
+
+
+.PHONY: run
+run: image.iso
+	qemu-system-i386 -cdrom $< ${QEMU_ARGS}
+
+.PHONY: fast
+fast: image.iso
+	qemu-system-i386 -cdrom $< ${QEMU_ARGS} \
+	  -fw_cfg name=opt/org.toaruos.bootmode,string=normal
+
+.PHONY: headless
+headless: image.iso
+	@qemu-system-i386 -cdrom $< ${QEMU_ARGS} \
+	  -nographic -no-reboot \
+	  -fw_cfg name=opt/org.toaruos.bootmode,string=headless
+
+.PHONY: shell
+shell: image.iso
+	@qemu-system-i386 -cdrom $< ${QEMU_ARGS} \
+	  -nographic -no-reboot \
+	  -fw_cfg name=opt/org.toaruos.bootmode,string=headless \
+	  -fw_cfg name=opt/org.toaruos.forceuser,string=local
+
+.PHONY: efi64
+efi64: image.iso
+	qemu-system-x86_64 -cdrom $< ${QEMU_ARGS} \
+	  -bios /usr/share/qemu/OVMF.fd
+
+
+VMNAME=ToaruOS-NIH CD
+
+define virtualbox-runner =
+.PHONY: $1
+$1: image.iso
+	-VBoxManage unregistervm "$(VMNAME)" --delete
+	VBoxManage createvm --name "$(VMNAME)" --ostype $2 --register
+	VBoxManage modifyvm "$(VMNAME)" --memory 1024 --vram 32 --audio pulse --audiocontroller ac97 --bioslogodisplaytime 1 --bioslogofadeout off --bioslogofadein off --biosbootmenu disabled $3
+	VBoxManage storagectl "$(VMNAME)" --add ide --name "IDE"
+	VBoxManage storageattach "$(VMNAME)" --storagectl "IDE" --port 0 --device 0 --medium $$(shell pwd)/image.iso --type dvddrive
+	VBoxManage setextradata "$(VMNAME)" GUI/DefaultCloseAction PowerOff
+	VBoxManage startvm "$(VMNAME)" --type separate
 endef
 
-toaruos.iso: _cdrom/ramdisk.img.gz _cdrom/kernel
-	@${BEG} "ISO" "Building a CD image"
-	@if [ -e /etc/lsb-release ] && grep precise /etc/lsb-release; then grub-mkrescue -o $@ _cdrom; else grub-mkrescue -d /usr/lib/grub/i386-pc --compress=xz -o $@ _cdrom 2> /dev/null; fi
-	@${END} "ISO" "Building a CD image"
-	$(call fixup-cd)
-	@${INFO} "--" "CD generated"
-
-netboot.img.gz: _cdrom/ramdisk.img.gz
-	cp _cdrom/ramdisk.img.gz netboot.img.gz
-	$(call fixup-cd)
-
-fix-cd:
-	$(call fixup-cd)
-
-
-##############
-#  packages  #
-##############
-
-kernel-headers.img.gz: kernel/include
-	@${BEG} "pack" "$@"
-	@rm -rf _kernel
-	@mkdir _kernel
-	@cp -rL kernel/include/* _kernel/
-	@${GENEXT} -B 4096 -d _kernel -U -b 170 $@ ${ERRORS}
-	@rm -r _kernel
-	@${END} "pack" "$@"
-
-##############
-#    ctags   #
-##############
-tags: kernel/*/*.c kernel/*.c userspace/**/*.c modules/*.c linker/*.c
-	@${BEG} "ctag" "Generating CTags..."
-	@-ctags -R --c++-kinds=+p --fields=+iaS --extra=+q kernel userspace modules util linker ${ERRORS}
-	@${END} "ctag" "Generated CTags."
-
-###############
-#    clean    #
-###############
-
-clean-soft:
-	@${BEGRM} "RM" "Cleaning kernel objects..."
-	@-rm -f kernel/*.o
-	@-rm -f kernel/*/*.o
-	@-rm -f ${KERNEL_OBJS}
-	@${ENDRM} "RM" "Cleaned kernel objects"
-
-clean-user:
-	@${BEGRM} "RM" "Cleaning userspace products..."
-	@-rm -f ${USERSPACE}
-	@${ENDRM} "RM" "Cleaned userspace products"
-
-clean-mods:
-	@${BEGRM} "RM" "Cleaning kernel modules..."
-	@-rm -f hdd/mod/*
-	@${ENDRM} "RM" "Cleaned kernel modules"
-
-clean-core:
-	@${BEGRM} "RM" "Cleaning final output..."
-	@-rm -f toaruos-kernel
-	@${ENDRM} "RM" "Cleaned final output"
-
-clean-disk:
-	@${BEGRM} "RM" "Deleting hard disk image..."
-	@-rm -f toaruos-disk.img
-	@${ENDRM} "RM" "Deleted hard disk image"
-
-clean: clean-soft clean-core
-	@${INFO} "--" "Finished soft cleaning"
-
-clean-hard: clean clean-user clean-mods
-	@${INFO} "--" "Finished hard cleaning"
-
-
-# vim:noexpandtab
-# vim:tabstop=4
-# vim:shiftwidth=4
+$(eval $(call virtualbox-runner,virtualbox,"Other",))
+$(eval $(call virtualbox-runner,virtualbox-efi,"Other",--firmware efi))
+$(eval $(call virtualbox-runner,virtualbox-efi64,"Other_64",--firmware efi))
+
+##
+# Optional Extensions
+#
+# These optional extension libraries require third-party components to build,
+# but allow the native applications to make use of functionality such as
+# TrueType fonts or PNG images. You must have the necessary elements to build
+# these already installed into your sysroot for this to work.
+EXT_LIBS=$(patsubst ext/%.c,%,$(wildcard ext/*.c))
+EXT_LIBS_X=$(foreach lib,$(EXT_LIBS),base/lib/libtoaru_$(lib).so)
+EXT_LIBS_Y=$(foreach lib,$(EXT_LIBS),.make/$(lib).elmak)
+
+.make/%.elmak: ext/%.c util/auto-dep.py | dirs
+	util/auto-dep.py --makelib $< > $@
+
+ifeq (,$(findstring clean,$(MAKECMDGOALS)))
+-include ${EXT_LIBS_Y}
+endif
+
+# Freetype: Terminal text rendering backend
+ext-freetype: base/lib/libtoaru_ext_freetype_fonts.so
+
+# Cairo: Compositor rendering backend
+ext-cairo: base/lib/libtoaru_ext_cairo_renderer.so

File diff suppressed because it is too large
+ 155 - 13
README.md


+ 0 - 65
README_OLD.md

@@ -1,65 +0,0 @@
-![](https://i.imgur.com/QGVoRJD.png)
-![](https://i.imgur.com/MlAxGpj.png)
-![](https://i.imgur.com/OpMGfHP.png)
-
-# ToaruOS (とあるOS) #
-
-とあるOS (ToaruOS) is a hobby operating system built mostly from scratch, including both a kernel and userspace.
-
-This repository contains the kernel, modules, and core userspace applications and libraries. Some third-party libraries and utilities are required to build a working system.
-
-## Build Instructions ##
-
-If you just want to build the kernel and userspace and get a working CD image, you can use Docker:
-
-    docker pull toaruos/build-tools:test
-    docker run -v `pwd`:/opt/tree -w /opt/tree -t toaruos/build-tools:test util/build-travis.sh
-
-To build a full toolchain locally, set up [a supported environment](https://github.com/klange/toaruos/wiki/Testing-and-Building#requirements) and run:
-
-    make toolchain
-
-You may be prompted to enter your password for `sudo` to install required packages. The toolchain build scripts will then build several dependencies for building ToaruOS such as a GCC cross compiler and a few libraries used by the userspace. This may take around thirty minutes to an hour to complete, depending on your hardware. Once it is done, verify there were no errors and then follow the instructions provided to continue with the build.
-
-If you experience issues, please join our [IRC channel](#irc) for help.
-
-## History ##
-
-ToaruOS started as a side project at the University of Illinois at Urbana-Champaign. For several months in late 2011 and early 2012, the University's [SIGOps](http://www.acm.uiuc.edu/sigops/) chapter managed development efforts focused on building the original compositing GUI. Since then, the project has mostly been a one-man effort with a handful of third party contributions.
-
-## Kernel ##
-
-The Toaru kernel provides a basic Unix-like environment. The kernel uses a hybrid modular architecture, with loadable modules providing most device driver support. The core kernel includes support for Unix pipes and TTYs, a virtual file system, multitasking, ELF binary support, and various core platform features on x86 systems.
-
-Modules provide support for disk drives, ext2 filesystems, serial, keyboards and mice, a `/proc` filesystem similar to the one found in Linux, as well as an expanding collection of other device drivers.
-
-## Userspace ##
-
-ToaruOS's userspace is focused on a rich graphical environment, backed by an in-house compositing window manager. ToaruOS's terminal emulator supports xterm-compatible 256-color modes, as well as Konsole 24-bit color modes and anti-aliased text with basic Unicode support. Program binaries are dynamically linked. Several graphical demos are provided, alongside a number of command-line applications. A port of SDL targetting the native graphical environment is also available.
-
-### Third-Party Software ###
-
-The userspace depends on a number of third-party libraries which are outside of the development scope of the project. Additionally, several third-party applications and libraries have been integrated into ToaruOS's core userspace, or otherwise ported to ToaruOS.
-
-License for the included third-party tools and libraries can be found [here](hdd/usr/share/help/licenses).
-
-## Community ##
-
-### Git ###
-
-ToaruOS is hosted across multiple social git hosting sites:
-
-- [Gitlab](https://gitlab.com/toaruos/toaruos)
-- [Github](https://github.com/klange/toaruos)
-- [Bitbucket](https://bitbucket.org/klange/toaruos)
-- [git.toaruos.org](https://git.toaruos.org/klange/toaruos)
-
-### Wiki ###
-
-For additional screenshots, see [Screenshots](https://github.com/klange/toaruos/wiki/Screenshots).
-
-For instructions on building, see [Testing and Building](https://github.com/klange/toaruos/wiki/Testing-and-Building).
-
-### IRC ###
-
-For help building the kernel and userspace, join us in `#toaruos` on Freenode (`irc.freenode.net`).

+ 223 - 0
apps/about.c

@@ -0,0 +1,223 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * about - Show an "About <Application>" dialog.
+ *
+ * By default, shows "About ToaruOS", suitable for use as an application
+ * menu entry. Optionally, takes arguments specifying another application
+ * to describe, suitable for the "Help > About" menu bar entry.
+ */
+#include <toaru/yutani.h>
+#include <toaru/graphics.h>
+#include <toaru/decorations.h>
+#include <toaru/sdf.h>
+#include <toaru/menu.h>
+
+#include <sys/utsname.h>
+
+static yutani_t * yctx;
+static yutani_window_t * window = NULL;
+static gfx_context_t * ctx = NULL;
+static sprite_t logo;
+
+static int32_t width = 350;
+static int32_t height = 250;
+static char * version_str;
+
+static char * icon_path;
+static char * title_str;
+static char * version_str;
+static char * copyright_str[20] = {NULL};
+
+static int center_x(int x) {
+	return (width - x) / 2;
+}
+
+static void draw_string(int y, const char * string, int font, uint32_t color) {
+
+	struct decor_bounds bounds;
+	decor_get_bounds(window, &bounds);
+
+	draw_sdf_string(ctx, bounds.left_width + center_x(draw_sdf_string_width(string, 16, font)), bounds.top_height + 10 + logo.height + 10 + y, string, 16, color, font);
+}
+
+static void redraw(void) {
+
+	struct decor_bounds bounds;
+	decor_get_bounds(window, &bounds);
+
+	draw_fill(ctx, rgb(204,204,204));
+	draw_sprite(ctx, &logo, bounds.left_width + center_x(logo.width), bounds.top_height + 10);
+
+	draw_string(0, version_str, SDF_FONT_BOLD, rgb(0,0,0));
+
+	int offset = 20;
+
+	for (char ** copy_str = copyright_str; *copy_str; ++copy_str) {
+		if (**copy_str == '-') {
+			offset += 10;
+		} else if (**copy_str == '%') {
+			draw_string(offset, *copy_str+1, SDF_FONT_THIN, rgb(0,0,255));
+			offset += 20;
+		} else {
+			draw_string(offset, *copy_str, SDF_FONT_THIN, rgb(0,0,0));
+			offset += 20;
+		}
+	}
+
+	window->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE;
+	render_decorations(window, ctx, title_str);
+
+	flip(ctx);
+	yutani_flip(yctx, window);
+}
+
+static void init_default(void) {
+	title_str = "About ToaruOS-NIH";
+	icon_path = "/usr/share/logo_login.bmp";
+
+	{
+		version_str = malloc(100);
+		struct utsname u;
+		uname(&u);
+		char * tmp = strstr(u.release, "-");
+		if (tmp) {
+			*tmp = '\0';
+		}
+		sprintf(version_str, "ToaruOS-NIH %s", u.release);
+	}
+
+	copyright_str[0] = "(C) 2011-2018 K. Lange, et al.";
+	copyright_str[1] = "-";
+	copyright_str[2] = "ToaruOS is free software released under the";
+	copyright_str[3] = "NCSA/University of Illinois license.";
+	copyright_str[4] = "-";
+	copyright_str[5] = "%https://toaruos.org";
+	copyright_str[6] = "%https://gitlab.com/toaruos";
+
+}
+
+int main(int argc, char * argv[]) {
+	int req_center_x, req_center_y;
+	yctx = yutani_init();
+	if (!yctx) {
+		fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]);
+		return 1;
+	}
+	init_decorations();
+
+	struct decor_bounds bounds;
+	decor_get_bounds(NULL, &bounds);
+
+	window = yutani_window_create(yctx, width + bounds.width, height + bounds.height);
+	req_center_x = yctx->display_width / 2;
+	req_center_y = yctx->display_height / 2;
+
+	if (argc < 2) {
+		init_default();
+	} else if (argc < 5) {
+		fprintf(stderr, "Invalid arguments.\n");
+		return 1;
+	} else {
+		title_str = argv[1];
+		icon_path = argv[2];
+		version_str = argv[3];
+
+		int i = 0;
+		char * me = argv[4], * end;
+		do {
+			copyright_str[i] = me;
+			i++;
+			end = strchr(me,'\n');
+			if (end) {
+				*end = '\0';
+				me = end+1;
+			}
+		} while (end);
+
+		if (argc > 6) {
+			req_center_x = atoi(argv[5]);
+			req_center_y = atoi(argv[6]);
+		}
+	}
+
+	yutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2);
+
+	yutani_window_advertise_icon(yctx, window, title_str, "star");
+
+	ctx = init_graphics_yutani_double_buffer(window);
+	load_sprite(&logo, icon_path);
+	logo.alpha = ALPHA_EMBEDDED;
+
+	redraw();
+
+	int playing = 1;
+	while (playing) {
+		yutani_msg_t * m = yutani_poll(yctx);
+		while (m) {
+			if (menu_process_event(yctx, m)) {
+				redraw();
+			}
+			switch (m->type) {
+				case YUTANI_MSG_KEY_EVENT:
+					{
+						struct yutani_msg_key_event * ke = (void*)m->data;
+						if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {
+							playing = 0;
+						}
+					}
+					break;
+				case YUTANI_MSG_WINDOW_FOCUS_CHANGE:
+					{
+						struct yutani_msg_window_focus_change * wf = (void*)m->data;
+						yutani_window_t * win = hashmap_get(yctx->windows, (void*)wf->wid);
+						if (win) {
+							win->focused = wf->focused;
+							redraw();
+						}
+					}
+					break;
+#if 0
+				case YUTANI_MSG_RESIZE_OFFER:
+					{
+						struct yutani_msg_window_resize * wr = (void*)m->data;
+						resize_finish(wr->width, wr->height);
+					}
+					break;
+#endif
+				case YUTANI_MSG_WINDOW_MOUSE_EVENT:
+					{
+						struct yutani_msg_window_mouse_event * me = (void*)m->data;
+						int result = decor_handle_event(yctx, m);
+						switch (result) {
+							case DECOR_CLOSE:
+								playing = 0;
+								break;
+							case DECOR_RIGHT:
+								/* right click in decoration, show appropriate menu */
+								decor_show_default_menu(window, window->x + me->new_x, window->y + me->new_y);
+								break;
+							default:
+								/* Other actions */
+								break;
+						}
+					}
+					break;
+				case YUTANI_MSG_WINDOW_CLOSE:
+				case YUTANI_MSG_SESSION_END:
+					playing = 0;
+					break;
+				default:
+					break;
+			}
+			free(m);
+			m = yutani_poll_async(yctx);
+		}
+	}
+
+	yutani_close(yctx, window);
+
+	return 0;
+}

+ 173 - 0
apps/background.c

@@ -0,0 +1,173 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * background - Draw a desktop wallpaper.
+ *
+ * TODO: This is a very minimal wallpaper renderer.
+ *       ToaruOS-mainline, before it went all Python,
+ *       included a more complete wallpaper application,
+ *       which supported icons and config files.
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <sys/fswait.h>
+
+#include <toaru/yutani.h>
+#include <toaru/graphics.h>
+#include <toaru/menu.h>
+
+static yutani_t * yctx;
+static yutani_window_t * wallpaper_window;
+static gfx_context_t * wallpaper_ctx;
+static sprite_t * wallpaper;
+static struct MenuList * _rc_menu = NULL;
+
+static void draw_background(int width, int height) {
+
+	float x = (float)wallpaper_window->width / (float)wallpaper->width;
+	float y = (float)wallpaper_window->height / (float)wallpaper->height;
+
+	int nh = (int)(x * (float)wallpaper->height);
+	int nw = (int)(y * (float)wallpaper->width);
+
+	if (nw == wallpaper->width && nh == wallpaper->height) {
+		// special case
+		draw_sprite(wallpaper_ctx, wallpaper, 0, 0);
+	} else if (nw >= width) {
+		draw_sprite_scaled(wallpaper_ctx, wallpaper, ((int)wallpaper_window->width - nw) / 2, 0, nw+2, wallpaper_window->height);
+	} else {
+		draw_sprite_scaled(wallpaper_ctx, wallpaper, 0, ((int)wallpaper_window->height - nh) / 2, wallpaper_window->width+2, nh);
+	}
+}
+
+static void show_right_click_menu(int x, int y) {
+	if (_rc_menu->window) return; /* Already shown */
+
+	menu_show(_rc_menu, yctx);
+	if (x + _rc_menu->window->width > yctx->display_width) {
+		yutani_window_move(yctx, _rc_menu->window, x - _rc_menu->window->width, y);
+	} else {
+		yutani_window_move(yctx, _rc_menu->window, x, y);
+	}
+}
+
+static void resize_finish_wallpaper(int width, int height) {
+	yutani_window_resize_accept(yctx, wallpaper_window, width, height);
+	reinit_graphics_yutani(wallpaper_ctx, wallpaper_window);
+	draw_background(width, height);
+	yutani_window_resize_done(yctx, wallpaper_window);
+	yutani_flip(yctx, wallpaper_window);
+}
+
+static void launch_application(char * app) {
+	if (!fork()) {
+		printf("Starting %s\n", app);
+		char * args[] = {"/bin/sh", "-c", app, NULL};
+		execvp(args[0], args);
+		exit(1);
+	}
+}
+
+static void launch_application_menu(struct MenuEntry * self) {
+	struct MenuEntry_Normal * _self = (void *)self;
+	launch_application((char *)_self->action);
+}
+
+static void check_click(struct yutani_msg_window_mouse_event * evt) {
+	if (evt->wid == wallpaper_window->wid) {
+		if (evt->buttons & YUTANI_MOUSE_BUTTON_RIGHT) {
+			show_right_click_menu(evt->new_x, evt->new_y);
+		}
+	}
+}
+
+static void sig_usr2(int sig) {
+	yutani_set_stack(yctx, wallpaper_window, YUTANI_ZORDER_BOTTOM);
+	yutani_flip(yctx, wallpaper_window);
+}
+
+int main (int argc, char ** argv) {
+
+	if (argc < 2 || strcmp(argv[1],"--really")) {
+		fprintf(stderr,
+				"%s: Desktop environment wallpaper\n"
+				"\n"
+				" Renders the desktop wallpaper. You probably don't want\n"
+				" to be running this directly - it is started by the\n"
+				" session manager along with the panel.\n", argv[0]);
+		return 1;
+	}
+
+	signal(SIGUSR2, sig_usr2);
+
+	wallpaper = malloc(sizeof(sprite_t));
+	load_sprite(wallpaper, "/usr/share/wallpaper.bmp");
+	wallpaper->alpha = 0;
+
+	yctx = yutani_init();
+	if (!yctx) {
+		fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]);
+		return 1;
+	}
+
+	_rc_menu = menu_create();
+	menu_insert(_rc_menu, menu_create_normal("utilities-terminal", "terminal", "Open Terminal", launch_application_menu));
+
+	/* wallpaper */
+	wallpaper_window = yutani_window_create(yctx, yctx->display_width, yctx->display_height);
+	yutani_window_move(yctx, wallpaper_window, 0, 0);
+	yutani_set_stack(yctx, wallpaper_window, YUTANI_ZORDER_BOTTOM);
+
+	wallpaper_ctx = init_graphics_yutani(wallpaper_window);
+	draw_background(yctx->display_width, yctx->display_height);
+	yutani_flip(yctx, wallpaper_window);
+
+	int should_exit = 0;
+
+	while (!should_exit) {
+		int fds[1] = {fileno(yctx->sock)};
+		int index = fswait2(1,fds,200);
+		if (index == 0) {
+			yutani_msg_t * m = yutani_poll(yctx);
+			while (m) {
+				menu_process_event(yctx, m);
+				switch (m->type) {
+					case YUTANI_MSG_WELCOME:
+						yutani_window_resize_offer(yctx, wallpaper_window, yctx->display_width, yctx->display_height);
+						break;
+					case YUTANI_MSG_RESIZE_OFFER:
+						{
+							struct yutani_msg_window_resize * wr = (void*)m->data;
+							if (wr->wid == wallpaper_window->wid) {
+								resize_finish_wallpaper(wr->width, wr->height);
+							}
+						}
+						break;
+					case YUTANI_MSG_WINDOW_MOUSE_EVENT:
+						check_click((struct yutani_msg_window_mouse_event *)m->data);
+						break;
+					case YUTANI_MSG_SESSION_END:
+						should_exit = 1;
+						break;
+					default:
+						break;
+				}
+				free(m);
+				m = yutani_poll_async(yctx);
+			}
+		} else {
+			/* Perform timer events here. Animations? */
+			waitpid(-1, NULL, WNOHANG);
+		}
+	}
+
+	yutani_close(yctx, wallpaper_window);
+
+	return 0;
+}
+

+ 55 - 0
apps/beep.c

@@ -0,0 +1,55 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2014 K. Lange
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+int spkr = 0;
+
+struct spkr {
+	int length;
+	int frequency;
+};
+
+void note(int length, int frequency) {
+	struct spkr s = {
+		.length = length,
+		.frequency = frequency,
+	};
+
+	write(spkr, &s, sizeof(s));
+}
+
+int main(int argc, char * argv[]) {
+
+	spkr = open("/dev/spkr", O_WRONLY);
+	if (spkr == -1) {
+		fprintf(stderr, "%s: could not open speaker\n", argv[0]);
+	}
+
+	note(20, 15680);
+	note(10, 11747);
+	note(10, 12445);
+	note(20, 13969);
+	note(10, 12445);
+	note(10, 11747);
+	note(20, 10465);
+	note(10, 10465);
+	note(10, 12445);
+	note(20, 15680);
+	note(10, 13969);
+	note(10, 12445);
+	note(30, 11747);
+	note(10, 12445);
+	note(20, 13969);
+	note(20, 15680);
+	note(20, 12445);
+	note(20, 10465);
+	note(20, 10465);
+
+	return 0;
+}
+

File diff suppressed because it is too large
+ 5999 - 0
apps/bim.c


+ 162 - 0
apps/cat-img.c

@@ -0,0 +1,162 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2016-2018 K. Lange
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#include <toaru/graphics.h>
+#include <toaru/termemu.h>
+
+void get_cell_sizes(int * w, int * h) {
+	struct winsize wsz;
+	ioctl(0, TIOCGWINSZ, &wsz);
+
+	if (!wsz.ws_col || !wsz.ws_row) {
+		*w = 0;
+		*h = 0;
+	}
+
+	*w = wsz.ws_xpixel / wsz.ws_col;
+	*h = wsz.ws_ypixel / wsz.ws_row;
+}
+
+void raw_output(void) {
+	struct termios new;
+	tcgetattr(fileno(stdin), &new);
+	new.c_oflag &= (~ONLCR);
+	tcsetattr(fileno(stdin), TCSAFLUSH, &new);
+}
+
+void unraw_output(void) {
+	struct termios new;
+	tcgetattr(fileno(stdin), &new);
+	new.c_oflag |= ONLCR;
+	tcsetattr(fileno(stdin), TCSAFLUSH, &new);
+}
+
+int usage(char * argv[]) {
+	printf(
+			"usage: %s [-?ns] [path]\n"
+			"\n"
+			" -n     \033[3mdon't print a new line after image\033[0m\n"
+			" -s     \033[3mscale to cell height (up or down)\033[0m\n"
+			" -w     \033[3mscale to terminal width (up or down)\033[0m\n"
+			" -?     \033[3mshow this help text\033[0m\n"
+			"\n", argv[0]);
+	return 1;
+}
+
+int main (int argc, char * argv[]) {
+	if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
+		fprintf(stderr, "Can't cat-img to a non-terminal.\n");
+		exit(1);
+	}
+
+	int opt;
+	int no_newline = 0;
+	int scale_to_cell_height = 0;
+	int scale_to_term_width = 0;
+
+	while ((opt = getopt(argc, argv, "?nsw")) != -1) {
+		switch (opt) {
+			case '?':
+				return usage(argv);
+			case 'n':
+				no_newline = 1;
+				break;
+			case 'w':
+				scale_to_term_width = 1;
+				break;
+			case 's':
+				scale_to_cell_height = 1;
+				break;
+		}
+	}
+
+	if (optind >= argc ) {
+		return usage(argv);
+	}
+
+	int w, h;
+	get_cell_sizes(&w, &h);
+
+	if (!w || !h) return 1;
+
+	while (optind < argc) {
+		sprite_t * image = calloc(sizeof(sprite_t),1);
+		load_sprite(image, argv[optind]);
+		image->alpha = ALPHA_EMBEDDED;
+
+		sprite_t * source = image;
+
+		if (scale_to_cell_height) {
+			int new_width = (h * image->width) / image->height;
+			source = create_sprite(new_width,h,1);
+			gfx_context_t * g = init_graphics_sprite(source);
+			draw_fill(g, 0x00000000);
+			draw_sprite_scaled(g, image, 0, 0, new_width, h);
+			sprite_free(image);
+		}
+
+		if (scale_to_term_width) {
+			struct winsize w;
+			ioctl(0, TIOCGWINSZ, &w);
+			int new_height = (w.ws_xpixel * image->height) / image->width;
+			source = create_sprite(w.ws_xpixel, new_height, 1);
+			gfx_context_t * g = init_graphics_sprite(source);
+			draw_fill(g, 0x00000000);
+			draw_sprite_scaled(g, image, 0, 0, w.ws_xpixel, new_height);
+			sprite_free(image);
+		}
+
+		int width_in_cells = source->width / w;
+		if (source->width % w) width_in_cells++;
+
+		int height_in_cells = source->height / h;
+		if (source->height % h) height_in_cells++;
+
+		raw_output();
+		printf("\033[?25l");
+
+		for (int y = 0; y < height_in_cells; y++) {
+			for (int x = 0; x < width_in_cells; x++) {
+				printf("\033Ts");
+				uint32_t * tmp = malloc(sizeof(uint32_t) * w * h);
+				for (int yy = 0; yy < h; yy++) {
+					for (int xx = 0; xx < w; xx++) {
+						if (x*w + xx >= source->width || y*h + yy >= source->height) {
+							tmp[yy * w + xx] = rgba(0,0,0,TERM_DEFAULT_OPAC);
+						} else {
+							uint32_t data = alpha_blend_rgba(
+								rgba(0,0,0,TERM_DEFAULT_OPAC),
+								premultiply(source->bitmap[(x*w+xx)+(y*h+yy)*source->width]));
+							tmp[yy * w + xx] = data;
+						}
+					}
+				}
+				fwrite(tmp, sizeof(uint32_t) * w * h, 1, stdout);
+				free(tmp);
+				fflush(stdout);
+			}
+			if (y != height_in_cells - 1 || !no_newline) {
+				printf("\r\n");
+			}
+		}
+
+		sprite_free(source);
+
+		printf("\033[?25h");
+		unraw_output();
+		fflush(stdout);
+		optind++;
+	}
+
+	return 0;
+}
+ 

+ 63 - 0
apps/cat.c

@@ -0,0 +1,63 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2018 K. Lange
+ *
+ * cat - Concatenate files
+ *
+ * Concatenates files together to standard output.
+ * In a supporting terminal, you can then pipe
+ * standard out to another file or other useful
+ * things like that.
+ */
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#define CHUNK_SIZE 4096
+
+void doit(int fd) {
+	while (1) {
+		char buf[CHUNK_SIZE];
+		memset(buf, 0, CHUNK_SIZE);
+		ssize_t r = read(fd, buf, CHUNK_SIZE);
+		if (!r) return;
+		write(STDOUT_FILENO, buf, r);
+	}
+}
+
+int main(int argc, char ** argv) {
+	int ret = 0;
+	if (argc == 1) {
+		doit(0);
+	}
+
+	for (int i = 1; i < argc; ++i) {
+		int fd = open(argv[i], O_RDONLY);
+		if (fd < 0) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno));
+			ret = 1;
+			continue;
+		}
+
+		struct stat _stat;
+		fstat(fd, &_stat);
+
+		if (S_ISDIR(_stat.st_mode)) {
+			fprintf(stderr, "%s: %s: Is a directory\n", argv[0], argv[i]);
+			close(fd);
+			ret = 1;
+			continue;
+		}
+
+		doit(fd);
+
+		close(fd);
+	}
+
+	return ret;
+}
+

+ 135 - 0
apps/chmod.c

@@ -0,0 +1,135 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * chmod - change file permissions
+ *
+ * Note: This implementation is likely non-compliant, though it does
+ *       attempt to look similar to the standard POSIX syntax,
+ *       supporting both octal mode setings and +/-rwx flavors.
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+enum mode_set {
+	MODE_SET,
+	MODE_ADD,
+	MODE_REMOVE,
+};
+
+static int calc(int mode, int users) {
+	int out = 0;
+	if (users & 1) {
+		out |= (mode << 6);
+	}
+	if (users & 2) {
+		out |= (mode << 3);
+	}
+	if (users & 4) {
+		out |= (mode << 0);
+	}
+	return out;
+}
+
+int main(int argc, char * argv[]) {
+	if (argc < 3) {
+		fprintf(stderr, "usage: %s OCTAL-MODE FILE...\n", argv[0]);
+		return 1;
+	}
+
+	/* Parse mode */
+	int mode = 0;
+	enum mode_set mode_set = MODE_SET;
+	char * c = argv[1];
+	int user_modes = 0;
+	int all_users = 7;
+
+	while (*c) {
+		switch (*c) {
+			case '0':
+				c++; /* 0 */
+				while (*c >= '0' || *c <= '7') {
+					mode *= 8;
+					mode += (*c - '0');
+					c++;
+				}
+				break;
+			case 'u':
+				all_users = 0;
+				user_modes |= 1;
+				c++;
+				break;
+			case 'g':
+				all_users = 0;
+				user_modes |= 2;
+				c++;
+				break;
+			case 'o':
+				all_users = 0;
+				user_modes |= 4;
+				c++;
+				break;
+			case 'a':
+				all_users = 7;
+				user_modes = 7;
+				c++;
+				break;
+			case '-':
+				mode_set = MODE_REMOVE;
+				c++;
+				break;
+			case '+':
+				mode_set = MODE_ADD;
+				c++;
+				break;
+			case '=':
+				mode_set = MODE_SET;
+				c++;
+				break;
+			case 'r':
+				mode |= calc(S_IROTH, user_modes | all_users);
+				c++;
+				break;
+			case 'w':
+				mode |= calc(S_IWOTH, user_modes | all_users);
+				c++;
+				break;
+			case 'x':
+				mode |= calc(S_IXOTH, user_modes | all_users);
+				c++;
+				break;
+		}
+	}
+
+	int i = 2;
+	while (i < argc) {
+		int actual_mode = 0;
+		struct stat _stat;
+		if (stat(argv[i], &_stat) < 0) {
+			fprintf(stderr, "%s: %s: error with stat\n", argv[0], argv[i]);
+		}
+
+		switch (mode_set) {
+			case MODE_SET:
+				actual_mode = mode;
+				break;
+			case MODE_ADD:
+				actual_mode = _stat.st_mode | mode;
+				break;
+			case MODE_REMOVE:
+				actual_mode = _stat.st_mode &= ~(mode);
+				break;
+		}
+
+		if (chmod(argv[i], actual_mode) < 0) {
+			fprintf(stderr, "%s: %s: error with chmod\n", argv[0], argv[i]);
+			return 1;
+		}
+		i++;
+	}
+
+	return 0;
+}

+ 19 - 0
apps/clear.c

@@ -0,0 +1,19 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013 K. Lange
+ *
+ * clear - Clear the terminal
+ *
+ * Sends an escape code to clear the screen. Ideally, this should
+ * come from a database of terminal escape codes (eg. terminfo),
+ * but we don't have one of those yet, so just send a code that
+ * makes sense for a lot of terminals.
+ */
+#include <stdio.h>
+
+int main(int argc, char ** argv) {
+	printf("\033[H\033[2J");
+	fflush(stdout);
+	return 0;
+}

File diff suppressed because it is too large
+ 2653 - 0
apps/compositor.c


+ 87 - 0
apps/cp.c

@@ -0,0 +1,87 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013 K. Lange
+ * Copyright (C) 2013 Tyler Bindon
+ *
+ * cp - Copy files
+ *
+ * This is an incomplete implementation of `cp`. A more complete
+ * version of recursive directory copying can be found in the
+ * `migrate` sources, and should probably be imported here.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#define CHUNK_SIZE 4096
+
+int main(int argc, char ** argv) {
+
+	FILE * fd;
+	FILE * fout;
+	if (argc < 3) {
+		fprintf(stderr, "usage: %s [source] [destination]\n", argv[0]);
+		return 1;
+	}
+	fd = fopen(argv[1], "r");
+	if (!fd) {
+		fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno));
+		return 1;
+	}
+
+	struct stat statbuf;
+	stat(argv[1], &statbuf);
+	int initial_mode = statbuf.st_mode;
+
+	stat(argv[2], &statbuf);
+	if (S_ISDIR(statbuf.st_mode)) {
+		char *filename = strrchr(argv[1], '/');
+		if (!filename) {
+			filename = argv[1];
+		}
+
+		char *target_path = malloc((strlen(argv[2]) + strlen(filename) + 2) * sizeof(char));
+		sprintf(target_path, "%s/%s", argv[2], filename );
+		fout = fopen( target_path, "w" );
+
+		free(target_path);
+	} else {
+		fout = fopen( argv[2], "w" );
+	}
+
+	if (!fout) {
+		fprintf(stderr, "%s: %s: %s\n", argv[0], argv[2], strerror(errno));
+		return 1;
+	}
+
+	size_t length;
+
+	fseek(fd, 0, SEEK_END);
+	length = ftell(fd);
+	fseek(fd, 0, SEEK_SET);
+
+	char buf[CHUNK_SIZE];
+	while (length > CHUNK_SIZE) {
+		fread( buf, 1, CHUNK_SIZE, fd);
+		fwrite(buf, 1, CHUNK_SIZE, fout);
+		length -= CHUNK_SIZE;
+	}
+	if (length > 0) {
+		fread( buf, 1, length, fd);
+		fwrite(buf, 1, length, fout);
+	}
+
+	fclose(fd);
+	fclose(fout);
+
+	if (chmod(argv[2], initial_mode) < 0) {
+		fprintf(stderr, "%s: %s: %s\n", argv[0], argv[2], strerror(errno));
+	}
+
+	return 0;
+}
+

+ 19 - 0
apps/cursor-off.c

@@ -0,0 +1,19 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2016-2018 K. Lange
+ *
+ * cursor-off - Disables the VGA text mode cursor.
+ *
+ * This is an old tool that calls a special system call
+ * to change the VGA text-mode cursor position. The VGA
+ * terminal renders its own cursor in software, so we
+ * try to move the hardware cursor off screen so it doesn't
+ * interfere with the rest of the terminal and look weird.
+ */
+#include <syscall.h>
+
+int main(int argc, char * argv[]) {
+	int x[] = {0xFF,0xFF};
+	return syscall_system_function(13, (char **)x);
+}

+ 59 - 0
apps/date.c

@@ -0,0 +1,59 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * date - Print the current date and time.
+ *
+ * TODO: The traditional POSIX version of this tool is supposed
+ *       to accept a format *and* allow you to set the time.
+ *       We currently lack system calls for setting the time,
+ *       but when we add those this should probably be updated.
+ *
+ *       At the very least, improving this to print the "correct"
+ *       default format would be good.
+ */
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+static void show_usage(int argc, char * argv[]) {
+	printf(
+			"%s - print the time and day\n"
+			"\n"
+			"usage: %s [-?] +FORMAT\n"
+			"\n"
+			"    Note: This implementation is not currently capable of\n"
+			"          setting the system time.\n"
+			"\n"
+			" -?     \033[3mshow this help text\033[0m\n"
+			"\n", argv[0], argv[0]);
+}
+
+int main(int argc, char * argv[]) {
+	char * format = "%a %b %d %T %Y";
+	struct tm * timeinfo;
+	struct timeval now;
+	char buf[BUFSIZ] = {0};
+	int opt;
+
+	while ((opt = getopt(argc,argv,"?")) != -1) {
+		switch (opt) {
+			case '?':
+				show_usage(argc,argv);
+				return 1;
+		}
+	}
+
+	if (optind < argc && *argv[optind] == '+') {
+		format = &argv[optind][1];
+	}
+
+	gettimeofday(&now, NULL); //time(NULL);
+	timeinfo = localtime((time_t *)&now.tv_sec);
+
+	strftime(buf,BUFSIZ,format,timeinfo);
+	puts(buf);
+	return 0;
+}

+ 134 - 0
apps/drawlines.c

@@ -0,0 +1,134 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2018 K. Lange
+ *
+ * drawlines - Draw random lines into a GUI window
+ *
+ * The original compositor demo application, this dates all the
+ * way back to the original pre-Yutani compositor. Opens a very
+ * basic window (no decorations) and randomly fills it with
+ * colorful lines in a separate thread from the listener.
+ *
+ * There's no good reason for this to use threads - it should use
+ * `fswait2` to apply timeouts - but it demonstrates threading
+ * so we'll leave it that way for now.
+ */
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <time.h>
+#include <sched.h>
+#include <math.h>
+
+#include <toaru/yutani.h>
+#include <toaru/graphics.h>
+
+static int left, top, width, height;
+
+static yutani_t * yctx;
+static yutani_window_t * wina;
+static gfx_context_t * ctx;
+static int should_exit = 0;
+static int thick = 0;
+
+void * draw_thread(void * garbage) {
+	(void)garbage;
+	while (!should_exit) {
+		if (thick) {
+			draw_line_aa(ctx, rand() % width, rand() % width, rand() % height, rand() % height, rgb(rand() % 255,rand() % 255,rand() % 255), (float)thick);
+		} else {
+			draw_line(ctx, rand() % width, rand() % width, rand() % height, rand() % height, rgb(rand() % 255,rand() % 255,rand() % 255));
+		}
+		yutani_flip(yctx, wina);
+		usleep(16666);
+	}
+	pthread_exit(0);
+	return NULL;
+}
+
+static void show_usage(char * argv[]) {
+	printf(
+			"drawlines - graphical demo, draws lines randomly\n"
+			"\n"
+			"usage: %s [-t thickness]\n"
+			"\n"
+			" -t     \033[3mdraw with anti-aliasing and the specified thickness\033[0m\n"
+			" -?     \033[3mshow this help text\033[0m\n"
+			"\n", argv[0]);
+}
+
+
+int main (int argc, char ** argv) {
+	left   = 100;
+	top    = 100;
+	width  = 500;
+	height = 500;
+
+	srand(time(NULL));
+
+	int c;
+	while ((c = getopt(argc, argv, "t:?")) != -1) {
+		switch (c) {
+			case 't':
+				thick = atoi(optarg);
+				break;
+			case '?':
+				show_usage(argv);
+				return 0;
+		}
+	}
+
+	yctx = yutani_init();
+	if (!yctx) {
+		fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]);
+		return 1;
+	}
+
+	wina = yutani_window_create(yctx, width, height);
+	yutani_window_move(yctx, wina, left, top);
+	yutani_window_advertise_icon(yctx, wina, "drawlines", "drawlines");
+
+	ctx = init_graphics_yutani(wina);
+	draw_fill(ctx, rgb(0,0,0));
+
+	pthread_t thread;
+	pthread_create(&thread, NULL, draw_thread, NULL);
+
+	while (!should_exit) {
+		yutani_msg_t * m = yutani_poll(yctx);
+		if (m) {
+			switch (m->type) {
+				case YUTANI_MSG_KEY_EVENT:
+					{
+						struct yutani_msg_key_event * ke = (void*)m->data;
+						if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') {
+							should_exit = 1;
+							sched_yield();
+						}
+					}
+					break;
+				case YUTANI_MSG_WINDOW_MOUSE_EVENT:
+					{
+						struct yutani_msg_window_mouse_event * me = (void*)m->data;
+						if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
+							yutani_window_drag_start(yctx, wina);
+						}
+					}
+					break;
+				case YUTANI_MSG_WINDOW_CLOSE:
+				case YUTANI_MSG_SESSION_END:
+					should_exit = 1;
+					break;
+				default:
+					break;
+			}
+		}
+		free(m);
+	}
+
+	yutani_close(yctx, wina);
+
+	return 0;
+}

+ 124 - 0
apps/echo.c

@@ -0,0 +1,124 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2018 K. Lange
+ *
+ * echo - Print arguments to stdout.
+ *
+ * Prints arguments to stdout, possibly interpreting escape
+ * sequences in the arguments.
+ */
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+void show_usage(char * argv[]) {
+	printf(
+			"echo - print arguments\n"
+			"\n"
+			"usage: %s [-ne] ARG...\n"
+			"\n"
+			" -n     \033[3mdo not output a new line at the end\033[0m\n"
+			" -e     \033[3mprocess escape sequences\033[0m\n"
+			" -?     \033[3mshow this help text\033[0m\n"
+			"\n", argv[0]);
+}
+
+int main(int argc, char ** argv) {
+	int use_newline     = 1;
+	int process_escapes = 0;
+
+	int opt;
+	while ((opt = getopt(argc, argv, "enh?")) != -1) {
+		switch (opt) {
+			case '?':
+			case 'h':
+				show_usage(argv);
+				return 1;
+			case 'n':
+				use_newline = 0;
+				break;
+			case 'e':
+				process_escapes = 1;
+				break;
+		}
+	}
+
+	for (int i = optind; i < argc; ++i) {
+		if (process_escapes) {
+			char * c = argv[i];
+			while (*c) {
+				if (*c == '\\') {
+					c++;
+					switch (*c) {
+						case '\\':
+							putchar('\\');
+							break;
+						case 'a':
+							putchar('\a');
+							break;
+						case 'b':
+							putchar('\b');
+							break;
+						case 'c':
+							return 0;
+						case 'e':
+							putchar('\033');
+							break;
+						case 'f':
+							putchar('\f');
+							break;
+						case 'n':
+							putchar('\n');
+							break;
+						case 't':
+							putchar('\t');
+							break;
+						case 'v':
+							putchar('\v');
+							break;
+						case '0':
+							{
+								int i = 0;
+								if (!isdigit(*(c+1)) || *(c+1) > '7') {
+									break;
+								}
+								c++;
+								i = *c - '0';
+								if (isdigit(*(c+1)) && *(c+1) <= '7') {
+									c++;
+									i = (i << 3) | (*c - '0');
+									if (isdigit(*(c+1)) && *(c+1) <= '7') {
+										c++;
+										i = (i << 3) | (*c - '0');
+									}
+								}
+								putchar(i);
+							}
+							break;
+						default:
+							putchar('\\');
+							putchar(*c);
+							break;
+					}
+				} else {
+					putchar(*c);
+				}
+				c++;
+			}
+		} else {
+			printf("%s",argv[i]);
+		}
+		if (i != argc - 1) {
+			printf(" ");
+		}
+	}
+
+	if (use_newline) {
+		printf("\n");
+	}
+
+	fflush(stdout);
+	return 0;
+}

+ 49 - 0
apps/env.c

@@ -0,0 +1,49 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2013-2018 K. Lange
+ *
+ * env - Print or set environment
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+extern int _environ_size;
+
+int main(int argc, char ** argv) {
+	int start = 1;
+
+	if (start < argc && !strcmp(argv[start],"-i")) {
+		for (int i = 0; i < _environ_size; ++i) {
+			environ[i] = NULL;
+		}
+		start++;
+	}
+
+	for (; start < argc; ++start) {
+		if (!strchr(argv[start],'=')) {
+			break;
+		} else {
+			putenv(argv[start]);
+		}
+	}
+
+	if (start < argc) {
+		/* Execute command */
+		if (execvp(argv[start], &argv[start])) {
+			fprintf(stderr, "%s: %s: %s\n", argv[0], argv[start], strerror(errno));
+		}
+	} else {
+		char ** env = environ;
+
+		while (*env) {
+			printf("%s\n", *env);
+			env++;
+		}
+	}
+
+	return 0;
+}

+ 10 - 0
apps/false.c

@@ -0,0 +1,10 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2018 K. Lange
+ *
+ * false - returns a failure status code.
+ */
+int main() {
+	return 1;
+}

+ 407 - 0
apps/fetch.c

@@ -0,0 +1,407 @@
+/* vim: tabstop=4 shiftwidth=4 noexpandtab
+ * This file is part of ToaruOS and is released under the terms
+ * of the NCSA / University of Illinois License - see LICENSE.md
+ * Copyright (C) 2015-2018 K. Lange
+ *
+ * fetch - Retreive documents from HTTP servers.
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <time.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <toaru/hashmap.h>
+
+#define SIZE 512
+#define BOUNDARY "------ToaruOSFetchUploadBoundary"
+
+struct http_req {
+	char domain[SIZE];
+	char path[SIZE];
+};
+
+struct {
+	int show_headers;
+	const char * output_file;
+	const char * cookie;
+	FILE * out;
+	int prompt_password;
+	const char * upload_file;
+	char * password;
+	int show_progress;
+	size_t content_length;
+	size_t size;
+	struct timeval start;
+	int calculate_output;
+	int slow_upload;
+	int machine_readable;
+} fetch_options = {0};
+
+void parse_url(char * d, struct http_req * r) {
+	if (strstr(d, "http://") == d) {
+
+		d += strlen("http://");
+
+		char * s = strstr(d, "/");
+		if (!s) {
+			strcpy(r->domain, d);
+			strcpy(r->path, "");
+		} else {
+			*s = 0;
+			s++;
+			strcpy(r->domain, d);
+			strcpy(r->path, s);
+		}
+	} else {
+		fprintf(stderr, "sorry, can't parse %s\n", d);
+		exit(1);
+	}
+}
+
+#define BAR_WIDTH 20
+#define bar_perc "||||||||||||||||||||"
+#define bar_spac "                    "
+void print_progress(void) {
+	struct timeval now;
+	gettimeofday(&now, NULL);
+	fprintf(stderr,"\033[G%6dkB",(int)fetch_options.size/1024);
+	if (fetch_options.content_length) {
+		int percent = (fetch_options.size * BAR_WIDTH) / (fetch_options.content_length);
+		fprintf(stderr," / %6dkB [%.*s%.*s]", (int)fetch_options.content_length/1024, percent,bar_perc,BAR_WIDTH-percent,bar_spac);
+	}
+
+	double timediff = (double)(now.tv_sec - fetch_options.start.tv_sec) + (double)(now.tv_usec - fetch_options.start.tv_usec)/1000000.0;
+	if (timediff > 0.0) {
+		double rate = (double)(fetch_options.size) / timediff;
+		double s = rate/(1024.0) * 8.0;
+		if (s > 1024.0) {
+			fprintf(stderr," %.2f mbps", s/1024.0);
+		} else {
+			fprintf(stderr," %.2f kbps", s);
+		}
+
+		if (fetch_options.content_length) {
+			if (rate > 0.0) {
+				double remaining = (double)(fetch_options.content_length - fetch_options.size) / rate;
+
+				fprintf(stderr," (%.2f sec remaining)", remaining);
+			}
+		}
+	}
+	fprintf(stderr,"\033[K");
+	fflush(stderr);
+}
+
+int usage(char * argv[]) {
+	fprintf(stderr,
+			"fetch - download files over HTTP\n"
+			"\n"
+			"usage: %s [-hOvmp?] [-c cookie] [-o file] [-u file] [-s speed] URL\n"
+			"\n"
+			" -h     \033[3mshow headers\033[0m\n"
+			" -O     \033[3msave the file based on the filename in the URL\033[0m\n"
+			" -v     \033[3mshow progress\033[0m\n"
+			" -m     \033[3mmachine readable output\033[0m\n"
+			" -p     \033[3mprompt for password\033[0m\n"
+			" -c ... \033[3mset cookies\033[0m\n"
+			" -o ... \033[3msave to the specified file\033[0m\n"
+			" -u ... \033[3mupload the specified file\033[0m\n"
+			" -s ... \033[3mspecify the speed for uploading slowly\033[0m\n"
+			" -?     \033[3mshow this help text\033[0m\n"
+			"\n", argv[0]);
+	return 1;
+}
+
+int collect_password(char * password) {
+	fprintf(stdout, "Password for upload: ");
+	fflush(stdout);
+
+	/* Disable echo */
+	struct termios old, new;
+	tcgetattr(fileno(stdin), &old);
+	new = old;
+	new.c_lflag &= (~ECHO);
+	tcsetattr(fileno(stdin), TCSAFLUSH, &new);
+
+	fgets(password, 1024, stdin);
+	password[strlen(password)-1] = '\0';
+	tcsetattr(fileno(stdin), TCSAFLUSH, &old);
+	fprintf(stdout, "\n");
+
+	return 0;
+}
+
+void read_http_line(char * buf, FILE * f) {
+	memset(buf, 0x00, 256);
+
+	fgets(buf, 255, f);
+	char * _r = strchr(buf, '\r');
+	if (_r) {
+		*_r = '\0';
+	}
+	if (!_r) {
+		_r = strchr(buf, '\n'); /* that's not right, but, whatever */
+		if (_r) {
+			*_r = '\0';
+		}
+	}
+}
+
+void bad_response(void) {
+	fprintf(stderr, "Bad response.\n");
+	exit(1);
+}
+
+int http_fetch(FILE * f) {
+	hashmap_t * headers = hashmap_create(10);
+
+	/* Parse response */
+	{
+		char buf[256];
+		read_http_line(buf, f);
+
+		char * elements[3];
+
+		elements[0] = buf;
+		elements[1] = strchr(elements[0], ' ');
+		if (!elements[1]) bad_response();
+		*elements[1] = '\0';
+		elements[1]++;
+
+		elements[2] = strchr(elements[1], ' ');
+		if (!elements[2]) bad_response();
+		*elements[2] = '\0';
+		elements[2]++;
+
+		if (strcmp(elements[1], "200")) {
+			fprintf(stderr, "Bad response code: %s\n", elements[1]);
+			return 1;
+		}
+	}
+
+	/* Parse headers */
+	while (1) {
+		char buf[256];
+		read_http_line(buf, f);
+
+		if (!*buf) {
+			break;
+		}
+
+		/* Split */
+		char * name = buf;
+		char * value = strstr(buf, ": ");
+		if (!value) bad_response();
+		*value = '\0';
+		value += 2;
+
+		hashmap_set(headers, name, strdup(value));
+	}
+
+	if (fetch_options.show_headers) {
+		list_t * hash_keys = hashmap_keys(headers);
+		foreach(_key, hash_keys) {
+			char * key = (char *)_key->value;
+			fprintf(stderr, "[%s] = %s\n", key, (char*)hashmap_get(headers, key));
+		}
+		list_free(hash_keys);
+		free(hash_keys);
+	}
+
+	/* determine how many bytes we should read now */
+	if (!hashmap_has(headers, "Content-Length")) {
+		fprintf(stderr, "Don't know how much to read.\n");
+		return 1;
+	}
+
+	int bytes_to_read = atoi(hashmap_get(headers, "Content-Length"));
+	fetch_options.content_length = bytes_to_read;
+
+	while (bytes_to_read > 0) {
+		char buf[1024];
+		size_t r = fread(buf, 1, bytes_to_read < 1024 ? bytes_to_read : 1024, f);
+		fwrite(buf, 1, r, fetch_options.out);
+		fetch_options.size += r;
+		if (fetch_options.show_progress) {
+			print_progress();
+		}
+		if (fetch_options.machine_readable && fetch_options.content_length) {
+			fprintf(stdout,"%d %d\n",(int)fetch_options.size, (int)fetch_options.content_length);
+		}
+		bytes_to_read -= r;
+	}
+
+	return 0;
+}
+
+int main(int argc, char * argv[]) {
+
+	int opt;
+
+	while ((opt = getopt(argc, argv, "?c:hmo:Opu:vs:")) != -1) {
+		switch (opt) {
+			case '?':
+				return usage(argv);
+			case 'O':
+				fetch_options.calculate_output = 1;
+				break;
+			case 'c':
+				fetch_options.cookie = optarg;
+				break;
+			case 'h':
+				fetch_options.show_headers = 1;
+				break;
+			case 'o':
+				fetch_options.output_file = optarg;
+				break;
+			case 'u':
+				fetch_options.upload_file = optarg;
+				break;
+			case 'v':
+				fetch_options.show_progress = 1;
+				break;
+			case 'm':
+				fetch_options.machine_readable = 1;
+				break;
+			case 'p':
+				fetch_options.prompt_password = 1;
+				break;
+			case 's':
+				fetch_options.slow_upload = atoi(optarg);
+				break;
+		}
+	}
+
+	if (optind >= argc) {
+		return usage(argv);
+	}
+
+	struct http_req my_req;
+	parse_url(argv[optind], &my_req);
+
+	char file[100];
+	sprintf(file, "/dev/net/%s", my_req.domain);
+
+	if (fetch_options.calculate_output) {
+		char * tmp = strdup(my_req.path);
+		char * x = strrchr(tmp,'/');
+		if (x) {
+			tmp = x + 1;
+		}
+		fetch_options.output_file = tmp;
+	}
+
+	fetch_options.out = stdout;
+	if (fetch_options.output_file) {
+		fetch_options.out = fopen(fetch_options.output_file, "w+");
+	}
+
+	FILE * f = fopen(file,"r+");
+
+	if (!f) {
+		fprintf(stderr, "Nope.\n");
+		return 1;
+	}
+
+	if (fetch_options.prompt_password) {
+		fetch_options.password = malloc(100);
+		collect_password(fetch_options.password);
+	}
+
+	if (fetch_options.upload_file) {
+		FILE * in_file = fopen(fetch_options.upload_file, "r");
+
+		srand(time(NULL));
+		int boundary_fuzz = rand();
+		char tmp[512];
+
+		size_t out_size = 0;
+		if (fetch_options.password) {
+			out_size += sprintf(tmp,
+				"--" BOUNDARY "%08x\r\n"
+				"Content-Disposition: form-data; name=\"password\"\r\n"
+				"\r\n"
+				"%s\r\n",boundary_fuzz, fetch_options.password);
+		}
+
+		out_size += strlen("--" BOUNDARY "00000000\r\n"
+				"Content-Disposition: form-data; name=\"file\"; filename=\"\"\r\n"
+				"Content-Type: application/octet-stream\r\n"
+				"\r\n"
+				/* Data goes here */
+				"\r\n"
+				"--" BOUNDARY "00000000" "--\r\n");
+
+		out_size += strlen(fetch_options.upload_file);
+
+		fseek(in_file, 0, SEEK_END);
+		out_size += ftell(in_file);
+		fseek(in_file, 0, SEEK_SET);
+
+		fprintf(f,
+			"POST /%s HTTP/1.0\r\n"
+			"User-Agent: curl/7.35.0\r\n"
+			"Host: %s\r\n"
+			"Accept: */*\r\n"
+			"Content-Length: %d\r\n"
+			"Content-Type: multipart/form-data; boundary=" BOUNDARY "%08x\r\n"
+			"\r\n", my_req.path, my_req.domain, (int)out_size, boundary_fuzz);
+
+		fprintf(f,"%s",tmp);
+		fprintf(f,
+				"--" BOUNDARY "%08x\r\n"
+				"Content-Disposition: form-data; name=\"file\"; filename=\"%s\"\r\n"
+				"Content-Type: application/octet-stream\r\n"
+				"\r\n", boundary_fuzz, fetch_options.upload_file);
+
+		while (!feof(in_file)) {
+			char buf[1024];